use crate::types::{self, Range, Span, Visibility, ROOT};
use std::collections::BTreeSet;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Pairing<T> {
Both { lhs: T, rhs: T },
LeftOnly { lhs: T },
RightOnly { rhs: T },
}
impl<T> Pairing<T> {
pub fn map<U>(self, mut f: impl FnMut(T) -> U) -> Pairing<U> {
match self {
Self::Both { lhs, rhs } => Pairing::Both {
lhs: f(lhs),
rhs: f(rhs),
},
Self::LeftOnly { lhs } => Pairing::LeftOnly { lhs: f(lhs) },
Self::RightOnly { rhs } => Pairing::RightOnly { rhs: f(rhs) },
}
}
pub fn lhs(&self) -> Option<&T> {
match self {
Self::Both { lhs, .. } | Self::LeftOnly { lhs } => Some(lhs),
Self::RightOnly { .. } => None,
}
}
pub fn rhs(&self) -> Option<&T> {
match self {
Self::Both { rhs, .. } | Self::RightOnly { rhs } => Some(rhs),
Self::LeftOnly { .. } => None,
}
}
pub fn sides(&self) -> Vec<&T> {
match self {
Self::Both { lhs, rhs } => vec![lhs, rhs],
Self::LeftOnly { lhs } => vec![lhs],
Self::RightOnly { rhs } => vec![rhs],
}
}
pub fn sides_mut(&mut self) -> Vec<&mut T> {
match self {
Self::Both { lhs, rhs } => vec![lhs, rhs],
Self::LeftOnly { lhs } => vec![lhs],
Self::RightOnly { rhs } => vec![rhs],
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Region {
pub id: u32,
pub fold_state_id: u32,
pub range: Range,
pub tags: Vec<String>,
pub visibility: Visibility,
pub node: Node,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Node {
Leaf {
alignment_id: u32,
changed: Vec<Span>,
},
Fold {
children: Vec<Region>,
},
}
impl Region {
pub fn alignment_id(&self) -> Option<u32> {
match self.node {
Node::Leaf { alignment_id, .. } => Some(alignment_id),
Node::Fold { .. } => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Source {
pub text: String,
pub regions: Vec<Region>,
}
impl Source {
pub fn from_record(side: &types::Source) -> anyhow::Result<Self> {
fn children(
regions: &mut std::iter::Peekable<std::slice::Iter<'_, types::Region>>,
parent: u32,
) -> Vec<Region> {
let mut out = Vec::new();
while let Some(region) = regions.next_if(|region| region.parent == parent) {
let node = match ®ion.kind {
types::Kind::Leaf(leaf) => Node::Leaf {
alignment_id: leaf.alignment_id,
changed: leaf.changed.clone(),
},
types::Kind::Fold => Node::Fold {
children: children(regions, region.id),
},
};
out.push(Region {
id: region.id,
fold_state_id: region.fold_state_id,
range: region.range,
tags: region.tags.clone(),
visibility: region.visibility.clone(),
node,
});
}
out
}
let mut regions = side.regions.iter().peekable();
let tree = children(&mut regions, ROOT);
if let Some(stray) = regions.next() {
anyhow::bail!(
"region {} names parent {}, which does not precede it",
stray.id,
stray.parent
);
}
Ok(Self {
text: side.text.clone(),
regions: tree,
})
}
pub fn to_record(&self) -> types::Source {
fn flatten(regions: &[Region], parent: u32, out: &mut Vec<types::Region>) {
for region in regions {
let kind = match ®ion.node {
Node::Leaf {
alignment_id,
changed,
} => types::Kind::Leaf(types::Leaf {
alignment_id: *alignment_id,
changed: changed.clone(),
}),
Node::Fold { .. } => types::Kind::Fold,
};
out.push(types::Region {
id: region.id,
parent,
fold_state_id: region.fold_state_id,
range: region.range,
tags: region.tags.clone(),
visibility: region.visibility.clone(),
kind,
});
if let Node::Fold { children } = ®ion.node {
flatten(children, region.id, out);
}
}
}
let mut regions = Vec::new();
flatten(&self.regions, ROOT, &mut regions);
types::Source {
text: self.text.clone(),
regions,
}
}
}
pub fn sides(sides: &types::SourceSides) -> anyhow::Result<Pairing<Source>> {
Ok(match sides {
types::SourceSides::Both((lhs, rhs)) => Pairing::Both {
lhs: Source::from_record(lhs)?,
rhs: Source::from_record(rhs)?,
},
types::SourceSides::LeftOnly(lhs) => Pairing::LeftOnly {
lhs: Source::from_record(lhs)?,
},
types::SourceSides::RightOnly(rhs) => Pairing::RightOnly {
rhs: Source::from_record(rhs)?,
},
})
}
pub fn walk(regions: &[Region], visit: &mut impl FnMut(&Region)) {
for region in regions {
visit(region);
if let Node::Fold { children } = ®ion.node {
walk(children, visit);
}
}
}
pub fn walk_mut(regions: &mut [Region], visit: &mut impl FnMut(&mut Region)) {
for region in regions {
visit(region);
if let Node::Fold { children } = &mut region.node {
walk_mut(children, visit);
}
}
}
#[derive(Default)]
pub struct OtherSide {
leaf_ids: BTreeSet<u32>,
fold_state_ids: BTreeSet<u32>,
}
impl OtherSide {
pub fn of(other: &[Region]) -> Self {
let mut side = Self::default();
walk(other, &mut |region| {
if let Some(alignment_id) = region.alignment_id() {
side.leaf_ids.insert(alignment_id);
}
side.fold_state_ids.insert(region.fold_state_id);
});
side
}
pub fn pairs(&self, region: &Region) -> bool {
match region.node {
Node::Leaf { alignment_id, .. } => self.leaf_ids.contains(&alignment_id),
Node::Fold { .. } => self.fold_state_ids.contains(®ion.fold_state_id),
}
}
}
pub fn one_sided(region: &Region, other: &OtherSide) -> bool {
let mut paired = false;
walk(std::slice::from_ref(region), &mut |inner| {
paired |= matches!(inner.node, Node::Leaf { .. }) && other.pairs(inner);
});
!paired
}
pub fn line_count(region: &Region) -> usize {
region.range.lines().len()
}
pub fn is_fold(region: &Region) -> bool {
matches!(region.node, Node::Fold { .. })
}
pub fn has_tag(region: &Region, tag: &str) -> bool {
region.tags.iter().any(|own| own == tag)
}
const MAX_SIGNATURE_LINES: usize = 12;
pub fn docstring_of(side: &Source, body: &Region, plugin: &str) -> Option<u32> {
let tag = format!("{plugin}:docstring");
let own = format!("{plugin}:");
if let Node::Fold { children } = &body.node {
if let Some(first) = children
.iter()
.find(|child| is_fold(child))
.filter(|first| has_tag(first, &tag) && first.range.start.line == body.range.start.line)
{
return Some(first.id);
}
}
let path = path_to(&side.regions, body.id).expect("the body is on this side");
let mut between = 0;
let mut level: &[Region] = &side.regions;
let mut levels = Vec::new();
for &index in &path {
levels.push((level, index));
if let Node::Fold { children } = &level[index].node {
level = children;
}
}
for (depth, (siblings, index)) in levels.iter().enumerate().rev() {
if depth + 1 < path.len() {
let parent = &siblings[*index];
if parent.tags.iter().any(|tag| tag.starts_with(&own)) {
return None;
}
}
for region in siblings[..*index].iter().rev() {
if is_fold(region) {
return has_tag(region, &tag).then_some(region.id);
}
between += line_count(region);
if between > MAX_SIGNATURE_LINES {
return None;
}
}
}
None
}
pub fn path_to(regions: &[Region], id: u32) -> Option<Vec<usize>> {
for (index, region) in regions.iter().enumerate() {
if region.id == id {
return Some(vec![index]);
}
if let Node::Fold { children } = ®ion.node {
if let Some(mut path) = path_to(children, id) {
path.insert(0, index);
return Some(path);
}
}
}
None
}
pub fn siblings_of(regions: &[Region], id: u32) -> Option<&[Region]> {
if regions.iter().any(|region| region.id == id) {
return Some(regions);
}
regions.iter().find_map(|region| match ®ion.node {
Node::Fold { children } => siblings_of(children, id),
Node::Leaf { .. } => None,
})
}
pub fn before_and_after_ids(sides: &Pairing<Source>) -> Option<(&Source, OtherSide)> {
match sides {
Pairing::Both { lhs, rhs } => Some((lhs, OtherSide::of(&rhs.regions))),
Pairing::LeftOnly { lhs } => Some((lhs, OtherSide::default())),
Pairing::RightOnly { .. } => None,
}
}
pub fn sides_with_other_ids(sides: &Pairing<Source>) -> Vec<(&Source, OtherSide)> {
match sides {
Pairing::Both { lhs, rhs } => vec![
(lhs, OtherSide::of(&rhs.regions)),
(rhs, OtherSide::of(&lhs.regions)),
],
Pairing::LeftOnly { lhs } => vec![(lhs, OtherSide::default())],
Pairing::RightOnly { rhs } => vec![(rhs, OtherSide::default())],
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::Position;
fn region(id: u32, alignment: u32, fold_state_id: u32, line: u32, fold: bool) -> Region {
let range = Range {
start: Position { line, column: 0 },
end: Position {
line: line + 1,
column: 0,
},
};
let leaf = Region {
id,
fold_state_id,
range,
tags: vec![],
visibility: Visibility::default(),
node: Node::Leaf {
alignment_id: alignment,
changed: vec![],
},
};
if !fold {
return leaf;
}
Region {
node: Node::Fold {
children: vec![Region {
id: id + 100,
fold_state_id: id + 100,
node: Node::Leaf {
alignment_id: alignment + 100,
changed: vec![],
},
..leaf.clone()
}],
},
..leaf
}
}
#[test]
fn leaves_pair_by_alignment_id_and_folds_by_fold_state_on_the_other_side() {
let lhs = vec![
region(1, 1, 1, 0, false),
region(2, 2, 2, 1, true),
region(3, 3, 3, 2, true),
region(4, 4, 3, 3, false),
];
let rhs = vec![region(5, 1, 1, 0, false), region(6, 6, 2, 1, true)];
let from_lhs = OtherSide::of(&rhs);
let from_rhs = OtherSide::of(&lhs);
assert!(from_lhs.pairs(&lhs[0]));
assert!(from_lhs.pairs(&lhs[1]), "the matched fold is paired");
assert!(
!from_lhs.pairs(&lhs[2]),
"a fold state shared only on its own side does not pair"
);
assert!(
!from_lhs.pairs(&lhs[3]),
"a leaf pairs by alignment id alone"
);
assert!(from_rhs.pairs(&rhs[0]));
assert!(from_rhs.pairs(&rhs[1]));
}
#[test]
fn newness_comes_from_the_leaves_not_the_fold_state() {
let moved = region(2, 2, 2, 1, true);
let elsewhere = region(6, 6, 2, 1, true);
let other = OtherSide::of(std::slice::from_ref(&elsewhere));
assert!(other.pairs(&moved), "the fold itself is matched");
assert!(one_sided(&moved, &other));
let kept = region(6, 2, 9, 1, true);
let other = OtherSide::of(std::slice::from_ref(&kept));
assert!(!other.pairs(&moved), "the folds are not matched");
assert!(!one_sided(&moved, &other));
}
#[test]
fn a_tree_survives_the_round_trip_through_its_record() {
let source = Source {
text: "a\nb\nc\n".to_owned(),
regions: vec![region(1, 1, 1, 0, false), region(2, 2, 2, 1, true)],
};
let record = source.to_record();
assert_eq!(
record
.regions
.iter()
.map(|region| (region.id, region.parent))
.collect::<Vec<_>>(),
[(1, ROOT), (2, ROOT), (102, 2)]
);
assert_eq!(Source::from_record(&record).unwrap(), source);
}
}