use indicatrix::optics::materials::GemMaterial;
use indicatrix_cut_core::Design;
use indicatrix_editor::raw_text::{ApplyPlan, DiffKind, TextDiff, TextProblem, plan_apply};
const DIFF_CONTEXT: usize = 3;
pub(super) const KIND_NEUTRAL: i32 = 0;
pub(super) const KIND_READY: i32 = 1;
pub(super) const KIND_PROBLEM: i32 = 2;
#[derive(Debug, Clone, PartialEq)]
pub(super) enum Outcome {
Unchanged,
Problem(TextProblem),
Plan(Box<ApplyPlan>),
}
pub(super) fn same_lines(a: &str, b: &str) -> bool {
a.lines().eq(b.lines())
}
pub(super) fn evaluate(
design: &Design,
base: &str,
edited: &str,
custom: &[GemMaterial],
) -> Outcome {
if same_lines(base, edited) {
return Outcome::Unchanged;
}
match plan_apply(design, base, edited, custom) {
Ok(plan) => Outcome::Plan(Box::new(plan)),
Err(problem) => Outcome::Problem(problem),
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) struct PanelView {
pub(super) status: String,
pub(super) kind: i32,
pub(super) changed: Vec<String>,
pub(super) lost: Vec<String>,
pub(super) can_apply: bool,
}
impl PanelView {
fn sentence(status: impl Into<String>, kind: i32) -> Self {
Self {
status: status.into(),
kind,
changed: Vec::new(),
lost: Vec::new(),
can_apply: false,
}
}
pub(super) fn empty() -> Self {
Self::sentence("", KIND_NEUTRAL)
}
pub(super) fn problem(message: impl Into<String>) -> Self {
Self::sentence(message, KIND_PROBLEM)
}
}
pub(super) const UNCHANGED_SENTENCE: &str = "No changes yet. Edit the text, then choose Apply.";
const NOTHING_SENTENCE: &str =
"These edits change nothing in the design (spacing and blank lines are not kept).";
const READY_SENTENCE: &str =
"The text is valid. Apply changes the design in one step that Undo takes back.";
pub(super) fn panel_view(outcome: &Outcome) -> PanelView {
match outcome {
Outcome::Unchanged => PanelView::sentence(UNCHANGED_SENTENCE, KIND_NEUTRAL),
Outcome::Problem(problem) => PanelView::sentence(problem.to_string(), KIND_PROBLEM),
Outcome::Plan(plan) if plan.is_noop() => PanelView {
lost: plan.report.lost.clone(),
..PanelView::sentence(NOTHING_SENTENCE, KIND_NEUTRAL)
},
Outcome::Plan(plan) => PanelView {
changed: plan.report.changed.clone(),
lost: plan.report.lost.clone(),
can_apply: true,
..PanelView::sentence(READY_SENTENCE, KIND_READY)
},
}
}
pub(super) fn design_moved_problem() -> TextProblem {
TextProblem {
line: None,
message: "The design changed while this window was open. Use Revert to write the text \
again."
.to_owned(),
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) struct RowView {
pub(super) kind: i32,
pub(super) old_line: String,
pub(super) new_line: String,
pub(super) text: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) struct CompareView {
pub(super) title: String,
pub(super) summary: String,
pub(super) rows: Vec<RowView>,
}
const fn kind_code(kind: DiffKind) -> i32 {
match kind {
DiffKind::Same => 0,
DiffKind::Added => 1,
DiffKind::Removed => 2,
DiffKind::Skipped => 3,
}
}
fn number(line: Option<usize>) -> String {
line.map_or_else(String::new, |line| line.to_string())
}
pub(super) fn compare_title(label: &str) -> String {
format!("Comparing {label} with the text in the box")
}
pub(super) fn compare_view(label: &str, diff: &TextDiff) -> CompareView {
let rows = if diff.is_identical() {
diff.rows.clone()
} else {
diff.collapsed(DIFF_CONTEXT)
};
let summary = if diff.is_identical() {
diff.summary()
} else {
format!(
"{} Added lines are only in the text box, removed lines only in {label}.",
diff.summary()
)
};
CompareView {
title: compare_title(label),
summary,
rows: rows
.into_iter()
.map(|row| RowView {
kind: kind_code(row.kind),
old_line: number(row.old_line),
new_line: number(row.new_line),
text: row.text,
})
.collect(),
}
}
pub(super) fn compare_failure_view(label: &str, reason: &str) -> CompareView {
CompareView {
title: format!("Cannot compare with {label}"),
summary: reason.to_owned(),
rows: Vec::new(),
}
}