use crate::apply::{find, trees_ref, Applier};
use crate::tree::{siblings_of, walk, Node, Pairing, Region, Source};
use crate::types::{Cut, Move, Visibility};
use anyhow::{anyhow, ensure};
pub struct Draft {
sides: Pairing<Source>,
file: Visibility,
applier: Applier,
moves: Vec<Move>,
}
impl Draft {
pub fn new(sides: &Pairing<Source>) -> Self {
let sides = sides.clone().map(|source| Source {
text: String::new(),
regions: source.regions,
});
Self {
applier: Applier::new(&sides),
sides,
file: Visibility::default(),
moves: Vec::new(),
}
}
pub fn push(&mut self, next: Move) -> anyhow::Result<()> {
self.applier
.apply(next.clone(), &mut self.sides, &mut self.file)?;
self.moves.push(next);
Ok(())
}
pub fn into_moves(self) -> Vec<Move> {
self.moves
}
fn region(&self, id: u32) -> anyhow::Result<&Region> {
trees_ref(&self.sides)
.into_iter()
.find_map(|tree| find(tree, id))
.ok_or_else(|| anyhow!("no region {id}"))
}
fn regions(&self, visit: &mut impl FnMut(&Region)) {
for tree in trees_ref(&self.sides) {
walk(tree, visit);
}
}
pub fn paired_leaf(&self, id: u32) -> anyhow::Result<Option<u32>> {
let alignment = self.region(id)?.alignment_id();
let mut paired = None;
self.regions(&mut |region| {
if region.id != id && alignment.is_some() && region.alignment_id() == alignment {
paired = Some(region.id);
}
});
Ok(paired)
}
pub fn collapse(&mut self, id: u32, label: String) -> anyhow::Result<()> {
let target = self.region(id)?;
let (state, alignment) = (target.fold_state_id, target.alignment_id());
let (mut named, mut cleared) = (vec![id], Vec::new());
self.regions(&mut |region| {
if region.fold_state_id != state || region.id == id {
return;
}
if alignment.is_some() && region.alignment_id() == alignment {
named.push(region.id);
} else if !region.visibility.collapsed && !region.visibility.label.is_empty() {
cleared.push(region.id);
}
});
self.push(Move::SetCollapsed((id, true)))?;
for region in named {
self.push(Move::SetLabel((region, Some(label.clone()))))?;
}
for region in cleared {
self.push(Move::SetLabel((region, None)))?;
}
Ok(())
}
pub fn link(&mut self, ids: &[u32]) -> anyhow::Result<()> {
let states = ids
.iter()
.map(|id| Ok(self.region(*id)?.fold_state_id))
.collect::<anyhow::Result<Vec<u32>>>()?;
let first_collapsed = self.region(ids[0])?.visibility.collapsed;
let (mut collapsed, mut cleared) = (false, Vec::new());
self.regions(&mut |region| {
if !states.contains(®ion.fold_state_id) {
return;
}
collapsed |= region.visibility.collapsed;
if !region.visibility.collapsed && !region.visibility.label.is_empty() {
cleared.push(region.id);
}
});
self.push(Move::LinkFoldState(ids.to_vec()))?;
if !collapsed {
return Ok(());
}
if !first_collapsed {
self.push(Move::SetCollapsed((ids[0], true)))?;
}
for region in cleared {
self.push(Move::SetLabel((region, None)))?;
}
Ok(())
}
pub fn cut_lines(&mut self, id: u32, start: u32, end: u32) -> anyhow::Result<u32> {
let leaf = self.region(id)?;
ensure!(
leaf.alignment_id().is_some(),
"region {id} is a fold; only a leaf can be cut to lines"
);
let len = leaf.range.lines().len() as u32;
ensure!(
start < end && end <= len,
"lines {start}..{end} are outside region {id}, which has {len} lines"
);
let mut piece = id;
if start > 0 {
self.push(Move::Cut(Cut {
region: id,
at: start,
}))?;
piece = self.next_sibling(id)?;
}
if end < len {
self.push(Move::Cut(Cut {
region: piece,
at: end - start,
}))?;
}
Ok(piece)
}
fn next_sibling(&self, id: u32) -> anyhow::Result<u32> {
trees_ref(&self.sides)
.into_iter()
.find_map(|tree| {
let siblings = siblings_of(tree, id)?;
let index = siblings.iter().position(|region| region.id == id)?;
siblings.get(index + 1).map(|region| region.id)
})
.ok_or_else(|| anyhow!("region {id} has no next sibling"))
}
pub fn group(&mut self, ids: Vec<u32>, label: String) -> anyhow::Result<()> {
self.push(Move::JoinFolds(ids.clone()))?;
let mut folds = Vec::new();
for tree in trees_ref(&self.sides) {
let mut parent = None;
walk(tree, &mut |region| {
if let Node::Fold { children } = ®ion.node {
if children.iter().any(|child| ids.contains(&child.id)) {
parent = Some(region.id);
}
}
});
folds.extend(parent);
}
let first = *folds.first().expect("a join adds a fold on some side");
self.push(Move::SetCollapsed((first, true)))?;
for region in folds {
self.push(Move::SetLabel((region, Some(label.clone()))))?;
}
Ok(())
}
}