From 6426aea4b09c8e3dff166220896143e99277c944 Mon Sep 17 00:00:00 2001 From: Sidharth Menon Date: Sat, 19 Sep 2026 14:48:27 -0700 Subject: [PATCH 1/3] Extend the plugin contract with shared sources and search highlights Add Same source inputs, unchanged file status, and leaf search spans. File references retain their separate three-way pairing. External component plugins must rebuild for the WIT change. AI assistance: reorganized with Codex. --- crates/diffr-plugin-sdk/src/lib.rs | 5 +-- crates/diffr-plugin-sdk/src/tree.rs | 47 ++++++++++++++++++++++++++++- src/protocol/mod.rs | 4 +++ wit/plugin.wit | 6 +++- 4 files changed, 58 insertions(+), 4 deletions(-) diff --git a/crates/diffr-plugin-sdk/src/lib.rs b/crates/diffr-plugin-sdk/src/lib.rs index 66fab82f7..d7d68cfd5 100644 --- a/crates/diffr-plugin-sdk/src/lib.rs +++ b/crates/diffr-plugin-sdk/src/lib.rs @@ -38,8 +38,9 @@ pub use anyhow; pub use draft::Draft; use serde::de::DeserializeOwned; pub use tree::{ - before_and_after_ids, docstring_of, has_tag, is_fold, line_count, one_sided, path_to, - siblings_of, sides_with_other_ids, walk, walk_mut, Node, OtherSide, Pairing, Region, Source, + before_and_after_ids, docstring_of, has_search_highlights, has_tag, highlights_in_states, + is_fold, line_count, one_sided, path_to, siblings_of, sides_with_other_ids, walk, walk_mut, + Node, OtherSide, Pairing, Region, Source, }; pub use types::{ FileEntry, FileRef, FileSides, FileStatus, Move, Position, QuerySource, Range, Side, Span, diff --git a/crates/diffr-plugin-sdk/src/tree.rs b/crates/diffr-plugin-sdk/src/tree.rs index 9cdabed7e..b22a46150 100644 --- a/crates/diffr-plugin-sdk/src/tree.rs +++ b/crates/diffr-plugin-sdk/src/tree.rs @@ -6,6 +6,8 @@ use std::collections::BTreeSet; /// Which sides a thing exists on. #[derive(Debug, Clone, PartialEq, Eq)] pub enum Pairing { + /// One unchanged source, with highlights on its regions. Never an addition or deletion. + Same { source: T }, Both { lhs: T, rhs: T }, LeftOnly { lhs: T }, RightOnly { rhs: T }, @@ -19,6 +21,7 @@ impl Pairing { lhs: f(lhs), rhs: f(rhs), }, + Self::Same { source } => Pairing::Same { source: f(source) }, Self::LeftOnly { lhs } => Pairing::LeftOnly { lhs: f(lhs) }, Self::RightOnly { rhs } => Pairing::RightOnly { rhs: f(rhs) }, } @@ -26,6 +29,7 @@ impl Pairing { pub fn lhs(&self) -> Option<&T> { match self { + Self::Same { source } => Some(source), Self::Both { lhs, .. } | Self::LeftOnly { lhs } => Some(lhs), Self::RightOnly { .. } => None, } @@ -33,6 +37,7 @@ impl Pairing { pub fn rhs(&self) -> Option<&T> { match self { + Self::Same { source } => Some(source), Self::Both { rhs, .. } | Self::RightOnly { rhs } => Some(rhs), Self::LeftOnly { .. } => None, } @@ -41,6 +46,7 @@ impl Pairing { /// Every side that exists, lhs first. pub fn sides(&self) -> Vec<&T> { match self { + Self::Same { source } => vec![source], Self::Both { lhs, rhs } => vec![lhs, rhs], Self::LeftOnly { lhs } => vec![lhs], Self::RightOnly { rhs } => vec![rhs], @@ -50,6 +56,7 @@ impl Pairing { /// Every side that exists, lhs first. pub fn sides_mut(&mut self) -> Vec<&mut T> { match self { + Self::Same { source } => vec![source], Self::Both { lhs, rhs } => vec![lhs, rhs], Self::LeftOnly { lhs } => vec![lhs], Self::RightOnly { rhs } => vec![rhs], @@ -73,6 +80,7 @@ pub enum Node { Leaf { alignment_id: u32, changed: Vec, + search_highlights: Vec, }, Fold { children: Vec, @@ -89,6 +97,36 @@ impl Region { } } +/// Whether collapsing this region would conceal a search match. +pub fn has_search_highlights(region: &Region) -> bool { + match ®ion.node { + Node::Leaf { + search_highlights, .. + } => !search_highlights.is_empty(), + Node::Fold { children } => children.iter().any(has_search_highlights), + } +} + +/// Whether any of these regions' linked collapse states contains a match. +/// Include a body's docstring when deciding whether it can be summarized. +pub fn highlights_in_states(sides: &Pairing, ids: &[u32]) -> bool { + let mut states = BTreeSet::new(); + for source in sides.sides() { + walk(&source.regions, &mut |region| { + if ids.contains(®ion.id) { + states.insert(region.fold_state_id); + } + }); + } + sides.sides().iter().any(|source| { + let mut found = false; + walk(&source.regions, &mut |region| { + found |= states.contains(®ion.fold_state_id) && has_search_highlights(region); + }); + found + }) +} + /// One side of the diffed file as a tree. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Source { @@ -110,6 +148,7 @@ impl Source { types::Kind::Leaf(leaf) => Node::Leaf { alignment_id: leaf.alignment_id, changed: leaf.changed.clone(), + search_highlights: leaf.search_highlights.clone(), }, types::Kind::Fold => Node::Fold { children: children(regions, region.id), @@ -149,9 +188,11 @@ impl Source { Node::Leaf { alignment_id, changed, + search_highlights, } => types::Kind::Leaf(types::Leaf { alignment_id: *alignment_id, changed: changed.clone(), + search_highlights: search_highlights.clone(), }), Node::Fold { .. } => types::Kind::Fold, }; @@ -182,6 +223,7 @@ impl Source { /// on the way in, so a plugin is handed the pairing rather than the record. pub fn sides(sides: &types::SourceSides) -> anyhow::Result> { Ok(match sides { + types::SourceSides::Same(source) => Pairing::Same { source: Source::from_record(source)? }, types::SourceSides::Both((lhs, rhs)) => Pairing::Both { lhs: Source::from_record(lhs)?, rhs: Source::from_record(rhs)?, @@ -376,7 +418,7 @@ pub fn before_and_after_ids(sides: &Pairing) -> Option<(&Source, OtherSi match sides { Pairing::Both { lhs, rhs } => Some((lhs, OtherSide::of(&rhs.regions))), Pairing::LeftOnly { lhs } => Some((lhs, OtherSide::default())), - Pairing::RightOnly { .. } => None, + Pairing::Same { .. } | Pairing::RightOnly { .. } => None, } } @@ -387,6 +429,7 @@ pub fn sides_with_other_ids(sides: &Pairing) -> Vec<(&Source, OtherSide) (lhs, OtherSide::of(&rhs.regions)), (rhs, OtherSide::of(&lhs.regions)), ], + Pairing::Same { source } => vec![(source, OtherSide::of(&source.regions))], Pairing::LeftOnly { lhs } => vec![(lhs, OtherSide::default())], Pairing::RightOnly { rhs } => vec![(rhs, OtherSide::default())], } @@ -415,6 +458,7 @@ mod tests { visibility: Visibility::default(), node: Node::Leaf { alignment_id: alignment, + search_highlights: Vec::new(), changed: vec![], }, }; @@ -428,6 +472,7 @@ mod tests { fold_state_id: id + 100, node: Node::Leaf { alignment_id: alignment + 100, + search_highlights: Vec::new(), changed: vec![], }, ..leaf.clone() diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index b3ff1bc04..5ddcc9d71 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -118,6 +118,7 @@ pub enum FileStatus { Renamed, Copied, TypeChanged, + Unchanged, } /// One side of a git delta. @@ -236,6 +237,8 @@ pub enum Node { alignment_id: u32, #[serde(default, skip_serializing_if = "Vec::is_empty")] changed: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + search_highlights: Vec, }, /// A foldable region. Its range is the hull of its children. Fold { children: Vec }, @@ -322,6 +325,7 @@ mod tests { visibility: Visibility::default(), node: Node::Leaf { alignment_id: index, + search_highlights: Vec::new(), changed, }, } diff --git a/wit/plugin.wit b/wit/plugin.wit index afc494227..5ca75a338 100644 --- a/wit/plugin.wit +++ b/wit/plugin.wit @@ -41,6 +41,7 @@ interface types { renamed, copied, type-changed, + unchanged, } /// One side of a git delta, as the manifest names it. @@ -104,6 +105,7 @@ interface types { /// lines up with this one. alignment-id: u32, changed: list, + search-highlights: list, } /// A leaf tiles the file; a fold's range is the hull of its children. @@ -136,8 +138,10 @@ interface types { regions: list, } - /// The sides a diffed file has, the same sides as its entry's `file`. + /// Original comparison, independent of display-side selection. `same` + /// represents identical sources once; its file entry still names both refs. variant source-sides { + same(source), both(tuple), left-only(source), right-only(source), From ed6d8062845d8b71da1d63d31e22f966c55dbfc5 Mon Sep 17 00:00:00 2001 From: Sidharth Menon Date: Sat, 19 Sep 2026 14:48:27 -0700 Subject: [PATCH 2/3] Preserve comparison identity and highlights through plugin mutations Carry spans through host, SDK, record and WASM conversions and partition them when splitting leaves. Process shared trees once; ordinary diffs retain their existing source representation. Batch the mechanical conversion and constructor updates together. AI assistance: reorganized with Codex. --- crates/diffr-plugin-sdk/src/apply.rs | 111 ++++++++++++++++++++-- examples/plugins/fixtures/src/lib.rs | 1 + src/plugin/mod.rs | 134 +++++++++++++++++++++------ src/plugin/tests/mod.rs | 10 +- src/plugin/wasm.rs | 11 +++ src/protocol/project.rs | 1 + src/protocol/stream.rs | 6 +- 7 files changed, 236 insertions(+), 38 deletions(-) diff --git a/crates/diffr-plugin-sdk/src/apply.rs b/crates/diffr-plugin-sdk/src/apply.rs index bfbd0f1df..81a963257 100644 --- a/crates/diffr-plugin-sdk/src/apply.rs +++ b/crates/diffr-plugin-sdk/src/apply.rs @@ -12,7 +12,7 @@ //! side keeps its leaf's ids. The second piece takes a fresh `id` on each //! side and a fresh `alignment_id` shared by the two, and its //! `fold_state_id` is the `id` of the lhs piece, or of its only piece. -//! Pieces keep the leaf's tags and visibility and the `changed` spans on +//! Pieces keep the leaf's tags and visibility and both change/search spans on //! their lines. A fold, or the file, cannot be cut. //! - `JoinFolds { regions }` needs two or more region ids. Each side wraps the //! ones it holds, which must be two or more consecutive siblings under one @@ -24,10 +24,12 @@ //! so is an id no side holds. //! - `LinkFoldState { regions }` needs two or more region ids. Every region //! in any of their fold states, on either side, takes the first region's -//! `fold_state_id` and whether it starts collapsed. +//! `fold_state_id` and whether it starts collapsed. A linked state containing +//! search highlights stays open. //! - `SetCollapsed { region, collapsed }` sets whether every region sharing //! the region's `fold_state_id` starts collapsed, on both sides: they open //! and close together. On [`ROOT`] it sets whether the file starts hidden. +//! Collapse requests that would conceal search highlights leave it open. //! - `SetLabel { region, label }` sets the label of that region alone, or of //! the file on [`ROOT`]; `None` clears it. //! - `SetTags { region, tags }` replaces that region's tags. The file's tags @@ -38,7 +40,9 @@ //! [`ROOT`]) when a plugin's moves begin, and fresh `alignment_id`s above the //! largest leaf `alignment_id`; each is handed out in the order the moves //! need them, lhs before rhs. -use crate::tree::{walk, walk_mut, Node, Pairing, Region, Source}; +use crate::tree::{ + has_search_highlights, highlights_in_states, walk, walk_mut, Node, Pairing, Region, Source, +}; use crate::types::{Cut, Move, Position, Range, Visibility, ROOT}; use anyhow::{bail, ensure}; use std::collections::BTreeSet; @@ -68,11 +72,16 @@ impl Applier { Move::JoinFolds(regions) => join(sides, ®ions, &mut self.fresh), Move::LinkFoldState(regions) => link(sides, ®ions), Move::SetCollapsed((ROOT, collapsed)) => { - file.collapsed = collapsed; + file.collapsed = collapsed + && !sides + .sides() + .iter() + .any(|source| source.regions.iter().any(has_search_highlights)); Ok(()) } Move::SetCollapsed((region, collapsed)) => { let state = region_of(sides, region)?.fold_state_id; + let collapsed = collapsed && !highlights_in_states(sides, &[region]); for tree in trees(sides) { walk_mut(tree, &mut |region| { if region.fold_state_id == state { @@ -116,6 +125,7 @@ pub fn apply( fn trees(sides: &mut Pairing) -> Vec<&mut Vec> { match sides { + Pairing::Same { source } => vec![&mut source.regions], Pairing::Both { lhs, rhs } => vec![&mut lhs.regions, &mut rhs.regions], Pairing::LeftOnly { lhs } => vec![&mut lhs.regions], Pairing::RightOnly { rhs } => vec![&mut rhs.regions], @@ -124,6 +134,7 @@ fn trees(sides: &mut Pairing) -> Vec<&mut Vec> { pub fn trees_ref(sides: &Pairing) -> Vec<&[Region]> { match sides { + Pairing::Same { source } => vec![&source.regions], Pairing::Both { lhs, rhs } => vec![&lhs.regions, &rhs.regions], Pairing::LeftOnly { lhs } => vec![&lhs.regions], Pairing::RightOnly { rhs } => vec![&rhs.regions], @@ -306,7 +317,12 @@ fn cut(sides: &mut Pairing, id: u32, offset: u32, fresh: &mut Fresh) -> /// A leaf split at relative line `offset`. The second piece takes `id`, /// `alignment_id` and `fold_state_id`. fn split(leaf: Region, offset: u32, id: u32, alignment_id: u32, fold_state_id: u32) -> [Region; 2] { - let Node::Leaf { changed, .. } = &leaf.node else { + let Node::Leaf { + changed, + search_highlights, + .. + } = &leaf.node + else { unreachable!("only leaves are cut"); }; let boundary = Position { @@ -328,6 +344,11 @@ fn split(leaf: Region, offset: u32, id: u32, alignment_id: u32, fold_state_id: u .copied() .filter(|span| lines.contains(&span.line)) .collect(), + search_highlights: search_highlights + .iter() + .copied() + .filter(|span| lines.contains(&span.line)) + .collect(), }, } }; @@ -366,7 +387,10 @@ fn check_regions(ids: &[u32], what: &str) -> anyhow::Result<()> { fn link(sides: &mut Pairing, ids: &[u32]) -> anyhow::Result<()> { check_regions(ids, "a link")?; let first = region_of(sides, ids[0])?; - let (state, collapsed) = (first.fold_state_id, first.visibility.collapsed); + let (state, collapsed) = ( + first.fold_state_id, + first.visibility.collapsed && !highlights_in_states(sides, ids), + ); let states = ids .iter() .map(|id| Ok(region_of(sides, *id)?.fold_state_id)) @@ -462,6 +486,7 @@ mod tests { visibility: Visibility::default(), node: Node::Leaf { alignment_id: alignment, + search_highlights: Vec::new(), changed: changed .iter() .map(|&line| Span { @@ -807,4 +832,78 @@ mod tests { assert_eq!(lhs.regions[0].visibility, Visibility::default()); assert_eq!(rhs.regions[0].tags, ["mine:tag"]); } + #[test] + fn cuts_preserve_each_sides_search_spans_through_record_roundtrips() { + let mut left = leaf(1, 7, 10, 16, &[11]); + let right = in_state(leaf(2, 7, 20, 26, &[]), 1); + if let Node::Leaf { + search_highlights, .. + } = &mut left.node + { + *search_highlights = vec![ + Span { + line: 11, + start_column: 2, + end_column: 7, + }, + Span { + line: 13, + start_column: 0, + end_column: 5, + }, + ]; + } + let mut sides = both(vec![left], vec![right]); + run(vec![Move::Cut(Cut { region: 1, at: 3 })], &mut sides).unwrap(); + let (lhs, rhs) = sides_of(&sides); + let spans = |region: &Region| match ®ion.node { + Node::Leaf { + search_highlights, .. + } => search_highlights.clone(), + _ => panic!("expected leaf"), + }; + assert_eq!(spans(&lhs.regions[0])[0].line, 11); + assert_eq!(spans(&lhs.regions[1])[0].line, 13); + assert!(rhs.regions.iter().all(|r| spans(r).is_empty())); + assert_eq!(Source::from_record(&lhs.to_record()).unwrap(), *lhs); + assert_eq!(Source::from_record(&rhs.to_record()).unwrap(), *rhs); + } + + #[test] + fn later_moves_cannot_hide_highlights_through_files_links_or_ancestors() { + let mut matched = leaf(1, 0, 0, 2, &[]); + if let Node::Leaf { + search_highlights, .. + } = &mut matched.node + { + search_highlights.push(Span { + line: 1, + start_column: 0, + end_column: 3, + }); + } + let mut sides = both( + vec![fold(3, false, vec![matched]), leaf(4, 2, 2, 4, &[2])], + vec![], + ); + let visibility = run( + vec![ + Move::SetCollapsed((4, true)), + Move::LinkFoldState(vec![4, 3]), + Move::SetCollapsed((4, true)), + Move::SetCollapsed((ROOT, true)), + Move::JoinFolds(vec![3, 4]), + Move::SetCollapsed((5, true)), + Move::SetLabel((5, Some("summary".into()))), + Move::SetTags((5, vec!["test:tag".into()])), + ], + &mut sides, + ) + .unwrap(); + assert!(!visibility.collapsed); + walk(&sides.lhs().unwrap().regions, &mut |region| { + assert!(!region.visibility.collapsed) + }); + assert!(has_search_highlights(&sides.lhs().unwrap().regions[0])); + } } diff --git a/examples/plugins/fixtures/src/lib.rs b/examples/plugins/fixtures/src/lib.rs index 1455ca79f..a25efd77e 100644 --- a/examples/plugins/fixtures/src/lib.rs +++ b/examples/plugins/fixtures/src/lib.rs @@ -88,6 +88,7 @@ impl Plugin for Fixtures { // piece on this leaf's side takes the first fresh id either way. let side = match &sides { Pairing::Both { lhs, .. } | Pairing::LeftOnly { lhs } => lhs, + Pairing::Same { source } => source, Pairing::RightOnly { rhs } => rhs, }; let leaf = side diff --git a/src/plugin/mod.rs b/src/plugin/mod.rs index 93e02ed5d..7f5b399e4 100644 --- a/src/plugin/mod.rs +++ b/src/plugin/mod.rs @@ -201,15 +201,13 @@ impl Pipeline { Ok(entry.tags) } - /// Run every plugin on one file's sides, returning the file's own - /// visibility. - pub(crate) fn run( + /// Ordinary diff protocol retains its three-way source representation. + pub(crate) fn run_diff( &self, file: &FileChange, sides: &mut Pairing, ) -> anyhow::Result { - let entry = file_entry(file); - let mut trees = match &*sides { + let trees = match &*sides { Pairing::Both { lhs, rhs } => tree::Pairing::Both { lhs: to_tree(lhs), rhs: to_tree(rhs), @@ -217,23 +215,7 @@ impl Pipeline { Pairing::LeftOnly { lhs } => tree::Pairing::LeftOnly { lhs: to_tree(lhs) }, Pairing::RightOnly { rhs } => tree::Pairing::RightOnly { rhs: to_tree(rhs) }, }; - let mut visibility = types::Visibility::default(); - for plugin in &self.plugins { - let started = Instant::now(); - let records = source_sides(&trees); - let moves = plugin - .runner - .mutate(self.host(&plugin.name), &entry, &records) - .with_context(|| MutationFailed(plugin.name.to_string()))?; - log::debug!( - "plugin {}: mutate {} took {:?}", - plugin.name, - entry.path(), - started.elapsed() - ); - apply::apply(moves, &mut trees, &mut visibility) - .with_context(|| MutationFailed(plugin.name.to_string()))?; - } + let (trees, visibility) = self.mutate(file, trees)?; match (sides, trees) { ( Pairing::Both { lhs, rhs }, @@ -246,17 +228,95 @@ impl Pipeline { rhs.regions = from_tree(right.regions); } (Pairing::LeftOnly { lhs }, tree::Pairing::LeftOnly { lhs: left }) => { - lhs.regions = from_tree(left.regions); + lhs.regions = from_tree(left.regions) } (Pairing::RightOnly { rhs }, tree::Pairing::RightOnly { rhs: right }) => { + rhs.regions = from_tree(right.regions) + } + _ => unreachable!("moves never add or remove a side"), + } + Ok(visibility) + } + + /// Run every plugin on one file's sides, returning the file's own + /// visibility. + pub(crate) fn run( + &self, + file: &FileChange, + sides: &mut crate::pairing::Comparison, + ) -> anyhow::Result { + use crate::pairing::Comparison; + let trees = match &*sides { + Comparison::Same { source, .. } => tree::Pairing::Same { + source: to_tree(source), + }, + Comparison::Both { lhs, rhs, .. } => tree::Pairing::Both { + lhs: to_tree(lhs), + rhs: to_tree(rhs), + }, + Comparison::LeftOnly { source, .. } => tree::Pairing::LeftOnly { + lhs: to_tree(source), + }, + Comparison::RightOnly { source, .. } => tree::Pairing::RightOnly { + rhs: to_tree(source), + }, + }; + let (trees, visibility) = self.mutate(file, trees)?; + match (sides, trees) { + (Comparison::Same { source, .. }, tree::Pairing::Same { source: processed }) => { + source.regions = from_tree(processed.regions) + } + ( + Comparison::Both { lhs, rhs, .. }, + tree::Pairing::Both { + lhs: left, + rhs: right, + }, + ) => { + lhs.regions = from_tree(left.regions); rhs.regions = from_tree(right.regions); } + (Comparison::LeftOnly { source, .. }, tree::Pairing::LeftOnly { lhs }) => { + source.regions = from_tree(lhs.regions) + } + (Comparison::RightOnly { source, .. }, tree::Pairing::RightOnly { rhs }) => { + source.regions = from_tree(rhs.regions) + } _ => unreachable!("moves never add or remove a side"), } - Ok(protocol::Visibility { - collapsed: visibility.collapsed, - label: visibility.label, - }) + Ok(visibility) + } + + fn mutate( + &self, + file: &FileChange, + mut trees: tree::Pairing, + ) -> anyhow::Result<(tree::Pairing, protocol::Visibility)> { + let entry = file_entry(file); + let mut visibility = types::Visibility::default(); + for plugin in &self.plugins { + let started = Instant::now(); + let records = source_sides(&trees); + let moves = plugin + .runner + .mutate(self.host(&plugin.name), &entry, &records) + .with_context(|| MutationFailed(plugin.name.to_string()))?; + log::debug!( + "plugin {}: mutate {} took {:?}", + plugin.name, + entry.path(), + started.elapsed() + ); + apply::apply(moves, &mut trees, &mut visibility) + .with_context(|| MutationFailed(plugin.name.to_string()))?; + } + Ok(( + trees, + protocol::Visibility { + collapsed: visibility.collapsed, + label: visibility.label, + }, + )) } } @@ -274,6 +334,7 @@ pub(crate) fn file_entry(file: &FileChange) -> types::FileEntry { Pairing::RightOnly { rhs } => types::FileSides::RightOnly(file_ref(rhs)), }, status: match file.status { + FileStatus::Unchanged => types::FileStatus::Unchanged, FileStatus::Added => types::FileStatus::Added, FileStatus::Deleted => types::FileStatus::Deleted, FileStatus::Modified => types::FileStatus::Modified, @@ -288,6 +349,7 @@ pub(crate) fn file_entry(file: &FileChange) -> types::FileEntry { /// The trees a plugin is given, as the contract's records. fn source_sides(trees: &tree::Pairing) -> types::SourceSides { match trees { + tree::Pairing::Same { source } => types::SourceSides::Same(source.to_record()), tree::Pairing::Both { lhs, rhs } => { types::SourceSides::Both((lhs.to_record(), rhs.to_record())) } @@ -322,6 +384,7 @@ pub(crate) fn to_tree(side: &protocol::Source) -> tree::Source { protocol::Node::Leaf { alignment_id, changed, + search_highlights, } => tree::Node::Leaf { alignment_id: *alignment_id, changed: changed @@ -332,6 +395,14 @@ pub(crate) fn to_tree(side: &protocol::Source) -> tree::Source { end_column: span.end_column, }) .collect(), + search_highlights: search_highlights + .iter() + .map(|span| types::Span { + line: span.line, + start_column: span.start_column, + end_column: span.end_column, + }) + .collect(), }, protocol::Node::Fold { children } => tree::Node::Fold { children: regions(children), @@ -372,6 +443,7 @@ fn from_tree(regions: Vec) -> Vec { tree::Node::Leaf { alignment_id, changed, + search_highlights, } => protocol::Node::Leaf { alignment_id, changed: changed @@ -382,6 +454,14 @@ fn from_tree(regions: Vec) -> Vec { end_column: span.end_column, }) .collect(), + search_highlights: search_highlights + .into_iter() + .map(|span| protocol::Span { + line: span.line, + start_column: span.start_column, + end_column: span.end_column, + }) + .collect(), }, tree::Node::Fold { children } => protocol::Node::Fold { children: from_tree(children), diff --git a/src/plugin/tests/mod.rs b/src/plugin/tests/mod.rs index a29e288b8..b20757023 100644 --- a/src/plugin/tests/mod.rs +++ b/src/plugin/tests/mod.rs @@ -85,7 +85,7 @@ pub(crate) fn manifest(path: &str, sides: &tree::Pairing, status: FileStat }; FileChange { file: match sides { - tree::Pairing::Both { .. } => Pairing::Both { + tree::Pairing::Same { .. } | tree::Pairing::Both { .. } => Pairing::Both { lhs: file_ref(), rhs: file_ref(), }, @@ -117,6 +117,10 @@ pub(crate) fn wire(sides: tree::Pairing) -> Pairing Pairing::Both { + lhs: source(same.clone()), + rhs: source(same), + }, tree::Pairing::Both { lhs, rhs } => Pairing::Both { lhs: source(lhs), rhs: source(rhs), @@ -173,7 +177,7 @@ pub(crate) fn run( file: &FileChange, sides: &mut Pairing, ) { - bundled(name, overrides).run(file, sides).unwrap(); + bundled(name, overrides).run_diff(file, sides).unwrap(); } /// Run the bundled plugin `name` with `overrides` on trees built by hand, @@ -421,7 +425,7 @@ fn classifying_plugins_add_tags_in_order_and_a_bad_tag_is_an_error() { fn a_move_that_cannot_be_carried_out_fails_naming_the_plugin() { let (file, mut sides) = project("a.rs", "fn a() {}\n", "fn b() {}\n"); let bad = pipeline(vec![("bad", native::native::)]); - let error = bad.run(&file, &mut sides).unwrap_err(); + let error = bad.run_diff(&file, &mut sides).unwrap_err(); assert!(error.downcast_ref::().is_some()); assert_eq!(format!("{error:#}"), "mutation bad: no region 99999"); } diff --git a/src/plugin/wasm.rs b/src/plugin/wasm.rs index 90530d043..33c1531d4 100644 --- a/src/plugin/wasm.rs +++ b/src/plugin/wasm.rs @@ -344,6 +344,7 @@ fn file_entry(file: &contract::FileEntry) -> types::FileEntry { contract::FileSides::RightOnly(rhs) => types::FileSides::RightOnly(file_ref(rhs)), }, status: match file.status { + contract::FileStatus::Unchanged => types::FileStatus::Unchanged, contract::FileStatus::Added => types::FileStatus::Added, contract::FileStatus::Deleted => types::FileStatus::Deleted, contract::FileStatus::Modified => types::FileStatus::Modified, @@ -357,6 +358,7 @@ fn file_entry(file: &contract::FileEntry) -> types::FileEntry { fn source_sides(sides: &contract::SourceSides) -> types::SourceSides { match sides { + contract::SourceSides::Same(same) => types::SourceSides::Same(source(same)), contract::SourceSides::Both((lhs, rhs)) => { types::SourceSides::Both((source(lhs), source(rhs))) } @@ -400,6 +402,15 @@ fn source(side: &contract::Source) -> types::Source { end_column: span.end_column, }) .collect(), + search_highlights: leaf + .search_highlights + .iter() + .map(|span| types::Span { + line: span.line, + start_column: span.start_column, + end_column: span.end_column, + }) + .collect(), }), contract::Kind::Fold => types::Kind::Fold, }, diff --git a/src/protocol/project.rs b/src/protocol/project.rs index 8df7200aa..17f0ffb0e 100644 --- a/src/protocol/project.rs +++ b/src/protocol/project.rs @@ -570,6 +570,7 @@ fn leaf_region( visibility: Visibility::default(), node: Node::Leaf { alignment_id, + search_highlights: Vec::new(), changed, }, } diff --git a/src/protocol/stream.rs b/src/protocol/stream.rs index e7382a4a4..914e816d9 100644 --- a/src/protocol/stream.rs +++ b/src/protocol/stream.rs @@ -226,7 +226,7 @@ fn shape( mut sides, mut stats, } => { - let visibility = pipeline.run(entry, &mut sides)?; + let visibility = pipeline.run_diff(entry, &mut sides)?; stats.visible = visible_counts(&sides); Ok((visibility, Diff::Text { sides, stats })) } @@ -236,7 +236,7 @@ fn shape( syntax: Vec::new(), regions: Vec::new(), }); - let visibility = pipeline.run(entry, &mut empty)?; + let visibility = pipeline.run_diff(entry, &mut empty)?; Ok((visibility, Diff::Binary { sides })) } } @@ -375,6 +375,7 @@ pub(crate) fn visible_counts(sides: &Pairing) -> LineCounts { Node::Leaf { alignment_id, changed, + .. } => { if hidden { continue; @@ -442,6 +443,7 @@ mod visible_tests { }, node: Node::Leaf { alignment_id: alignment, + search_highlights: Vec::new(), changed: changed .iter() .map(|&line| Span { From 29db9d04b95fae4e5de9ee68ec6e8f1311fa96a4 Mon Sep 17 00:00:00 2001 From: Sidharth Menon Date: Sat, 19 Sep 2026 14:48:27 -0700 Subject: [PATCH 3/3] Make bundled plugins honor highlighted evidence Expand context around changes and matches, preserving visible scope boundaries. Keep highlighted deletions open, group shared sources once, and summarize only added unpaired bodies. Include behavior tests and the rebuilt bundled summarizer. AI assistance: reorganized with Codex. --- plugins/context/queries/javascript.scm | 8 ++ plugins/context/src/lib.rs | 152 +++++++++++++++++++++---- plugins/group/src/lib.rs | 15 ++- plugins/removed-runs/src/lib.rs | 11 +- plugins/summarize/plugin.toml | 2 +- plugins/summarize/plugin.wasm | Bin 545414 -> 550830 bytes plugins/summarize/src/lib.rs | 14 ++- src/plugin/tests/context.rs | 20 ++++ src/plugin/tests/deleted_bodies.rs | 1 + src/plugin/tests/group.rs | 42 +++++++ src/plugin/tests/removed_runs.rs | 2 + src/plugin/tests/summarize.rs | 45 +++++--- 12 files changed, 268 insertions(+), 44 deletions(-) diff --git a/plugins/context/queries/javascript.scm b/plugins/context/queries/javascript.scm index 0037606c3..6357f7343 100644 --- a/plugins/context/queries/javascript.scm +++ b/plugins/context/queries/javascript.scm @@ -22,3 +22,11 @@ (#set! tag "context:scope")) ((return_statement) @fold (#set! tag "context:scope")) + +; Preserve both the try body boundary (`} catch`) and the end of the whole +; try/catch construct when its opening line is visible in context. +((try_statement) @fold + (#set! tag "context:scope")) +(try_statement + body: (statement_block "{" @fold.open "}" @fold.close) @fold + (#set! tag "context:body")) diff --git a/plugins/context/src/lib.rs b/plugins/context/src/lib.rs index d3df92baa..57dc00788 100644 --- a/plugins/context/src/lib.rs +++ b/plugins/context/src/lib.rs @@ -2,9 +2,10 @@ //! //! A line stays visible when it is within `lines` of a changed line on its //! side, when it is paired with such a line, or when it opens or closes a -//! scope that holds a change on its side. Scopes are the constructs this -//! plugin's queries tag `context:scope` (see `scope_rows`), each covering -//! its signature line through the line that closes it; a file the +//! scope that holds a change on its side or has a visible boundary. Scopes +//! are the constructs this plugin's queries tag `context:scope` (see +//! `scope_rows`), each covering its signature through its closing line. +//! A `context:body` fold instead excludes both delimiter lines. A file the //! diff did not parse has none. Every other stretch of unchanged //! paired lines that is at least `MIN_GAP` lines long collapses, labelled //! with its line count; a file with no change collapses whole, however @@ -33,6 +34,9 @@ const MIN_GAP: u32 = 3; /// A fold the queries mark as a scope: a function, a type, a block. const SCOPE: &str = "context:scope"; +/// A body fold excludes its delimiter lines, unlike a whole-construct scope. +const BODY: &str = "context:body"; + pub struct Context { options: Options, } @@ -45,25 +49,42 @@ pub struct Options { } /// The first and last line of every scope on `source` that holds a changed -/// line. +/// line or has an opening or closing line already visible in the context window. /// /// A scope region is a whole construct: its first line is the line its /// signature or header starts on and its last is the line that closes it, /// since the construct's own node is what the queries tag. Keeping both /// always shows where a scope opens and where it ends, whatever sits inside. -fn scope_rows(source: &Source, changed: &BTreeSet) -> BTreeSet { - let mut rows = BTreeSet::new(); - walk(&source.regions, &mut |region| { - if !is_fold(region) || !has_tag(region, SCOPE) { - return; - } - let span = region.range.lines(); - if changed.range(span.clone()).next().is_none() { - return; +/// Body folds use the immediately adjacent lines for their delimiters. +fn scope_rows(source: &Source, changed: &BTreeSet, visible: &BTreeSet) -> BTreeSet { + let mut rows = visible.clone(); + loop { + let previous = rows.len(); + walk(&source.regions, &mut |region| { + if !is_fold(region) || !(has_tag(region, SCOPE) || has_tag(region, BODY)) { + return; + } + let span = region.range.lines(); + let (first, last) = if has_tag(region, BODY) { + (span.start.saturating_sub(1), span.end) + } else { + (span.start, span.end - 1) + }; + if changed.range(span.clone()).next().is_none() + && !rows.contains(&first) + && !rows.contains(&last) + { + return; + } + rows.insert(first); + rows.insert(last); + }); + // Shared boundaries connect constructs such as try and catch. Repeat + // so neither side of such a boundary leaves an unmatched delimiter. + if rows.len() == previous { + break; } - rows.insert(span.start); - rows.insert(span.end - 1); - }); + } rows } @@ -77,6 +98,7 @@ struct Leaf { paired: bool, /// Whether this side paints any byte of it as changed. spans: bool, + highlights: BTreeSet, } fn leaves(regions: &[Region], other: &BTreeSet) -> Vec { @@ -85,6 +107,7 @@ fn leaves(regions: &[Region], other: &BTreeSet) -> Vec { if let Node::Leaf { alignment_id, changed, + search_highlights, } = ®ion.node { out.push(Leaf { @@ -94,6 +117,7 @@ fn leaves(regions: &[Region], other: &BTreeSet) -> Vec { end: region.range.end.line, paired: other.contains(alignment_id), spans: !changed.is_empty(), + highlights: search_highlights.iter().map(|span| span.line).collect(), }); } }); @@ -204,6 +228,80 @@ fn segments(regions: &[Region], start: u32, end: u32, out: &mut Vec> } } +// Same processes one unchanged tree. Genuine one-sided diffs retain their +// existing behavior when there are no search highlights. +fn single_side_context(sides: &Pairing, context: u32) -> anyhow::Result> { + let source = sides.sides()[0]; + if !matches!(sides, Pairing::Same { .. }) + && !source + .regions + .iter() + .any(diffr_plugin_sdk::has_search_highlights) + { + return Ok(Vec::new()); + } + let mut anchors = BTreeSet::new(); + walk(&source.regions, &mut |region| { + if let Node::Leaf { + changed, + search_highlights, + .. + } = ®ion.node + { + if !changed.is_empty() { + anchors.extend(region.range.lines()); + } + anchors.extend(search_highlights.iter().map(|span| span.line)); + } + }); + let count = source.text.split_terminator('\n').count() as u32; + let mut shown = BTreeSet::new(); + for line in &anchors { + shown.extend( + line.saturating_sub(context)..line.saturating_add(context).saturating_add(1).min(count), + ); + } + shown.extend(scope_rows(source, &anchors, &shown)); + let mut gaps: Vec<(u32, u32)> = Vec::new(); + for line in (0..count).filter(|line| !shown.contains(line)) { + if let Some((_, end)) = gaps.last_mut().filter(|(_, end)| *end == line) { + *end += 1; + } else { + gaps.push((line, line + 1)); + } + } + let mut draft = Draft::new(sides); + for (start, end) in gaps.into_iter().rev() { + if end - start < MIN_GAP && !matches!(sides, Pairing::Same { .. }) { + continue; + } + let mut parts = Vec::new(); + segments(&source.regions, start, end, &mut parts); + for part in parts.into_iter().rev() { + let length: u32 = part.iter().map(Member::lines).sum(); + if length < MIN_GAP && length != end - start { + continue; + } + let mut members = Vec::new(); + for member in part.into_iter().rev() { + let (id, lines) = match member { + Member::Whole { id, lines, .. } => (id, lines), + Member::Part { id, start, end, .. } => { + (draft.cut_lines(id, start, end)?, end - start) + } + }; + draft.collapse(id, unchanged_label(lines))?; + members.push(id); + } + members.reverse(); + if members.len() > 1 { + draft.group(members, unchanged_label(length))?; + } + } + } + Ok(draft.into_moves()) +} + impl Plugin for Context { type Options = Options; @@ -258,8 +356,7 @@ impl Plugin for Context { fn mutate(&self, _file: &FileEntry, sides: &Pairing) -> anyhow::Result> { let options = &self.options; let Pairing::Both { lhs, rhs } = &sides else { - // A one-sided file is all changed lines. - return Ok(Vec::new()); + return single_side_context(sides, options.lines); }; let lhs_leaves = leaves(&lhs.regions, &leaf_alignments(&rhs.regions)); let rhs_leaves = leaves(&rhs.regions, &leaf_alignments(&lhs.regions)); @@ -295,6 +392,22 @@ impl Plugin for Context { unchanged.push((leaf.start, partner.start, leaf.end - leaf.start)); } } + // Search matches seed their actual rows, not the whole containing leaf. + // Keep unchanged leaves eligible for cutting around those rows. + for (side, own, other) in [(0, &lhs_leaves, &rhs_leaves), (1, &rhs_leaves, &lhs_leaves)] { + for leaf in own { + novel[side].extend(&leaf.highlights); + seeds[side].extend(&leaf.highlights); + if let Some(partner) = other.iter().find(|other| other.alignment == leaf.alignment) + { + seeds[1 - side].extend( + leaf.highlights + .iter() + .map(|line| partner.start + line - leaf.start), + ); + } + } + } let changed = !seeds[0].is_empty() || !seeds[1].is_empty(); let counts = [lhs, rhs].map(|source| source.text.split_terminator('\n').count() as u32); @@ -310,7 +423,8 @@ impl Plugin for Context { if changed { for (side, source) in [lhs, rhs].into_iter().enumerate() { // Context adds unchanged rows only; changed rows are shown anyway. - shown[side].extend(scope_rows(source, &novel[side])); + let boundaries = scope_rows(source, &novel[side], &shown[side]); + shown[side].extend(boundaries); } } diff --git a/plugins/group/src/lib.rs b/plugins/group/src/lib.rs index 543e9c88c..3ff280d1e 100644 --- a/plugins/group/src/lib.rs +++ b/plugins/group/src/lib.rs @@ -22,8 +22,8 @@ //! run whose regions are paired across sides some other way stays as it //! is. use diffr_plugin_sdk::{ - anyhow, export, is_fold, line_count, sides_with_other_ids, Draft, FileEntry, Move, Node, - Pairing, Plugin, Region, Source, + anyhow, export, has_search_highlights, is_fold, line_count, sides_with_other_ids, Draft, + FileEntry, Move, Node, Pairing, Plugin, Region, Source, }; use serde::Deserialize; use std::collections::BTreeSet; @@ -57,7 +57,11 @@ impl Plugin for Group { } fn mutate(&self, _file: &FileEntry, sides: &Pairing) -> anyhow::Result> { - let per_side: Vec<_> = sides_with_other_ids(sides) + let sources = match sides { + Pairing::Same { source } => vec![(source, diffr_plugin_sdk::OtherSide::default())], + _ => sides_with_other_ids(sides), + }; + let per_side: Vec<_> = sources .into_iter() .map(|(source, other_ids)| (runs(&source.regions), other_ids)) .collect(); @@ -176,6 +180,11 @@ fn collapsed_runs<'a>(regions: &'a [Region], out: &mut Vec>) { let mut separator: Vec<&Region> = Vec::new(); let mut gap = 0; for region in regions { + if has_search_highlights(region) { + push_run(&mut run, &mut own); + separator.clear(); + continue; + } match shape(region) { // A run starts with a collapsed row: a group never hides open // lines above its first one. diff --git a/plugins/removed-runs/src/lib.rs b/plugins/removed-runs/src/lib.rs index de952648e..cd3b2d893 100644 --- a/plugins/removed-runs/src/lib.rs +++ b/plugins/removed-runs/src/lib.rs @@ -1,7 +1,7 @@ //! Collapse the middle of long removed stretches. use diffr_plugin_sdk::{ - anyhow, before_and_after_ids, export, has_tag, line_count, one_sided, Draft, FileEntry, Move, - Node, OtherSide, Pairing, Plugin, Region, Source, + anyhow, before_and_after_ids, export, has_search_highlights, has_tag, line_count, one_sided, + Draft, FileEntry, Move, Node, OtherSide, Pairing, Plugin, Region, Source, }; use serde::Deserialize; @@ -149,7 +149,12 @@ fn visit( } Node::Leaf { .. } => { let len = line_count(region); - if !collapsed && gates.open() && !rhs.pairs(region) && len >= threshold { + if !collapsed + && !has_search_highlights(region) + && gates.open() + && !rhs.pairs(region) + && len >= threshold + { leaves.push((region.id, len as u32)); } } diff --git a/plugins/summarize/plugin.toml b/plugins/summarize/plugin.toml index 50a073898..fd5e7a551 100644 --- a/plugins/summarize/plugin.toml +++ b/plugins/summarize/plugin.toml @@ -76,7 +76,7 @@ verbatim from that doc; otherwise leave it empty. Answer with a JSON array of \ [options.tests] type = "boolean" title = "Summarize tests" -description = "Summarize right-side test bodies, including modified and unchanged tests in diffed files." +description = "Summarize only added, unpaired right-side test bodies." default = true [options.test_min_lines] diff --git a/plugins/summarize/plugin.wasm b/plugins/summarize/plugin.wasm index 542201db146b6c2c2f43fbbea1116e032bb229d3..e486a1b1b8626c94d691ee53b3b60e9700f8ad32 100644 GIT binary patch delta 86299 zcmdSC33yaR7B<{f)tz+GoumWV6X13eAb|ve#1IfA7g+^HR7O!z#BDHu;x^(&hebqX zwbcSth>8O`pkNq-dsI|Z(5MKgY$8h(WKq;Op#JZvy7#6N;x;q?_k5o`rtZD9ojT{# z*=p(Oj|ZQ6Kj?FfzJ7dyi@9PSjhW}Wl*P`wx|@i*@#=XCKV%{@o~5@)zW$2Q*IhQI zaLiSI8#507-*A0oDw`kYc3(ew{1q(Q^}rBs+p6CDNCtoDrK*#%SHz3Mo`aEH?e6w4 z?uw0#jBVffLNBBLlHB+c%b45k@x(ADL|j~)OSthLeW(8zcJUuBr@=g1*417?{P#Z#Uv>Jz+Xpfq~f00=o((_}J@E<{v#Jq0$ zO5LJ2dK3&VLU_H@Ox0r@1~PUuav-my-MTrixqM-l`L_D|j{3XP`c}8-b@g5w8IXTw z-{+U;l8ddh@-dzJ8l(EJtG{okzu#H)mMzvL?z7SvizA=rpUT$DTgqa)bJvE*SshOj z@9`e;wzAmV$eEoIw&Rdex}wt{6RT+%=cEwnRSd)G@q>FXm71fAA&zD0`HD!z%fA>;65w7*K@$L&vUn!BW8

T|!@X9#DQ3IxcHiTk;XWv~i{0W2 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 z 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 delta 81122 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 mZ<8m;YvSL (OtherSide::of(&lhs.regions), rhs), Pairing::RightOnly { rhs } => (OtherSide::default(), rhs), - Pairing::LeftOnly { .. } => return Vec::new(), + Pairing::Same { .. } | Pairing::LeftOnly { .. } => return Vec::new(), }; let mut selected = Vec::new(); fn visit( @@ -238,7 +238,8 @@ pub fn select( ) { for region in regions { let eligible = if has_tag(region, TEST) { - test_min_lines.is_some_and(|minimum| line_count(region) >= minimum) + one_sided(region, lhs) + && test_min_lines.is_some_and(|minimum| line_count(region) >= minimum) } else { has_tag(region, FUNCTION) && !region.visibility.collapsed @@ -422,6 +423,11 @@ impl Plugin for Summarize { }; let folds: Vec = selected .into_iter() + .filter(|(id, _, _, docstring)| { + let mut ids = vec![*id]; + ids.extend(*docstring); + !diffr_plugin_sdk::highlights_in_states(sides, &ids) + }) .map(|(id, first_line, last_line, docstring)| Request { id, first_line, diff --git a/src/plugin/tests/context.rs b/src/plugin/tests/context.rs index beae09445..e26da0e3a 100644 --- a/src/plugin/tests/context.rs +++ b/src/plugin/tests/context.rs @@ -311,6 +311,7 @@ fn a_fold_whose_matched_partner_holds_changes_stays_open() { visibility: types::Visibility::default(), node: tree::Node::Leaf { alignment_id: alignment, + search_highlights: Vec::new(), changed: (start..end) .filter(|_| changed) .map(|line| types::Span { @@ -566,3 +567,22 @@ fn a_scope_keeps_the_line_it_closes_on() { ); } } + +#[test] +fn visible_try_and_for_headers_keep_their_closing_boundaries() { + let before = include_str!("../../../tests/code-mode/fixtures/base/retry.js"); + let after = include_str!("../../../tests/code-mode/fixtures/head/retry.js"); + // The changed line is above the loop; context reaches its try header. + let sides = shaped("retry.js", before, after, 4); + for source in [lhs(&sides), rhs(&sides)] { + let visible = open_lines(&source.regions); + for line in [4, 5, 12, 14, 15, 17] { + assert!( + visible.contains(&line), + "missing boundary on line {}", + line + 1 + ); + } + assert!(!visible.contains(&7), "the try body should still collapse"); + } +} diff --git a/src/plugin/tests/deleted_bodies.rs b/src/plugin/tests/deleted_bodies.rs index f83f873e6..a8ebc372e 100644 --- a/src/plugin/tests/deleted_bodies.rs +++ b/src/plugin/tests/deleted_bodies.rs @@ -24,6 +24,7 @@ fn leaf(id: u32, alignment: u32, start: u32, end: u32) -> tree::Region { visibility: types::Visibility::default(), node: tree::Node::Leaf { alignment_id: alignment, + search_highlights: Vec::new(), changed: vec![], }, } diff --git a/src/plugin/tests/group.rs b/src/plugin/tests/group.rs index 3a47758cc..8c4118e50 100644 --- a/src/plugin/tests/group.rs +++ b/src/plugin/tests/group.rs @@ -24,6 +24,7 @@ fn leaf(id: u32, alignment: u32, start: u32, end: u32) -> tree::Region { visibility: types::Visibility::default(), node: tree::Node::Leaf { alignment_id: alignment, + search_highlights: Vec::new(), changed: vec![], }, } @@ -230,3 +231,44 @@ fn a_single_collapsed_fold_is_left_alone() { .unwrap() .is_empty()); } + +#[test] +fn unchanged_source_groups_once_and_keeps_shared_fold_state() { + let sides = tree::Pairing::Same { + source: source(vec![ + fold(1, 1, 0, 5, true, "5 unchanged lines"), + fold(2, 2, 5, 10, true, "5 unchanged lines"), + ]), + }; + let file = manifest("same.py", &sides, FileStatus::Unchanged); + let tree::Pairing::Same { source } = sides else { + unreachable!() + }; + let Pairing::Both { + lhs: lhs_file, + rhs: rhs_file, + } = file.file.clone() + else { + unreachable!() + }; + let mut comparison = crate::pairing::Comparison::Same { + lhs_file, + rhs_file, + source: protocol::Source { + text: source.text, + syntax: vec![], + regions: from_tree(source.regions), + }, + }; + bundled("group", json!({})) + .run(&file, &mut comparison) + .unwrap(); + let crate::pairing::Comparison::Same { source, .. } = comparison else { + panic!("shared tree retained") + }; + assert_eq!(source.regions.len(), 1); + assert_eq!( + source.regions[0].visibility.label, + "2 collapsed regions ยท 10 lines" + ); +} diff --git a/src/plugin/tests/removed_runs.rs b/src/plugin/tests/removed_runs.rs index 79801cead..dd24175bb 100644 --- a/src/plugin/tests/removed_runs.rs +++ b/src/plugin/tests/removed_runs.rs @@ -22,6 +22,7 @@ fn removed_leaf(id: u32, alignment: u32, start: u32, end: u32, changed: &[u32]) visibility: types::Visibility::default(), node: tree::Node::Leaf { alignment_id: alignment, + search_highlights: Vec::new(), changed: changed .iter() .map(|&line| types::Span { @@ -76,6 +77,7 @@ fn shape(regions: &[tree::Region]) -> Vec { let tree::Node::Leaf { alignment_id, changed, + .. } = ®ion.node else { panic!("leaf expected"); diff --git a/src/plugin/tests/summarize.rs b/src/plugin/tests/summarize.rs index 83d369202..d2176bcdb 100644 --- a/src/plugin/tests/summarize.rs +++ b/src/plugin/tests/summarize.rs @@ -167,7 +167,9 @@ fn long_summaries_are_discarded_and_the_body_stays_open() { let (file, mut sides) = project("a.py", "", LARGE); let id = select(&trees(&sides), 3, None)[0].0; let (endpoint, server) = serve(vec![(200, gemini_answer(&[(id, "a()\nb()\nc()")]))]); - summarizer(&endpoint, 0).run(&file, &mut sides).unwrap(); + summarizer(&endpoint, 0) + .run_diff(&file, &mut sides) + .unwrap(); let sides = trees(&sides); server.join().unwrap(); let mut folds = Vec::new(); @@ -185,7 +187,9 @@ fn summaries_collapse_selected_folds_behind_pseudocode() { let (file, mut sides) = project("a.py", "", LARGE); let id = select(&trees(&sides), 3, None)[0].0; let (endpoint, server) = serve(vec![(200, gemini_answer(&[(id, "call a, b, c")]))]); - summarizer(&endpoint, 0).run(&file, &mut sides).unwrap(); + summarizer(&endpoint, 0) + .run_diff(&file, &mut sides) + .unwrap(); let sides = trees(&sides); let bodies = server.join().unwrap(); assert!(bodies[0].contains("thinkingBudget")); @@ -222,7 +226,9 @@ fn a_docstring_is_sent_and_only_a_verbatim_sentence_from_it_is_kept() { let (file, mut sides) = project("a.rs", "", after); let id = select(&trees(&sides), 3, None)[0].0; let (endpoint, server) = serve(vec![(200, answer(id, "Sums three numbers."))]); - summarizer(&endpoint, 0).run(&file, &mut sides).unwrap(); + summarizer(&endpoint, 0) + .run_diff(&file, &mut sides) + .unwrap(); let sides = trees(&sides); let bodies = server.join().unwrap(); assert!( @@ -236,7 +242,9 @@ fn a_docstring_is_sent_and_only_a_verbatim_sentence_from_it_is_kept() { // A sentence the docstring does not contain is dropped. let (file, mut sides) = project("a.rs", "", after); let (endpoint, server) = serve(vec![(200, answer(id, "Adds things up."))]); - summarizer(&endpoint, 0).run(&file, &mut sides).unwrap(); + summarizer(&endpoint, 0) + .run_diff(&file, &mut sides) + .unwrap(); let sides = trees(&sides); server.join().unwrap(); assert_eq!(body_label(&sides), "return a + b + c"); @@ -324,7 +332,7 @@ fn the_system_prompt_is_the_configured_one() { overrides["api_key"] = json!("test-key"); overrides["endpoint"] = json!(endpoint); overrides["min_lines"] = json!(3); - summarizer_with(overrides).run(&file, sides).unwrap(); + summarizer_with(overrides).run_diff(&file, sides).unwrap(); let bodies = server.join().unwrap(); let body: serde_json::Value = serde_json::from_str(&bodies[0]).unwrap(); ( @@ -360,7 +368,9 @@ fn transient_failures_are_retried_then_succeed() { (429, "{}".to_owned()), (200, gemini_answer(&[(id, "retry ok")])), ]); - summarizer(&endpoint, 3).run(&file, &mut sides).unwrap(); + summarizer(&endpoint, 3) + .run_diff(&file, &mut sides) + .unwrap(); let sides = trees(&sides); assert_eq!(server.join().unwrap().len(), 3); let label = fold_label(&sides); @@ -372,7 +382,7 @@ fn hard_failures_and_exhausted_retries_are_run_failures() { let (file, sides) = project("a.py", "", LARGE); let (endpoint, server) = serve(vec![(400, "{\"error\": \"bad key\"}".to_owned())]); let error = summarizer(&endpoint, 3) - .run(&file, &mut sides.clone()) + .run_diff(&file, &mut sides.clone()) .unwrap_err(); server.join().unwrap(); assert!(error.downcast_ref::().is_some()); @@ -380,7 +390,7 @@ fn hard_failures_and_exhausted_retries_are_run_failures() { assert!(format!("{error:#}").contains("HTTP 400"), "{error:#}"); let (endpoint, server) = serve(vec![(500, "{}".to_owned()), (500, "{}".to_owned())]); let error = summarizer(&endpoint, 1) - .run(&file, &mut sides.clone()) + .run_diff(&file, &mut sides.clone()) .unwrap_err(); server.join().unwrap(); assert!( @@ -434,13 +444,13 @@ fn external_component_summarizes_over_http() { &|host, options| plugin.create(host, options), ) .unwrap(); - pipeline.run(&file, &mut sides).unwrap(); + pipeline.run_diff(&file, &mut sides).unwrap(); assert_eq!(fold_label(&trees(&sides)), "call a, b, c"); assert_eq!(server.join().unwrap().len(), 2); } #[test] -fn tests_are_selected_when_added_modified_unchanged_or_already_collapsed() { +fn only_unpaired_added_tests_are_selected_even_when_already_collapsed() { for (path, before, after) in [ ( "a.py", @@ -469,11 +479,18 @@ fn tests_are_selected_when_added_modified_unchanged_or_already_collapsed() { let comment = if path.ends_with(".py") { "#" } else { "//" }; let after = format!("{after}\n{comment} changed elsewhere\n"); let (file, mut sides) = project(path, old, &after); - assert_eq!(select(&trees(&sides), 3, Some(3)).len(), 1, "{path}: {old}"); + assert_eq!( + select(&trees(&sides), 3, Some(3)).len(), + usize::from(old.is_empty()), + "{path}: {old}" + ); assert!(select(&trees(&sides), 3, None).is_empty()); assert!(select(&trees(&sides), 3, Some(30)).is_empty()); run("test-bodies", json!({"min_lines": 3}), &file, &mut sides); - assert_eq!(select(&trees(&sides), 3, Some(3)).len(), 1); + assert_eq!( + select(&trees(&sides), 3, Some(3)).len(), + usize::from(old.is_empty()) + ); } } } @@ -488,7 +505,7 @@ fn suites_select_individual_tests_and_preserve_nested_summary_folds() { let selected = select(&trees(&sides), 3, Some(3)); assert_eq!(selected.len(), 2, "{path}: {selected:?}"); let (endpoint, server) = serve(vec![(200, gemini_answer(&[(selected[0].0, "setup; act; check one"), (selected[1].0, "setup; act; check two")]))]); - summarizer_with(json!({"api_key": "test", "endpoint": endpoint, "test_min_lines": 3})).run(&file, &mut sides).unwrap(); + summarizer_with(json!({"api_key": "test", "endpoint": endpoint, "test_min_lines": 3})).run_diff(&file, &mut sides).unwrap(); run("test-bodies", json!({"min_lines": 3}), &file, &mut sides); server.join().unwrap(); run("group", json!({}), &file, &mut sides); @@ -527,7 +544,7 @@ fn bundled_wasm_summarizer_streams_large_prompts() { "api_key": "test", "endpoint": endpoint, "test_min_lines": 3, "retries": 0, })); assert!(builtin::component("summarize").is_some()); - wasm.run(&file, &mut sides).unwrap(); + wasm.run_diff(&file, &mut sides).unwrap(); assert_eq!(fold_label(&trees(&sides)), "setup; act; check"); let requests = server.join().unwrap(); assert_eq!(requests.len(), 1);