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 pub header_text: Option<String>,
51}
52
53#[derive(Debug, Clone)]
55pub struct FoldManager {
56 ranges: Vec<FoldRange>,
57}
58
59impl FoldManager {
60 pub fn new() -> Self {
62 Self { ranges: Vec::new() }
63 }
64
65 pub fn is_empty(&self) -> bool {
67 self.ranges.is_empty()
68 }
69
70 pub fn add(
72 &mut self,
73 marker_list: &mut MarkerList,
74 start: usize,
75 end: usize,
76 placeholder: Option<String>,
77 ) {
78 if end <= start {
79 return;
80 }
81
82 let start_marker = marker_list.create(start, true); let end_marker = marker_list.create(end, false); self.ranges.push(FoldRange {
86 start_marker,
87 end_marker,
88 placeholder,
89 });
90 }
91
92 pub fn clear(&mut self, marker_list: &mut MarkerList) {
94 for range in &self.ranges {
95 marker_list.delete(range.start_marker);
96 marker_list.delete(range.end_marker);
97 }
98 self.ranges.clear();
99 }
100
101 pub fn remove_if_contains_byte(&mut self, marker_list: &mut MarkerList, byte: usize) -> bool {
104 let mut to_delete = Vec::new();
105
106 self.ranges.retain(|range| {
107 let Some(start_byte) = marker_list.get_position(range.start_marker) else {
108 return true;
109 };
110 let Some(end_byte) = marker_list.get_position(range.end_marker) else {
111 return true;
112 };
113 if start_byte <= byte && byte < end_byte {
114 to_delete.push((range.start_marker, range.end_marker));
115 false
116 } else {
117 true
118 }
119 });
120
121 for (start, end) in &to_delete {
122 marker_list.delete(*start);
123 marker_list.delete(*end);
124 }
125
126 !to_delete.is_empty()
127 }
128
129 pub fn resolved_ranges(
131 &self,
132 buffer: &Buffer,
133 marker_list: &MarkerList,
134 ) -> Vec<ResolvedFoldRange> {
135 let mut ranges = Vec::new();
136
137 for range in &self.ranges {
138 let Some(start_byte) = marker_list.get_position(range.start_marker) else {
139 continue;
140 };
141 let Some(end_byte) = marker_list.get_position(range.end_marker) else {
142 continue;
143 };
144 if end_byte <= start_byte {
145 continue;
146 }
147
148 let start_line = buffer.get_line_number(start_byte);
149 if start_line == 0 {
150 continue;
151 }
152 let end_line = buffer.get_line_number(end_byte.saturating_sub(1));
153 if end_line < start_line {
154 continue;
155 }
156
157 let header_byte =
158 indent_folding::find_line_start_byte(buffer, start_byte.saturating_sub(1));
159
160 ranges.push(ResolvedFoldRange {
161 header_line: start_line - 1,
162 start_line,
163 end_line,
164 start_byte,
165 end_byte,
166 header_byte,
167 placeholder: range.placeholder.clone(),
168 });
169 }
170
171 ranges
172 }
173
174 pub fn collapsed_header_bytes(
176 &self,
177 buffer: &Buffer,
178 marker_list: &MarkerList,
179 ) -> std::collections::BTreeMap<usize, Option<String>> {
180 let mut map = std::collections::BTreeMap::new();
181 for range in self.resolved_ranges(buffer, marker_list) {
182 map.insert(range.header_byte, range.placeholder);
183 }
184 map
185 }
186
187 pub fn remove_by_header_byte(
190 &mut self,
191 buffer: &Buffer,
192 marker_list: &mut MarkerList,
193 target_header_byte: usize,
194 ) -> bool {
195 let mut to_delete = Vec::new();
196
197 self.ranges.retain(|range| {
198 let Some(start_byte) = marker_list.get_position(range.start_marker) else {
199 return true;
200 };
201 let current_header =
202 indent_folding::find_line_start_byte(buffer, start_byte.saturating_sub(1));
203 if current_header == target_header_byte {
204 to_delete.push((range.start_marker, range.end_marker));
205 false
206 } else {
207 true
208 }
209 });
210
211 for (start, end) in &to_delete {
212 marker_list.delete(*start);
213 marker_list.delete(*end);
214 }
215
216 !to_delete.is_empty()
217 }
218
219 pub fn collapsed_line_ranges(
224 &self,
225 buffer: &Buffer,
226 marker_list: &MarkerList,
227 ) -> Vec<CollapsedFoldLineRange> {
228 self.resolved_ranges(buffer, marker_list)
229 .into_iter()
230 .map(|range| {
231 let header_text = buffer.get_line(range.header_line).map(|bytes| {
232 String::from_utf8_lossy(&bytes)
233 .trim_end_matches('\n')
234 .trim_end_matches('\r')
235 .to_string()
236 });
237 CollapsedFoldLineRange {
238 header_line: range.header_line,
239 end_line: range.end_line,
240 placeholder: range.placeholder,
241 header_text,
242 }
243 })
244 .collect()
245 }
246
247 pub fn hidden_line_count_in_range(
249 &self,
250 buffer: &Buffer,
251 marker_list: &MarkerList,
252 start_line: usize,
253 end_line: usize,
254 ) -> usize {
255 let mut hidden = 0usize;
256 for range in self.resolved_ranges(buffer, marker_list) {
257 if range.header_line >= start_line && range.header_line <= end_line {
258 hidden = hidden.saturating_add(range.end_line.saturating_sub(range.start_line) + 1);
259 }
260 }
261 hidden
262 }
263}
264
265#[derive(Debug, Clone)]
271struct LspFoldEntry {
272 start_marker: MarkerId,
277 end_marker: MarkerId,
280 kind: Option<lsp_types::FoldingRangeKind>,
282 collapsed_text: Option<String>,
284}
285
286#[derive(Debug, Clone, Default)]
291pub struct LspFoldRanges {
292 ranges: Vec<LspFoldEntry>,
293}
294
295impl LspFoldRanges {
296 pub fn new() -> Self {
298 Self::default()
299 }
300
301 pub fn is_empty(&self) -> bool {
303 self.ranges.is_empty()
304 }
305
306 pub fn len(&self) -> usize {
308 self.ranges.len()
309 }
310
311 pub fn clear(&mut self, marker_list: &mut MarkerList) {
313 for range in &self.ranges {
314 marker_list.delete(range.start_marker);
315 marker_list.delete(range.end_marker);
316 }
317 self.ranges.clear();
318 }
319
320 pub fn set_from_lsp(
326 &mut self,
327 buffer: &Buffer,
328 marker_list: &mut MarkerList,
329 ranges: impl IntoIterator<Item = lsp_types::FoldingRange>,
330 ) {
331 self.clear(marker_list);
332 for r in ranges {
333 let Some(start_byte) = buffer.line_start_offset(r.start_line as usize) else {
334 continue;
335 };
336 let Some(end_byte) = buffer.line_start_offset(r.end_line as usize) else {
337 continue;
338 };
339 let start_marker = marker_list.create(start_byte, false);
343 let end_marker = marker_list.create(end_byte, false);
344 self.ranges.push(LspFoldEntry {
345 start_marker,
346 end_marker,
347 kind: r.kind,
348 collapsed_text: r.collapsed_text,
349 });
350 }
351 }
352
353 pub fn resolved(
359 &self,
360 buffer: &Buffer,
361 marker_list: &MarkerList,
362 ) -> Vec<lsp_types::FoldingRange> {
363 self.ranges
364 .iter()
365 .filter_map(|r| {
366 let start_byte = marker_list.get_position(r.start_marker)?;
367 let end_byte = marker_list.get_position(r.end_marker)?;
368 let start_line = buffer.get_line_number(start_byte);
369 let end_line = buffer.get_line_number(end_byte);
370 if end_line <= start_line {
371 return None;
372 }
373 Some(lsp_types::FoldingRange {
374 start_line: start_line as u32,
375 end_line: end_line as u32,
376 start_character: None,
377 end_character: None,
378 kind: r.kind.clone(),
379 collapsed_text: r.collapsed_text.clone(),
380 })
381 })
382 .collect()
383 }
384}
385
386impl Default for FoldManager {
387 fn default() -> Self {
388 Self::new()
389 }
390}
391
392pub mod indent_folding {
398 use crate::model::buffer::Buffer;
399 use crate::primitives::indent_pattern::PatternIndentCalculator;
400
401 pub fn find_line_start_byte(buffer: &Buffer, pos: usize) -> usize {
404 if pos == 0 {
405 return 0;
406 }
407 let mut p = pos.min(buffer.len()).saturating_sub(1);
408 loop {
409 match PatternIndentCalculator::byte_at(buffer, p) {
410 Some(b'\n') => return p + 1,
411 None => return 0,
412 _ => {
413 if p == 0 {
414 return 0;
415 }
416 p -= 1;
417 }
418 }
419 }
420 }
421
422 pub fn find_line_end_byte(buffer: &Buffer, pos: usize) -> usize {
426 let buf_len = buffer.len();
427 let mut p = pos;
428 while p < buf_len {
429 match PatternIndentCalculator::byte_at(buffer, p) {
430 Some(b'\n') => return p + 1,
431 None => return buf_len,
432 _ => p += 1,
433 }
434 }
435 buf_len
436 }
437
438 fn slice_indent(line: &[u8], tab_size: usize) -> (usize, bool) {
440 let mut indent = 0;
441 let mut all_blank = true;
442 for &b in line {
443 match b {
444 b' ' => indent += 1,
445 b'\t' => {
446 if tab_size > 0 {
447 indent += tab_size - (indent % tab_size);
448 } else {
449 indent += 1;
450 }
451 }
452 b'\r' => {}
453 _ => {
454 all_blank = false;
455 break;
456 }
457 }
458 }
459 (indent, all_blank)
460 }
461
462 pub fn is_line_foldable_in_bytes(lines: &[&[u8]], tab_size: usize) -> bool {
465 if lines.is_empty() {
466 return false;
467 }
468
469 let (header_indent, header_blank) = slice_indent(lines[0], tab_size);
470 if header_blank {
471 return false;
472 }
473
474 let mut next = 1;
476 while next < lines.len() {
477 let (_, blank) = slice_indent(lines[next], tab_size);
478 if !blank {
479 break;
480 }
481 next += 1;
482 }
483
484 if next >= lines.len() {
485 return false;
486 }
487
488 let (next_indent, _) = slice_indent(lines[next], tab_size);
489 next_indent > header_indent
490 }
491
492 pub fn indent_fold_end_byte(
499 buffer: &Buffer,
500 header_byte: usize,
501 tab_size: usize,
502 max_scan_bytes: usize,
503 ) -> Option<usize> {
504 let buf_len = buffer.len();
505 let end = buf_len.min(header_byte.saturating_add(max_scan_bytes));
506 let bytes = buffer.slice_bytes(header_byte..end);
507 if bytes.is_empty() {
508 return None;
509 }
510
511 let lines: Vec<&[u8]> = bytes.split(|&b| b == b'\n').collect();
512 if lines.is_empty() {
513 return None;
514 }
515
516 let (header_indent, header_blank) = slice_indent(lines[0], tab_size);
517 if header_blank {
518 return None;
519 }
520
521 let mut next = 1;
523 while next < lines.len() {
524 let (_, blank) = slice_indent(lines[next], tab_size);
525 if !blank {
526 break;
527 }
528 next += 1;
529 }
530 if next >= lines.len() {
531 return None;
532 }
533
534 let (next_indent, _) = slice_indent(lines[next], tab_size);
535 if next_indent <= header_indent {
536 return None;
537 }
538
539 let mut last_non_blank_line = next;
541 let mut current = next + 1;
542 while current < lines.len() {
543 let (indent, blank) = slice_indent(lines[current], tab_size);
544 if blank {
545 current += 1;
546 continue;
547 }
548 if indent <= header_indent {
549 break;
550 }
551 last_non_blank_line = current;
552 current += 1;
553 }
554
555 if last_non_blank_line < 1 {
556 return None;
557 }
558
559 let mut byte_offset = 0;
562 for line in &lines[..last_non_blank_line] {
563 byte_offset += line.len() + 1; }
565 Some(header_byte + byte_offset)
566 }
567
568 pub fn find_next_line_start_byte(buffer: &Buffer, pos: usize) -> usize {
572 let mut p = pos;
573 let len = buffer.len();
574 while p < len {
575 match PatternIndentCalculator::byte_at(buffer, p) {
576 Some(b'\n') => return p + 1,
577 None => return len,
578 _ => p += 1,
579 }
580 }
581 len
582 }
583
584 pub fn find_fold_range_at_byte(
599 buffer: &Buffer,
600 target_byte: usize,
601 tab_size: usize,
602 max_scan_bytes: usize,
603 max_upward_lines: usize,
604 ) -> Option<(usize, usize, usize)> {
605 let mut header_byte = find_line_start_byte(buffer, target_byte);
606
607 for _ in 0..=max_upward_lines {
608 if let Some(fold_end_byte) =
609 indent_fold_end_byte(buffer, header_byte, tab_size, max_scan_bytes)
610 {
611 if fold_end_byte >= target_byte {
612 let eb = find_next_line_start_byte(buffer, fold_end_byte);
613 let sb = find_next_line_start_byte(buffer, header_byte);
614 if sb < eb {
615 return Some((header_byte, sb, eb));
616 }
617 }
618 }
619 if header_byte == 0 {
620 break;
621 }
622 header_byte = find_line_start_byte(buffer, header_byte.saturating_sub(1));
623 }
624
625 None
626 }
627
628 #[cfg(test)]
629 mod tests {
630 use super::*;
631
632 #[test]
633 fn test_slice_indent_spaces() {
634 assert_eq!(slice_indent(b" hello", 4), (4, false));
635 assert_eq!(slice_indent(b"hello", 4), (0, false));
636 assert_eq!(slice_indent(b" deep", 4), (8, false));
637 }
638
639 #[test]
640 fn test_slice_indent_tabs() {
641 assert_eq!(slice_indent(b"\thello", 4), (4, false));
642 assert_eq!(slice_indent(b"\t\thello", 4), (8, false));
643 assert_eq!(slice_indent(b" \thello", 4), (4, false));
645 }
646
647 #[test]
648 fn test_slice_indent_blank() {
649 assert_eq!(slice_indent(b"", 4), (0, true));
650 assert_eq!(slice_indent(b" ", 4), (3, true));
651 assert_eq!(slice_indent(b" \r", 4), (2, true));
652 }
653
654 #[test]
655 fn test_is_line_foldable_basic() {
656 let lines: Vec<&[u8]> = vec![b"fn main() {", b" println!();", b"}"];
657 assert!(is_line_foldable_in_bytes(&lines, 4));
658 }
659
660 #[test]
661 fn test_is_line_foldable_not_foldable() {
662 let lines: Vec<&[u8]> = vec![b"line1", b"line2", b"line3"];
663 assert!(!is_line_foldable_in_bytes(&lines, 4));
664 }
665
666 #[test]
667 fn test_is_line_foldable_blank_lines_skipped() {
668 let lines: Vec<&[u8]> = vec![b"fn main() {", b"", b" println!();", b"}"];
669 assert!(is_line_foldable_in_bytes(&lines, 4));
670 }
671 }
672}