1use crate::model::buffer::Buffer;
7use crate::model::marker::{MarkerId, MarkerList};
8
9#[derive(Debug, Clone)]
11pub struct FoldRange {
12 start_marker: MarkerId,
14 end_marker: MarkerId,
16 placeholder: Option<String>,
18}
19
20#[derive(Debug, Clone)]
22pub struct ResolvedFoldRange {
23 pub header_line: usize,
25 pub start_line: usize,
27 pub end_line: usize,
29 pub start_byte: usize,
31 pub end_byte: usize,
33 pub header_byte: usize,
35 pub placeholder: Option<String>,
37}
38
39#[derive(Debug, Clone)]
41pub struct CollapsedFoldLineRange {
42 pub header_line: usize,
44 pub end_line: usize,
46 pub placeholder: Option<String>,
48}
49
50#[derive(Debug, Clone)]
52pub struct FoldManager {
53 ranges: Vec<FoldRange>,
54}
55
56impl FoldManager {
57 pub fn new() -> Self {
59 Self { ranges: Vec::new() }
60 }
61
62 pub fn is_empty(&self) -> bool {
64 self.ranges.is_empty()
65 }
66
67 pub fn add(
69 &mut self,
70 marker_list: &mut MarkerList,
71 start: usize,
72 end: usize,
73 placeholder: Option<String>,
74 ) {
75 if end <= start {
76 return;
77 }
78
79 let start_marker = marker_list.create(start, true); let end_marker = marker_list.create(end, false); self.ranges.push(FoldRange {
83 start_marker,
84 end_marker,
85 placeholder,
86 });
87 }
88
89 pub fn clear(&mut self, marker_list: &mut MarkerList) {
91 for range in &self.ranges {
92 marker_list.delete(range.start_marker);
93 marker_list.delete(range.end_marker);
94 }
95 self.ranges.clear();
96 }
97
98 pub fn remove_if_contains_byte(&mut self, marker_list: &mut MarkerList, byte: usize) -> bool {
101 let mut to_delete = Vec::new();
102
103 self.ranges.retain(|range| {
104 let Some(start_byte) = marker_list.get_position(range.start_marker) else {
105 return true;
106 };
107 let Some(end_byte) = marker_list.get_position(range.end_marker) else {
108 return true;
109 };
110 if start_byte <= byte && byte < end_byte {
111 to_delete.push((range.start_marker, range.end_marker));
112 false
113 } else {
114 true
115 }
116 });
117
118 for (start, end) in &to_delete {
119 marker_list.delete(*start);
120 marker_list.delete(*end);
121 }
122
123 !to_delete.is_empty()
124 }
125
126 pub fn resolved_ranges(
128 &self,
129 buffer: &Buffer,
130 marker_list: &MarkerList,
131 ) -> Vec<ResolvedFoldRange> {
132 let mut ranges = Vec::new();
133
134 for range in &self.ranges {
135 let Some(start_byte) = marker_list.get_position(range.start_marker) else {
136 continue;
137 };
138 let Some(end_byte) = marker_list.get_position(range.end_marker) else {
139 continue;
140 };
141 if end_byte <= start_byte {
142 continue;
143 }
144
145 let start_line = buffer.get_line_number(start_byte);
146 if start_line == 0 {
147 continue;
148 }
149 let end_line = buffer.get_line_number(end_byte.saturating_sub(1));
150 if end_line < start_line {
151 continue;
152 }
153
154 let header_byte =
155 indent_folding::find_line_start_byte(buffer, start_byte.saturating_sub(1));
156
157 ranges.push(ResolvedFoldRange {
158 header_line: start_line - 1,
159 start_line,
160 end_line,
161 start_byte,
162 end_byte,
163 header_byte,
164 placeholder: range.placeholder.clone(),
165 });
166 }
167
168 ranges
169 }
170
171 pub fn collapsed_header_bytes(
173 &self,
174 buffer: &Buffer,
175 marker_list: &MarkerList,
176 ) -> std::collections::BTreeMap<usize, Option<String>> {
177 let mut map = std::collections::BTreeMap::new();
178 for range in self.resolved_ranges(buffer, marker_list) {
179 map.insert(range.header_byte, range.placeholder);
180 }
181 map
182 }
183
184 pub fn remove_by_header_byte(
187 &mut self,
188 buffer: &Buffer,
189 marker_list: &mut MarkerList,
190 target_header_byte: usize,
191 ) -> bool {
192 let mut to_delete = Vec::new();
193
194 self.ranges.retain(|range| {
195 let Some(start_byte) = marker_list.get_position(range.start_marker) else {
196 return true;
197 };
198 let current_header =
199 indent_folding::find_line_start_byte(buffer, start_byte.saturating_sub(1));
200 if current_header == target_header_byte {
201 to_delete.push((range.start_marker, range.end_marker));
202 false
203 } else {
204 true
205 }
206 });
207
208 for (start, end) in &to_delete {
209 marker_list.delete(*start);
210 marker_list.delete(*end);
211 }
212
213 !to_delete.is_empty()
214 }
215
216 pub fn collapsed_line_ranges(
218 &self,
219 buffer: &Buffer,
220 marker_list: &MarkerList,
221 ) -> Vec<CollapsedFoldLineRange> {
222 self.resolved_ranges(buffer, marker_list)
223 .into_iter()
224 .map(|range| CollapsedFoldLineRange {
225 header_line: range.header_line,
226 end_line: range.end_line,
227 placeholder: range.placeholder,
228 })
229 .collect()
230 }
231
232 pub fn hidden_line_count_in_range(
234 &self,
235 buffer: &Buffer,
236 marker_list: &MarkerList,
237 start_line: usize,
238 end_line: usize,
239 ) -> usize {
240 let mut hidden = 0usize;
241 for range in self.resolved_ranges(buffer, marker_list) {
242 if range.header_line >= start_line && range.header_line <= end_line {
243 hidden = hidden.saturating_add(range.end_line.saturating_sub(range.start_line) + 1);
244 }
245 }
246 hidden
247 }
248}
249
250impl Default for FoldManager {
251 fn default() -> Self {
252 Self::new()
253 }
254}
255
256pub mod indent_folding {
262 use crate::model::buffer::Buffer;
263 use crate::primitives::indent_pattern::PatternIndentCalculator;
264
265 pub fn find_line_start_byte(buffer: &Buffer, pos: usize) -> usize {
268 if pos == 0 {
269 return 0;
270 }
271 let mut p = pos.min(buffer.len()).saturating_sub(1);
272 loop {
273 match PatternIndentCalculator::byte_at(buffer, p) {
274 Some(b'\n') => return p + 1,
275 None => return 0,
276 _ => {
277 if p == 0 {
278 return 0;
279 }
280 p -= 1;
281 }
282 }
283 }
284 }
285
286 fn slice_indent(line: &[u8], tab_size: usize) -> (usize, bool) {
288 let mut indent = 0;
289 let mut all_blank = true;
290 for &b in line {
291 match b {
292 b' ' => indent += 1,
293 b'\t' => {
294 if tab_size > 0 {
295 indent += tab_size - (indent % tab_size);
296 } else {
297 indent += 1;
298 }
299 }
300 b'\r' => {}
301 _ => {
302 all_blank = false;
303 break;
304 }
305 }
306 }
307 (indent, all_blank)
308 }
309
310 pub fn foldable_lines_in_bytes(
321 bytes: &[u8],
322 tab_size: usize,
323 max_lookahead: usize,
324 ) -> Vec<usize> {
325 let lines: Vec<&[u8]> = bytes.split(|&b| b == b'\n').collect();
327 let line_count = lines.len();
328 let mut result = Vec::new();
329
330 for i in 0..line_count {
331 let (header_indent, header_blank) = slice_indent(lines[i], tab_size);
332 if header_blank {
333 continue;
334 }
335
336 let limit = line_count.min(i + 1 + max_lookahead);
338 let mut next = i + 1;
339 while next < limit {
340 let (_, blank) = slice_indent(lines[next], tab_size);
341 if !blank {
342 break;
343 }
344 next += 1;
345 }
346 if next >= limit {
347 continue;
348 }
349
350 let (next_indent, _) = slice_indent(lines[next], tab_size);
351 if next_indent > header_indent {
352 result.push(i);
353 }
354 }
355
356 result
357 }
358
359 pub fn indent_fold_end_byte(
366 buffer: &Buffer,
367 header_byte: usize,
368 tab_size: usize,
369 max_scan_bytes: usize,
370 ) -> Option<usize> {
371 let buf_len = buffer.len();
372 let end = buf_len.min(header_byte.saturating_add(max_scan_bytes));
373 let bytes = buffer.slice_bytes(header_byte..end);
374 if bytes.is_empty() {
375 return None;
376 }
377
378 let lines: Vec<&[u8]> = bytes.split(|&b| b == b'\n').collect();
379 if lines.is_empty() {
380 return None;
381 }
382
383 let (header_indent, header_blank) = slice_indent(lines[0], tab_size);
384 if header_blank {
385 return None;
386 }
387
388 let mut next = 1;
390 while next < lines.len() {
391 let (_, blank) = slice_indent(lines[next], tab_size);
392 if !blank {
393 break;
394 }
395 next += 1;
396 }
397 if next >= lines.len() {
398 return None;
399 }
400
401 let (next_indent, _) = slice_indent(lines[next], tab_size);
402 if next_indent <= header_indent {
403 return None;
404 }
405
406 let mut last_non_blank_line = next;
408 let mut current = next + 1;
409 while current < lines.len() {
410 let (indent, blank) = slice_indent(lines[current], tab_size);
411 if blank {
412 current += 1;
413 continue;
414 }
415 if indent <= header_indent {
416 break;
417 }
418 last_non_blank_line = current;
419 current += 1;
420 }
421
422 if last_non_blank_line < 1 {
423 return None;
424 }
425
426 let mut byte_offset = 0;
429 for i in 0..last_non_blank_line {
430 byte_offset += lines[i].len() + 1; }
432 Some(header_byte + byte_offset)
433 }
434
435 pub fn find_next_line_start_byte(buffer: &Buffer, pos: usize) -> usize {
439 let mut p = pos;
440 let len = buffer.len();
441 while p < len {
442 match PatternIndentCalculator::byte_at(buffer, p) {
443 Some(b'\n') => return p + 1,
444 None => return len,
445 _ => p += 1,
446 }
447 }
448 len
449 }
450
451 pub fn find_fold_range_at_byte(
466 buffer: &Buffer,
467 target_byte: usize,
468 tab_size: usize,
469 max_scan_bytes: usize,
470 max_upward_lines: usize,
471 ) -> Option<(usize, usize, usize)> {
472 let mut header_byte = find_line_start_byte(buffer, target_byte);
473
474 for _ in 0..=max_upward_lines {
475 if let Some(fold_end_byte) =
476 indent_fold_end_byte(buffer, header_byte, tab_size, max_scan_bytes)
477 {
478 if fold_end_byte >= target_byte {
479 let eb = find_next_line_start_byte(buffer, fold_end_byte);
480 let sb = find_next_line_start_byte(buffer, header_byte);
481 if sb < eb {
482 return Some((header_byte, sb, eb));
483 }
484 }
485 }
486 if header_byte == 0 {
487 break;
488 }
489 header_byte = find_line_start_byte(buffer, header_byte.saturating_sub(1));
490 }
491
492 None
493 }
494
495 #[cfg(test)]
496 mod tests {
497 use super::*;
498
499 #[test]
500 fn test_slice_indent_spaces() {
501 assert_eq!(slice_indent(b" hello", 4), (4, false));
502 assert_eq!(slice_indent(b"hello", 4), (0, false));
503 assert_eq!(slice_indent(b" deep", 4), (8, false));
504 }
505
506 #[test]
507 fn test_slice_indent_tabs() {
508 assert_eq!(slice_indent(b"\thello", 4), (4, false));
509 assert_eq!(slice_indent(b"\t\thello", 4), (8, false));
510 assert_eq!(slice_indent(b" \thello", 4), (4, false));
512 }
513
514 #[test]
515 fn test_slice_indent_blank() {
516 assert_eq!(slice_indent(b"", 4), (0, true));
517 assert_eq!(slice_indent(b" ", 4), (3, true));
518 assert_eq!(slice_indent(b" \r", 4), (2, true));
519 }
520
521 #[test]
522 fn test_foldable_lines_basic() {
523 let text = b"fn main() {\n println!();\n}\n";
524 let foldable = foldable_lines_in_bytes(text, 4, 50);
525 assert_eq!(foldable, vec![0]); }
527
528 #[test]
529 fn test_foldable_lines_nested() {
530 let text = b"fn main() {\n if true {\n x();\n }\n}\n";
531 let foldable = foldable_lines_in_bytes(text, 4, 50);
532 assert_eq!(foldable, vec![0, 1]); }
534
535 #[test]
536 fn test_foldable_lines_not_foldable() {
537 let text = b"line1\nline2\nline3\n";
538 let foldable = foldable_lines_in_bytes(text, 4, 50);
539 assert!(foldable.is_empty());
540 }
541
542 #[test]
543 fn test_foldable_lines_blank_lines_skipped() {
544 let text = b"fn main() {\n\n println!();\n}\n";
546 let foldable = foldable_lines_in_bytes(text, 4, 50);
547 assert_eq!(foldable, vec![0]);
548 }
549
550 #[test]
551 fn test_foldable_lines_max_lookahead() {
552 let text = b"fn main() {\n\n\n println!();\n}\n";
555 let foldable_short = foldable_lines_in_bytes(text, 4, 1);
556 assert!(foldable_short.is_empty());
557
558 let foldable_long = foldable_lines_in_bytes(text, 4, 50);
559 assert_eq!(foldable_long, vec![0]);
560 }
561 }
562}