1use std::collections::{HashMap, HashSet};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub struct FoldRange {
8 pub start: usize,
9 pub end: usize, }
11
12#[derive(Debug, Clone, Default)]
13pub struct FoldState {
14 pub ranges: Vec<FoldRange>,
16 pub closed: HashSet<usize>,
18 starts: HashMap<usize, FoldRange>,
22 hidden: HashSet<usize>,
25}
26
27impl FoldState {
28 pub fn new() -> Self {
29 Self::default()
30 }
31
32 pub fn clear(&mut self) {
33 self.ranges.clear();
34 self.closed.clear();
35 self.starts.clear();
36 self.hidden.clear();
37 }
38
39 fn reindex_starts(&mut self) {
41 self.starts.clear();
42 for r in &self.ranges {
43 self.starts
44 .entry(r.start)
45 .and_modify(|cur| {
46 if r.end > cur.end {
47 *cur = *r;
48 }
49 })
50 .or_insert(*r);
51 }
52 }
53
54 fn recompute_hidden(&mut self) {
57 self.hidden.clear();
58 for start in &self.closed {
59 if let Some(r) = self.starts.get(start) {
60 for row in (r.start + 1)..=r.end {
61 self.hidden.insert(row);
62 }
63 }
64 }
65 }
66
67 pub fn rebuild(&mut self, lines: &[String], tab_width: usize) {
69 let old_closed = self.closed.clone();
70 self.ranges.clear();
71 self.closed.clear();
72
73 let n = lines.len();
74 if n < 2 {
75 return;
76 }
77 let indents: Vec<usize> = lines.iter().map(|l| line_indent(l, tab_width)).collect();
78
79 for i in 0..n.saturating_sub(1) {
80 if lines[i].trim().is_empty() {
82 continue;
83 }
84 let base = indents[i];
85 let mut j = i + 1;
87 while j < n && lines[j].trim().is_empty() {
88 j += 1;
89 }
90 if j >= n || indents[j] <= base {
91 continue;
92 }
93 let mut end = j;
95 let mut k = j;
96 while k < n {
97 if lines[k].trim().is_empty() {
98 k += 1;
99 continue;
100 }
101 if indents[k] > base {
102 end = k;
103 k += 1;
104 } else {
105 break;
106 }
107 }
108 if end > i {
109 self.ranges.push(FoldRange {
110 start: i,
111 end,
112 });
113 }
114 }
115
116 for r in &self.ranges {
118 if old_closed.contains(&r.start) {
119 self.closed.insert(r.start);
120 }
121 }
122 self.reindex_starts();
123 self.recompute_hidden();
124 }
125
126 pub fn fold_at(&self, row: usize) -> Option<FoldRange> {
127 self.starts.get(&row).copied()
128 }
129
130 pub fn is_closed(&self, start: usize) -> bool {
131 self.closed.contains(&start)
132 }
133
134 pub fn is_hidden(&self, row: usize) -> bool {
136 self.hidden.contains(&row)
137 }
138
139 pub fn toggle(&mut self, row: usize) -> Option<&'static str> {
140 let start = if self.starts.contains_key(&row) {
142 row
143 } else {
144 self.ranges
145 .iter()
146 .filter(|r| row > r.start && row <= r.end)
147 .max_by_key(|r| r.start)
148 .map(|r| r.start)?
149 };
150 let msg = if self.closed.contains(&start) {
151 self.closed.remove(&start);
152 Some("opened fold")
153 } else if self.starts.contains_key(&start) {
154 self.closed.insert(start);
155 Some("closed fold")
156 } else {
157 None
158 };
159 if msg.is_some() {
160 self.recompute_hidden();
161 }
162 msg
163 }
164
165 pub fn close_at(&mut self, row: usize) -> bool {
166 if !self.starts.contains_key(&row) {
167 return false;
168 }
169 self.closed.insert(row);
170 self.recompute_hidden();
171 true
172 }
173
174 pub fn open_at(&mut self, row: usize) -> bool {
175 let removed = self.closed.remove(&row);
176 if removed {
177 self.recompute_hidden();
178 }
179 removed
180 }
181
182 pub fn close_all(&mut self) {
183 for r in &self.ranges {
184 self.closed.insert(r.start);
185 }
186 self.recompute_hidden();
187 }
188
189 pub fn open_all(&mut self) {
190 self.closed.clear();
191 self.hidden.clear();
192 }
193
194 pub fn closed_count(&self, start: usize) -> usize {
196 self.fold_at(start)
197 .filter(|_| self.is_closed(start))
198 .map(|r| r.end - r.start)
199 .unwrap_or(0)
200 }
201}
202
203fn line_indent(line: &str, tab_width: usize) -> usize {
204 let mut n = 0;
205 for c in line.chars() {
206 match c {
207 ' ' => n += 1,
208 '\t' => n += tab_width,
209 _ => break,
210 }
211 }
212 n
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218
219 #[test]
220 fn indent_fold_fn_body() {
221 let lines = vec![
222 "fn main() {".into(),
223 " let x = 1;".into(),
224 " let y = 2;".into(),
225 "}".into(),
226 ];
227 let mut f = FoldState::new();
228 f.rebuild(&lines, 4);
229 assert!(!f.ranges.is_empty());
230 let r = f.fold_at(0).expect("fold on fn line");
231 assert_eq!(r.start, 0);
232 assert!(r.end >= 2);
233 f.toggle(0);
234 assert!(f.is_hidden(1));
235 assert!(!f.is_hidden(0));
236 }
237
238 #[test]
239 fn close_all_open_all_updates_hidden_index() {
240 let lines = vec![
241 "fn a() {".into(),
242 " let x = 1;".into(),
243 "}".into(),
244 "fn b() {".into(),
245 " let y = 2;".into(),
246 "}".into(),
247 ];
248 let mut f = FoldState::new();
249 f.rebuild(&lines, 4);
250 f.close_all();
251 assert!(f.is_hidden(1));
252 assert!(f.is_hidden(4));
253 assert!(!f.is_hidden(0));
254 assert!(!f.is_hidden(3));
255 f.open_all();
256 assert!(!f.is_hidden(1));
257 assert!(!f.is_hidden(4));
258 f.close_at(0);
260 assert!(f.is_hidden(1));
261 f.open_at(0);
262 assert!(!f.is_hidden(1));
263 }
264}