use std::collections::BTreeSet;
use super::{Op, Part, Plan};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Step {
indices: Vec<usize>,
marked: bool,
}
impl Step {
#[must_use]
pub fn indices(&self) -> &[usize] {
&self.indices
}
#[must_use]
pub const fn is_marked(&self) -> bool {
self.marked
}
#[must_use]
pub fn len(&self) -> usize {
self.indices.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.indices.is_empty()
}
#[must_use]
pub fn reason(&self, plan: &Plan) -> StepReason {
if self.marked {
return StepReason::MarkedFold;
}
if self.indices.len() > 1 {
return StepReason::Folded;
}
let Some(op) = self
.indices
.first()
.and_then(|index| plan.steps().get(*index))
else {
return StepReason::Alone;
};
if op.effects().creates.is_some() {
StepReason::CreatesId
} else if op.effects().reads_output {
StepReason::ReadsOutput
} else {
StepReason::Alone
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum StepReason {
MarkedFold,
Folded,
CreatesId,
ReadsOutput,
Alone,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
#[non_exhaustive]
pub enum Planner {
#[default]
Sequential,
Folding,
Marked,
}
impl Planner {
#[must_use]
pub fn steps(&self, plan: &Plan) -> Vec<Step> {
self.steps_bounded(plan, &BTreeSet::new())
}
#[must_use]
pub fn steps_bounded(&self, plan: &Plan, boundaries: &BTreeSet<usize>) -> Vec<Step> {
let grouped = match self {
Self::Sequential => (0..plan.len())
.map(|index| Step {
indices: vec![index],
marked: false,
})
.collect(),
Self::Folding => fold(plan.steps(), false),
Self::Marked => fold(plan.steps(), true),
};
grouped
.into_iter()
.flat_map(|step| split_at_boundaries(step, boundaries))
.collect()
}
#[must_use]
pub fn explain(&self, plan: &Plan) -> Vec<(Step, StepReason)> {
self.steps(plan)
.into_iter()
.map(|step| {
let reason = step.reason(plan);
(step, reason)
})
.collect()
}
}
fn fold(ops: &[Op], marked: bool) -> Vec<Step> {
let mut steps = Vec::new();
let mut index = 0;
while index < ops.len() {
if marked {
let decorates = marked_decorates(ops, index);
if !decorates.is_empty() {
let end = decorates[decorates.len() - 1];
steps.push(Step {
indices: (index..=end).collect(),
marked: true,
});
index = end + 1;
continue;
}
}
if ops[index].is_chainable() {
let mut cursor = index;
while cursor < ops.len() && ops[cursor].is_chainable() {
cursor += 1;
}
steps.push(Step {
indices: (index..cursor).collect(),
marked: false,
});
index = cursor;
} else {
steps.push(Step {
indices: vec![index],
marked: false,
});
index += 1;
}
}
steps
}
fn marked_decorates(ops: &[Op], index: usize) -> Vec<usize> {
let Some(creation) = ops.get(index) else {
return Vec::new();
};
let Some(focused) = creation.focused_pane() else {
return Vec::new();
};
let mut decorates = Vec::new();
for (offset, op) in ops.iter().enumerate().skip(index + 1) {
if !op.is_chainable() {
break;
}
let named: Vec<(usize, Part)> = op.slots().iter().flatten().copied().collect();
if named.is_empty()
|| !named
.iter()
.all(|(slot, part)| *slot == index && *part == focused)
{
break;
}
decorates.push(offset);
}
decorates
}
fn split_at_boundaries(step: Step, boundaries: &BTreeSet<usize>) -> Vec<Step> {
if step.indices.len() < 2 {
return vec![step];
}
let cuts: Vec<usize> = (0..step.indices.len() - 1)
.filter(|position| boundaries.contains(&step.indices[*position]))
.map(|position| position + 1)
.collect();
if cuts.is_empty() {
return vec![step];
}
let mut runs = Vec::new();
let mut start = 0;
for cut in cuts.into_iter().chain(std::iter::once(step.indices.len())) {
runs.push(Step {
indices: step.indices[start..cut].to_vec(),
marked: step.marked && start == 0 && cut - start > 1,
});
start = cut;
}
runs
}
impl Step {
pub(crate) fn single(index: usize) -> Self {
Self {
indices: vec![index],
marked: false,
}
}
}