feat: add local diff review workflow
This commit is contained in:
+79
@@ -139,6 +139,8 @@ pub struct ChangedFile {
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct Comparison {
|
||||
/// Immutable comparison-context IDs; captured review text also freezes live edits.
|
||||
pub revisions: [String; 2],
|
||||
pub before: String,
|
||||
pub after: String,
|
||||
pub files: Vec<ChangedFile>,
|
||||
@@ -273,6 +275,7 @@ pub struct DiffState {
|
||||
pub loading: bool,
|
||||
pub error: Option<String>,
|
||||
pub comparison: Option<Comparison>,
|
||||
pub local_review: crate::review::ReviewState,
|
||||
pub comparison_id: Option<u64>,
|
||||
pub reviewed_files: BTreeSet<usize>,
|
||||
pub(crate) snapshot_roots: [Option<PathBuf>; 2],
|
||||
@@ -330,6 +333,7 @@ impl DiffState {
|
||||
loading: true,
|
||||
error: None,
|
||||
comparison: None,
|
||||
local_review: Default::default(),
|
||||
comparison_id: None,
|
||||
reviewed_files: BTreeSet::new(),
|
||||
files: None,
|
||||
@@ -366,6 +370,7 @@ impl DiffState {
|
||||
}
|
||||
pub(crate) fn set_resolver_paths(&mut self, root: PathBuf, paths: Vec<PathBuf>, file: usize) {
|
||||
let comparison = self.comparison.get_or_insert_with(|| Comparison {
|
||||
revisions: Default::default(),
|
||||
before: "Resolution".into(),
|
||||
after: "Other version".into(),
|
||||
files: Vec::new(),
|
||||
@@ -1710,6 +1715,40 @@ impl DiffState {
|
||||
lines
|
||||
}
|
||||
|
||||
pub fn review_anchor_current(&self, anchor: &crate::review::Anchor) -> bool {
|
||||
use crate::review::Side;
|
||||
let Some(comparison) = &self.comparison else {
|
||||
return false;
|
||||
};
|
||||
if self.local_review.document.root != comparison.root {
|
||||
return false;
|
||||
}
|
||||
comparison.files.iter().enumerate().any(|(index, file)| {
|
||||
if !file.loaded || file.notice.is_some() {
|
||||
return false;
|
||||
}
|
||||
let (path, lines, eof) = match anchor.side {
|
||||
Side::Before => (
|
||||
file.old_path.as_deref().unwrap_or(&file.path),
|
||||
&file.before,
|
||||
file.before_eof,
|
||||
),
|
||||
Side::After => (
|
||||
file.path.as_path(),
|
||||
&file.after,
|
||||
file.after_eof && !self.live_files.contains(&index),
|
||||
),
|
||||
};
|
||||
anchor.matches_lines(
|
||||
&comparison.revisions[anchor.side.index()],
|
||||
path,
|
||||
anchor.side,
|
||||
lines,
|
||||
eof,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn corresponding_row(&self, from: Focus, to: Focus, row: usize) -> Option<usize> {
|
||||
let aligned = self.rows.iter().position(|aligned| match from {
|
||||
Focus::Before => aligned.before == Some(row),
|
||||
@@ -3172,6 +3211,7 @@ mod tests {
|
||||
|
||||
fn comparison() -> Comparison {
|
||||
Comparison {
|
||||
revisions: ["abcdef123".into(), "working:snapshot".into()],
|
||||
before: "abcdef123".into(),
|
||||
after: "Working checkout".into(),
|
||||
root: PathBuf::new(),
|
||||
@@ -4159,6 +4199,7 @@ mod tests {
|
||||
assert!(editor.apply_diff_result(
|
||||
id,
|
||||
Ok(Outcome::Comparison(Comparison {
|
||||
revisions: ["working:snapshot".into(), "working:snapshot".into()],
|
||||
before: "Working".into(),
|
||||
after: "Working".into(),
|
||||
files: vec![],
|
||||
@@ -4190,6 +4231,7 @@ mod tests {
|
||||
Category::Commits,
|
||||
));
|
||||
state.comparison = Some(Comparison {
|
||||
revisions: ["base".into(), "working:snapshot".into()],
|
||||
root: PathBuf::new(),
|
||||
before: "base".into(),
|
||||
after: "Working".into(),
|
||||
@@ -4982,4 +5024,41 @@ mod tests {
|
||||
));
|
||||
assert!(editor.diff().unwrap().error.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn review_anchors_match_frozen_context_and_exact_live_eof_without_allocating() {
|
||||
use crate::review::{Anchor, Side};
|
||||
let mut state = live_state(vec!["old".into()], vec!["new".into()], true);
|
||||
let anchor = Anchor {
|
||||
revision: "working:snapshot".into(),
|
||||
path: "file".into(),
|
||||
side: Side::After,
|
||||
start_line: 1,
|
||||
end_line: 1,
|
||||
content: "new\n".into(),
|
||||
};
|
||||
assert!(state.review_anchor_current(&anchor));
|
||||
state.live_files.insert(0);
|
||||
assert!(!state.review_anchor_current(&anchor));
|
||||
state.comparison.as_mut().unwrap().files[0]
|
||||
.after
|
||||
.push(String::new());
|
||||
assert!(state.review_anchor_current(&anchor));
|
||||
state.comparison.as_mut().unwrap().files[0].after[0] = "changed".into();
|
||||
assert!(!state.review_anchor_current(&anchor));
|
||||
state.comparison.as_mut().unwrap().files[0].old_path = Some("original".into());
|
||||
let before = Anchor {
|
||||
revision: "base".into(),
|
||||
path: "original".into(),
|
||||
side: Side::Before,
|
||||
content: "old\n".into(),
|
||||
..anchor
|
||||
};
|
||||
assert!(state.review_anchor_current(&before));
|
||||
state.comparison.as_mut().unwrap().files[0].loaded = false;
|
||||
assert!(!state.review_anchor_current(&before));
|
||||
state.comparison.as_mut().unwrap().files[0].loaded = true;
|
||||
state.comparison.as_mut().unwrap().revisions[0] = "other".into();
|
||||
assert!(!state.review_anchor_current(&before));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user