1use crate::types::{self, Range, Span, Visibility, ROOT};
4use std::collections::BTreeSet;
5
6#[derive(Debug, Clone, PartialEq, Eq)]
8pub enum Pairing<T> {
9 Both { lhs: T, rhs: T },
10 LeftOnly { lhs: T },
11 RightOnly { rhs: T },
12}
13
14impl<T> Pairing<T> {
15 pub fn map<U>(self, mut f: impl FnMut(T) -> U) -> Pairing<U> {
17 match self {
18 Self::Both { lhs, rhs } => Pairing::Both {
19 lhs: f(lhs),
20 rhs: f(rhs),
21 },
22 Self::LeftOnly { lhs } => Pairing::LeftOnly { lhs: f(lhs) },
23 Self::RightOnly { rhs } => Pairing::RightOnly { rhs: f(rhs) },
24 }
25 }
26
27 pub fn lhs(&self) -> Option<&T> {
28 match self {
29 Self::Both { lhs, .. } | Self::LeftOnly { lhs } => Some(lhs),
30 Self::RightOnly { .. } => None,
31 }
32 }
33
34 pub fn rhs(&self) -> Option<&T> {
35 match self {
36 Self::Both { rhs, .. } | Self::RightOnly { rhs } => Some(rhs),
37 Self::LeftOnly { .. } => None,
38 }
39 }
40
41 pub fn sides(&self) -> Vec<&T> {
43 match self {
44 Self::Both { lhs, rhs } => vec![lhs, rhs],
45 Self::LeftOnly { lhs } => vec![lhs],
46 Self::RightOnly { rhs } => vec![rhs],
47 }
48 }
49
50 pub fn sides_mut(&mut self) -> Vec<&mut T> {
52 match self {
53 Self::Both { lhs, rhs } => vec![lhs, rhs],
54 Self::LeftOnly { lhs } => vec![lhs],
55 Self::RightOnly { rhs } => vec![rhs],
56 }
57 }
58}
59
60#[derive(Debug, Clone, PartialEq, Eq)]
62pub struct Region {
63 pub id: u32,
64 pub fold_state_id: u32,
65 pub range: Range,
66 pub tags: Vec<String>,
67 pub visibility: Visibility,
68 pub node: Node,
69}
70
71#[derive(Debug, Clone, PartialEq, Eq)]
72pub enum Node {
73 Leaf {
74 alignment_id: u32,
75 changed: Vec<Span>,
76 },
77 Fold {
78 children: Vec<Region>,
79 },
80}
81
82impl Region {
83 pub fn alignment_id(&self) -> Option<u32> {
85 match self.node {
86 Node::Leaf { alignment_id, .. } => Some(alignment_id),
87 Node::Fold { .. } => None,
88 }
89 }
90}
91
92#[derive(Debug, Clone, PartialEq, Eq)]
94pub struct Source {
95 pub text: String,
96 pub regions: Vec<Region>,
98}
99
100impl Source {
101 pub fn from_record(side: &types::Source) -> anyhow::Result<Self> {
103 fn children(
104 regions: &mut std::iter::Peekable<std::slice::Iter<'_, types::Region>>,
105 parent: u32,
106 ) -> Vec<Region> {
107 let mut out = Vec::new();
108 while let Some(region) = regions.next_if(|region| region.parent == parent) {
109 let node = match ®ion.kind {
110 types::Kind::Leaf(leaf) => Node::Leaf {
111 alignment_id: leaf.alignment_id,
112 changed: leaf.changed.clone(),
113 },
114 types::Kind::Fold => Node::Fold {
115 children: children(regions, region.id),
116 },
117 };
118 out.push(Region {
119 id: region.id,
120 fold_state_id: region.fold_state_id,
121 range: region.range,
122 tags: region.tags.clone(),
123 visibility: region.visibility.clone(),
124 node,
125 });
126 }
127 out
128 }
129 let mut regions = side.regions.iter().peekable();
130 let tree = children(&mut regions, ROOT);
131 if let Some(stray) = regions.next() {
132 anyhow::bail!(
133 "region {} names parent {}, which does not precede it",
134 stray.id,
135 stray.parent
136 );
137 }
138 Ok(Self {
139 text: side.text.clone(),
140 regions: tree,
141 })
142 }
143
144 pub fn to_record(&self) -> types::Source {
146 fn flatten(regions: &[Region], parent: u32, out: &mut Vec<types::Region>) {
147 for region in regions {
148 let kind = match ®ion.node {
149 Node::Leaf {
150 alignment_id,
151 changed,
152 } => types::Kind::Leaf(types::Leaf {
153 alignment_id: *alignment_id,
154 changed: changed.clone(),
155 }),
156 Node::Fold { .. } => types::Kind::Fold,
157 };
158 out.push(types::Region {
159 id: region.id,
160 parent,
161 fold_state_id: region.fold_state_id,
162 range: region.range,
163 tags: region.tags.clone(),
164 visibility: region.visibility.clone(),
165 kind,
166 });
167 if let Node::Fold { children } = ®ion.node {
168 flatten(children, region.id, out);
169 }
170 }
171 }
172 let mut regions = Vec::new();
173 flatten(&self.regions, ROOT, &mut regions);
174 types::Source {
175 text: self.text.clone(),
176 regions,
177 }
178 }
179}
180
181pub fn sides(sides: &types::SourceSides) -> anyhow::Result<Pairing<Source>> {
184 Ok(match sides {
185 types::SourceSides::Both((lhs, rhs)) => Pairing::Both {
186 lhs: Source::from_record(lhs)?,
187 rhs: Source::from_record(rhs)?,
188 },
189 types::SourceSides::LeftOnly(lhs) => Pairing::LeftOnly {
190 lhs: Source::from_record(lhs)?,
191 },
192 types::SourceSides::RightOnly(rhs) => Pairing::RightOnly {
193 rhs: Source::from_record(rhs)?,
194 },
195 })
196}
197
198pub fn walk(regions: &[Region], visit: &mut impl FnMut(&Region)) {
201 for region in regions {
202 visit(region);
203 if let Node::Fold { children } = ®ion.node {
204 walk(children, visit);
205 }
206 }
207}
208
209pub fn walk_mut(regions: &mut [Region], visit: &mut impl FnMut(&mut Region)) {
210 for region in regions {
211 visit(region);
212 if let Node::Fold { children } = &mut region.node {
213 walk_mut(children, visit);
214 }
215 }
216}
217
218#[derive(Default)]
225pub struct OtherSide {
226 leaf_ids: BTreeSet<u32>,
227 fold_state_ids: BTreeSet<u32>,
228}
229
230impl OtherSide {
231 pub fn of(other: &[Region]) -> Self {
233 let mut side = Self::default();
234 walk(other, &mut |region| {
235 if let Some(alignment_id) = region.alignment_id() {
236 side.leaf_ids.insert(alignment_id);
237 }
238 side.fold_state_ids.insert(region.fold_state_id);
239 });
240 side
241 }
242
243 pub fn pairs(&self, region: &Region) -> bool {
245 match region.node {
246 Node::Leaf { alignment_id, .. } => self.leaf_ids.contains(&alignment_id),
247 Node::Fold { .. } => self.fold_state_ids.contains(®ion.fold_state_id),
248 }
249 }
250}
251
252pub fn one_sided(region: &Region, other: &OtherSide) -> bool {
264 let mut paired = false;
265 walk(std::slice::from_ref(region), &mut |inner| {
266 paired |= matches!(inner.node, Node::Leaf { .. }) && other.pairs(inner);
267 });
268 !paired
269}
270
271pub fn line_count(region: &Region) -> usize {
275 region.range.lines().len()
276}
277
278pub fn is_fold(region: &Region) -> bool {
279 matches!(region.node, Node::Fold { .. })
280}
281
282pub fn has_tag(region: &Region, tag: &str) -> bool {
283 region.tags.iter().any(|own| own == tag)
284}
285
286const MAX_SIGNATURE_LINES: usize = 12;
289
290pub fn docstring_of(side: &Source, body: &Region, plugin: &str) -> Option<u32> {
303 let tag = format!("{plugin}:docstring");
304 let own = format!("{plugin}:");
305 if let Node::Fold { children } = &body.node {
306 if let Some(first) = children
307 .iter()
308 .find(|child| is_fold(child))
309 .filter(|first| has_tag(first, &tag) && first.range.start.line == body.range.start.line)
310 {
311 return Some(first.id);
312 }
313 }
314 let path = path_to(&side.regions, body.id).expect("the body is on this side");
315 let mut between = 0;
316 let mut level: &[Region] = &side.regions;
319 let mut levels = Vec::new();
320 for &index in &path {
321 levels.push((level, index));
322 if let Node::Fold { children } = &level[index].node {
323 level = children;
324 }
325 }
326 for (depth, (siblings, index)) in levels.iter().enumerate().rev() {
327 if depth + 1 < path.len() {
328 let parent = &siblings[*index];
330 if parent.tags.iter().any(|tag| tag.starts_with(&own)) {
331 return None;
332 }
333 }
334 for region in siblings[..*index].iter().rev() {
335 if is_fold(region) {
336 return has_tag(region, &tag).then_some(region.id);
337 }
338 between += line_count(region);
339 if between > MAX_SIGNATURE_LINES {
340 return None;
341 }
342 }
343 }
344 None
345}
346
347pub fn path_to(regions: &[Region], id: u32) -> Option<Vec<usize>> {
349 for (index, region) in regions.iter().enumerate() {
350 if region.id == id {
351 return Some(vec![index]);
352 }
353 if let Node::Fold { children } = ®ion.node {
354 if let Some(mut path) = path_to(children, id) {
355 path.insert(0, index);
356 return Some(path);
357 }
358 }
359 }
360 None
361}
362
363pub fn siblings_of(regions: &[Region], id: u32) -> Option<&[Region]> {
365 if regions.iter().any(|region| region.id == id) {
366 return Some(regions);
367 }
368 regions.iter().find_map(|region| match ®ion.node {
369 Node::Fold { children } => siblings_of(children, id),
370 Node::Leaf { .. } => None,
371 })
372}
373
374pub fn before_and_after_ids(sides: &Pairing<Source>) -> Option<(&Source, OtherSide)> {
376 match sides {
377 Pairing::Both { lhs, rhs } => Some((lhs, OtherSide::of(&rhs.regions))),
378 Pairing::LeftOnly { lhs } => Some((lhs, OtherSide::default())),
379 Pairing::RightOnly { .. } => None,
380 }
381}
382
383pub fn sides_with_other_ids(sides: &Pairing<Source>) -> Vec<(&Source, OtherSide)> {
385 match sides {
386 Pairing::Both { lhs, rhs } => vec![
387 (lhs, OtherSide::of(&rhs.regions)),
388 (rhs, OtherSide::of(&lhs.regions)),
389 ],
390 Pairing::LeftOnly { lhs } => vec![(lhs, OtherSide::default())],
391 Pairing::RightOnly { rhs } => vec![(rhs, OtherSide::default())],
392 }
393}
394
395#[cfg(test)]
396mod tests {
397 use super::*;
398 use crate::types::Position;
399
400 fn region(id: u32, alignment: u32, fold_state_id: u32, line: u32, fold: bool) -> Region {
403 let range = Range {
404 start: Position { line, column: 0 },
405 end: Position {
406 line: line + 1,
407 column: 0,
408 },
409 };
410 let leaf = Region {
411 id,
412 fold_state_id,
413 range,
414 tags: vec![],
415 visibility: Visibility::default(),
416 node: Node::Leaf {
417 alignment_id: alignment,
418 changed: vec![],
419 },
420 };
421 if !fold {
422 return leaf;
423 }
424 Region {
425 node: Node::Fold {
426 children: vec![Region {
427 id: id + 100,
428 fold_state_id: id + 100,
429 node: Node::Leaf {
430 alignment_id: alignment + 100,
431 changed: vec![],
432 },
433 ..leaf.clone()
434 }],
435 },
436 ..leaf
437 }
438 }
439
440 #[test]
441 fn leaves_pair_by_alignment_id_and_folds_by_fold_state_on_the_other_side() {
442 let lhs = vec![
447 region(1, 1, 1, 0, false),
448 region(2, 2, 2, 1, true),
449 region(3, 3, 3, 2, true),
450 region(4, 4, 3, 3, false),
451 ];
452 let rhs = vec![region(5, 1, 1, 0, false), region(6, 6, 2, 1, true)];
453 let from_lhs = OtherSide::of(&rhs);
454 let from_rhs = OtherSide::of(&lhs);
455 assert!(from_lhs.pairs(&lhs[0]));
456 assert!(from_lhs.pairs(&lhs[1]), "the matched fold is paired");
457 assert!(
458 !from_lhs.pairs(&lhs[2]),
459 "a fold state shared only on its own side does not pair"
460 );
461 assert!(
462 !from_lhs.pairs(&lhs[3]),
463 "a leaf pairs by alignment id alone"
464 );
465 assert!(from_rhs.pairs(&rhs[0]));
466 assert!(from_rhs.pairs(&rhs[1]));
467 }
468
469 #[test]
470 fn newness_comes_from_the_leaves_not_the_fold_state() {
471 let moved = region(2, 2, 2, 1, true);
475 let elsewhere = region(6, 6, 2, 1, true);
476 let other = OtherSide::of(std::slice::from_ref(&elsewhere));
477 assert!(other.pairs(&moved), "the fold itself is matched");
478 assert!(one_sided(&moved, &other));
479 let kept = region(6, 2, 9, 1, true);
481 let other = OtherSide::of(std::slice::from_ref(&kept));
482 assert!(!other.pairs(&moved), "the folds are not matched");
483 assert!(!one_sided(&moved, &other));
484 }
485
486 #[test]
487 fn a_tree_survives_the_round_trip_through_its_record() {
488 let source = Source {
489 text: "a\nb\nc\n".to_owned(),
490 regions: vec![region(1, 1, 1, 0, false), region(2, 2, 2, 1, true)],
491 };
492 let record = source.to_record();
493 assert_eq!(
494 record
495 .regions
496 .iter()
497 .map(|region| (region.id, region.parent))
498 .collect::<Vec<_>>(),
499 [(1, ROOT), (2, ROOT), (102, 2)]
500 );
501 assert_eq!(Source::from_record(&record).unwrap(), source);
502 }
503}