1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub struct Fold {
33 pub start_row: usize,
35 pub end_row: usize,
37 pub closed: bool,
39}
40
41impl Fold {
42 pub fn contains(&self, row: usize) -> bool {
43 row >= self.start_row && row <= self.end_row
44 }
45
46 pub fn hides(&self, row: usize) -> bool {
49 self.closed && row > self.start_row && row <= self.end_row
50 }
51
52 pub fn line_count(&self) -> usize {
54 self.end_row.saturating_sub(self.start_row) + 1
55 }
56}
57
58impl crate::Buffer {
59 pub fn folds(&self) -> &[Fold] {
60 let c = self.content.lock().unwrap();
64 let ptr = c.folds.as_ptr();
65 let len = c.folds.len();
66 drop(c);
67 unsafe { std::slice::from_raw_parts(ptr, len) }
68 }
69
70 pub fn add_fold(&mut self, start_row: usize, end_row: usize, closed: bool) {
74 if end_row < start_row {
75 return;
76 }
77 let last = self.row_count().saturating_sub(1);
78 if start_row > last {
79 return;
80 }
81 let end_row = end_row.min(last);
82 let fold = Fold {
83 start_row,
84 end_row,
85 closed,
86 };
87 {
88 let mut c = self.content_lock_mut();
89 if let Some(idx) = c.folds.iter().position(|f| f.start_row == start_row) {
90 c.folds[idx] = fold;
91 } else {
92 let pos = c
93 .folds
94 .iter()
95 .position(|f| f.start_row > start_row)
96 .unwrap_or(c.folds.len());
97 c.folds.insert(pos, fold);
98 }
99 }
100 self.dirty_gen_bump();
101 }
102
103 pub fn remove_fold_at(&mut self, row: usize) -> bool {
106 let idx = self
107 .content_lock()
108 .folds
109 .iter()
110 .position(|f| f.contains(row));
111 let Some(idx) = idx else {
112 return false;
113 };
114 self.content_lock_mut().folds.remove(idx);
115 self.dirty_gen_bump();
116 true
117 }
118
119 pub fn open_fold_at(&mut self, row: usize) -> bool {
121 let changed = {
122 let mut c = self.content_lock_mut();
123 let Some(f) = c.folds.iter_mut().find(|f| f.contains(row)) else {
124 return false;
125 };
126 if !f.closed {
127 return false;
128 }
129 f.closed = false;
130 true
131 };
132 if changed {
133 self.dirty_gen_bump();
134 }
135 changed
136 }
137
138 pub fn close_fold_at(&mut self, row: usize) -> bool {
140 let changed = {
141 let mut c = self.content_lock_mut();
142 let Some(f) = c.folds.iter_mut().find(|f| f.contains(row)) else {
143 return false;
144 };
145 if f.closed {
146 return false;
147 }
148 f.closed = true;
149 true
150 };
151 if changed {
152 self.dirty_gen_bump();
153 }
154 changed
155 }
156
157 pub fn toggle_fold_at(&mut self, row: usize) -> bool {
159 let changed = {
160 let mut c = self.content_lock_mut();
161 let Some(f) = c.folds.iter_mut().find(|f| f.contains(row)) else {
162 return false;
163 };
164 f.closed = !f.closed;
165 true
166 };
167 if changed {
168 self.dirty_gen_bump();
169 }
170 changed
171 }
172
173 pub fn open_all_folds(&mut self) {
175 let changed = {
176 let mut c = self.content_lock_mut();
177 let mut any = false;
178 for f in c.folds.iter_mut() {
179 if f.closed {
180 f.closed = false;
181 any = true;
182 }
183 }
184 any
185 };
186 if changed {
187 self.dirty_gen_bump();
188 }
189 }
190
191 pub fn clear_all_folds(&mut self) {
193 let was_nonempty = !self.content_lock().folds.is_empty();
194 if was_nonempty {
195 self.content_lock_mut().folds.clear();
196 self.dirty_gen_bump();
197 }
198 }
199
200 pub fn close_all_folds(&mut self) {
202 let changed = {
203 let mut c = self.content_lock_mut();
204 let mut any = false;
205 for f in c.folds.iter_mut() {
206 if !f.closed {
207 f.closed = true;
208 any = true;
209 }
210 }
211 any
212 };
213 if changed {
214 self.dirty_gen_bump();
215 }
216 }
217
218 pub fn fold_at_row(&self, row: usize) -> Option<&Fold> {
221 self.folds().iter().find(|f| f.contains(row))
223 }
224
225 pub fn is_row_hidden(&self, row: usize) -> bool {
227 self.folds().iter().any(|f| f.hides(row))
228 }
229
230 pub fn next_visible_row(&self, row: usize) -> Option<usize> {
233 let last = self.row_count().saturating_sub(1);
234 if last == 0 && row == 0 {
235 return None;
236 }
237 let mut r = row.checked_add(1)?;
238 while r <= last && self.is_row_hidden(r) {
239 r += 1;
240 }
241 (r <= last).then_some(r)
242 }
243
244 pub fn prev_visible_row(&self, row: usize) -> Option<usize> {
246 let mut r = row.checked_sub(1)?;
247 while self.is_row_hidden(r) {
248 r = r.checked_sub(1)?;
249 }
250 Some(r)
251 }
252
253 pub fn invalidate_folds_in_range(&mut self, start_row: usize, end_row: usize) {
255 let before = self.content_lock().folds.len();
256 self.content_lock_mut()
257 .folds
258 .retain(|f| f.end_row < start_row || f.start_row > end_row);
259 if self.content_lock().folds.len() != before {
260 self.dirty_gen_bump();
261 }
262 }
263}
264
265#[cfg(test)]
266mod tests {
267 use crate::Buffer;
268
269 fn b() -> Buffer {
270 Buffer::from_str("a\nb\nc\nd\ne")
271 }
272
273 #[test]
274 fn add_keeps_folds_in_start_row_order() {
275 let mut buf = b();
276 buf.add_fold(2, 3, true);
277 buf.add_fold(0, 1, false);
278 let starts: Vec<usize> = buf.folds().iter().map(|f| f.start_row).collect();
279 assert_eq!(starts, vec![0, 2]);
280 }
281
282 #[test]
283 fn add_replaces_existing_with_same_start_row() {
284 let mut buf = b();
285 buf.add_fold(1, 2, true);
286 buf.add_fold(1, 4, false);
287 assert_eq!(buf.folds().len(), 1);
288 assert_eq!(buf.folds()[0].end_row, 4);
289 assert!(!buf.folds()[0].closed);
290 }
291
292 #[test]
293 fn add_clamps_end_row_to_buffer_bounds() {
294 let mut buf = b();
295 buf.add_fold(2, 99, true);
296 assert_eq!(buf.folds()[0].end_row, 4);
297 }
298
299 #[test]
300 fn add_rejects_inverted_range() {
301 let mut buf = b();
302 buf.add_fold(3, 1, true);
303 assert!(buf.folds().is_empty());
304 }
305
306 #[test]
307 fn toggle_flips_state() {
308 let mut buf = b();
309 buf.add_fold(1, 3, false);
310 assert!(!buf.folds()[0].closed);
311 assert!(buf.toggle_fold_at(2));
312 assert!(buf.folds()[0].closed);
313 assert!(buf.toggle_fold_at(2));
314 assert!(!buf.folds()[0].closed);
315 }
316
317 #[test]
318 fn is_row_hidden_excludes_start_row() {
319 let mut buf = b();
320 buf.add_fold(1, 3, true);
321 assert!(!buf.is_row_hidden(0));
322 assert!(!buf.is_row_hidden(1)); assert!(buf.is_row_hidden(2));
324 assert!(buf.is_row_hidden(3));
325 assert!(!buf.is_row_hidden(4));
326 }
327
328 #[test]
329 fn open_close_all_changes_every_fold() {
330 let mut buf = b();
331 buf.add_fold(0, 1, false);
332 buf.add_fold(2, 3, true);
333 buf.close_all_folds();
334 assert!(buf.folds().iter().all(|f| f.closed));
335 buf.open_all_folds();
336 assert!(buf.folds().iter().all(|f| !f.closed));
337 }
338
339 #[test]
340 fn invalidate_drops_overlapping_folds() {
341 let mut buf = b();
342 buf.add_fold(0, 1, true);
343 buf.add_fold(2, 3, true);
344 buf.add_fold(4, 4, true);
345 buf.invalidate_folds_in_range(2, 3);
346 let starts: Vec<usize> = buf.folds().iter().map(|f| f.start_row).collect();
347 assert_eq!(starts, vec![0, 4]);
348 }
349}