use diffler_core::diffalgo::DiffAlgorithm;
use diffler_core::highlight::Highlighter;
use diffler_core::model::{BlobIds, FileDiff, HashCache, Hunk};
use diffler_core::pairing;
use super::App;
use super::diff::{FileHighlights, FileScope};
#[derive(Debug)]
pub struct EnrichJob {
pub path: String,
pub hash: String,
pub old_text: Option<String>,
pub new_text: Option<String>,
pub hunks: Vec<Hunk>,
pub semantic: bool,
pub algorithm: DiffAlgorithm,
}
#[derive(Debug)]
pub struct EnrichOutcome {
pub path: String,
pub hash: String,
pub hunks: Vec<Hunk>,
pub highlights: FileHighlights,
pub scope: FileScope,
pub algorithm: DiffAlgorithm,
}
pub(super) fn queue_if_stale(
inflight: &mut std::collections::HashSet<String>,
pending: &mut Vec<EnrichJob>,
file: &FileDiff,
semantic: bool,
algorithm: DiffAlgorithm,
ready: bool,
) {
if ready {
return;
}
let hash = file.sides_hash();
if !inflight.insert(hash.clone()) {
return;
}
pending.push(EnrichJob {
path: file.path.clone(),
hash,
old_text: file.old_text.clone(),
new_text: file.new_text.clone(),
hunks: file.hunks.clone(),
semantic,
algorithm,
});
}
pub fn run_enrich(highlighter: &Highlighter, job: EnrichJob) -> EnrichOutcome {
let mut file = FileDiff {
path: job.path,
old_path: None,
status: diffler_core::model::FileStatus::Modified,
binary: false,
old_text: job.old_text,
new_text: job.new_text,
hunks: job.hunks,
hashes: HashCache::default(),
blobs: BlobIds::default(),
};
let structural = job.algorithm == DiffAlgorithm::Structural;
if !(job.semantic && highlighter.syntactic_emphasis(&mut file, structural)) {
pairing::enrich_file(&mut file);
}
let highlight = |text: &Option<String>| {
text.as_deref()
.map(|content| highlighter.highlight(&file.path, content))
.unwrap_or_default()
};
let highlights = FileHighlights {
hash: job.hash.clone(),
old: highlight(&file.old_text),
new: highlight(&file.new_text),
};
let scope = FileScope {
hash: job.hash.clone(),
index: file
.new_text
.as_deref()
.map(|content| highlighter.scope_index(&file.path, content))
.unwrap_or_default(),
};
EnrichOutcome {
path: file.path,
hash: job.hash,
hunks: file.hunks,
highlights,
scope,
algorithm: job.algorithm,
}
}
impl App {
pub(crate) fn queue_enrich_selected(&mut self) {
let Some(diff) = self.diff.as_ref() else {
return;
};
let selected = diff.selected;
let file_count = diff
.commit_model
.as_ref()
.unwrap_or_else(|| self.review.model())
.files
.len();
let mut targets = vec![selected];
targets.extend(selected.checked_sub(1));
if selected + 1 < file_count {
targets.push(selected + 1);
}
for index in targets {
self.queue_enrich_file(index);
}
self.queue_enrich_context_files();
}
fn queue_enrich_context_files(&mut self) {
let algorithm = self.config.diff.algorithm;
let Some(diff) = self.diff.as_ref() else {
return;
};
for file in &diff.context_files {
let ready = diff
.highlights
.get(&file.path)
.is_some_and(|cached| cached.hash == file.sides_hash());
queue_if_stale(
&mut self.enrich_inflight,
&mut self.pending_enrich,
file,
false,
algorithm,
ready,
);
}
}
fn queue_enrich_file(&mut self, index: usize) {
let semantic = self.config.ui.semantic_diff;
let algorithm = self.config.diff.algorithm;
let Some(diff) = self.diff.as_ref() else {
return;
};
let model = diff
.commit_model
.as_ref()
.unwrap_or_else(|| self.review.model());
let Some(file) = model.files.get(index) else {
return;
};
if file.binary || file.hunks.is_empty() {
return;
}
let ready = diff
.highlights
.get(&file.path)
.is_some_and(|cached| cached.hash == file.sides_hash())
&& diff.is_enriched(&file.path);
queue_if_stale(
&mut self.enrich_inflight,
&mut self.pending_enrich,
file,
semantic,
algorithm,
ready,
);
}
pub(crate) fn on_enriched(&mut self, outcome: EnrichOutcome) {
self.enrich_inflight.remove(&outcome.hash);
if outcome.algorithm != self.config.diff.algorithm {
return;
}
self.install_status_enrichment(&outcome);
let algorithm = self.config.diff.algorithm;
let indent_heuristic = self.config.diff.indent_heuristic;
let Some(diff) = self.diff.as_mut() else {
return;
};
let context = diff.context.get(&outcome.path).copied();
let same = |file: &FileDiff| file.path == outcome.path && file.sides_hash() == outcome.hash;
let file = match diff.commit_model.as_mut() {
Some(model) => model.files.iter_mut().find(|f| same(f)),
None => self.review.model_mut().files.iter_mut().find(|f| same(f)),
}
.or_else(|| diff.context_files.iter_mut().find(|f| same(f)));
let Some(file) = file else {
return;
};
file.hunks = outcome.hunks;
let reshaped = context.is_some_and(|context| {
super::expand::apply_context(file, context, algorithm, indent_heuristic)
});
let hunks = file.hunks.clone();
diff.highlights
.insert(outcome.path.clone(), outcome.highlights);
diff.scopes.insert(outcome.path.clone(), outcome.scope);
diff.mark_enriched(&outcome.path);
if let Some(cached) = diff
.merged_model
.as_mut()
.and_then(|merged| merged.files.iter_mut().find(|f| same(f)))
{
cached.hunks = hunks;
}
if reshaped || diff.rows_show(&outcome.path) {
diff.mark_rows_dirty();
}
}
}
impl App {
pub fn enrich_now(&mut self) {
let jobs = std::mem::take(&mut self.pending_enrich);
for job in jobs {
let outcome = run_enrich(&self.highlighter, job);
self.on_enriched(outcome);
}
}
}