2023-05-19 23:52:57 +00:00
use std ::{ iter , ops ::Range } ;
2022-09-15 17:57:57 +00:00
use sum_tree ::SumTree ;
2024-03-08 23:37:24 +00:00
use text ::{ Anchor , BufferId , BufferSnapshot , OffsetRangeExt , Point } ;
2022-09-01 21:22:12 +00:00
pub use git2 as libgit ;
2022-09-15 17:57:57 +00:00
use libgit ::{ DiffLineType as GitDiffLineType , DiffOptions as GitOptions , Patch as GitPatch } ;
2022-09-01 21:22:12 +00:00
2022-10-03 00:56:09 +00:00
#[ derive(Debug, Clone, Copy, PartialEq, Eq) ]
2022-09-01 21:22:12 +00:00
pub enum DiffHunkStatus {
Added ,
Modified ,
Removed ,
}
2024-03-08 23:37:24 +00:00
/// A diff hunk, representing a range of consequent lines in a singleton buffer, associated with a generic range.
2022-09-09 19:40:33 +00:00
#[ derive(Debug, Clone, PartialEq, Eq) ]
2022-09-01 21:22:12 +00:00
pub struct DiffHunk < T > {
2024-03-08 23:37:24 +00:00
/// E.g. a range in multibuffer, that has an excerpt added, singleton buffer for which has this diff hunk.
/// Consider a singleton buffer with 10 lines, all of them are modified — so a corresponding diff hunk would have a range 0..10.
/// And a multibuffer with the excerpt of lines 2-6 from the singleton buffer.
/// If the multibuffer is searched for diff hunks, the associated range would be multibuffer rows, corresponding to rows 2..6 from the singleton buffer.
/// But the hunk range would be 0..10, same for any other excerpts from the same singleton buffer.
pub associated_range : Range < T > ,
/// Singleton buffer ID this hunk belongs to.
pub buffer_id : BufferId ,
/// A consequent range of lines in the singleton buffer, that were changed and produced this diff hunk.
pub buffer_range : Range < Anchor > ,
/// Original singleton buffer text before the change, that was instead of the `buffer_range`.
2022-10-24 20:33:43 +00:00
pub diff_base_byte_range : Range < usize > ,
2022-09-01 21:22:12 +00:00
}
impl DiffHunk < u32 > {
pub fn status ( & self ) -> DiffHunkStatus {
2022-10-24 20:33:43 +00:00
if self . diff_base_byte_range . is_empty ( ) {
2022-09-01 21:22:12 +00:00
DiffHunkStatus ::Added
2024-03-11 15:53:45 +00:00
} else if self . associated_range . is_empty ( ) {
2022-09-01 21:22:12 +00:00
DiffHunkStatus ::Removed
} else {
DiffHunkStatus ::Modified
}
}
}
2022-09-09 15:52:42 +00:00
impl sum_tree ::Item for DiffHunk < Anchor > {
type Summary = DiffHunkSummary ;
2022-09-01 21:22:12 +00:00
2022-09-09 15:52:42 +00:00
fn summary ( & self ) -> Self ::Summary {
DiffHunkSummary {
2024-03-08 23:37:24 +00:00
buffer_range : self . associated_range . clone ( ) ,
2022-09-01 21:22:12 +00:00
}
}
2022-09-09 15:52:42 +00:00
}
#[ derive(Debug, Default, Clone) ]
pub struct DiffHunkSummary {
2022-09-09 19:40:33 +00:00
buffer_range : Range < Anchor > ,
2022-09-09 15:52:42 +00:00
}
impl sum_tree ::Summary for DiffHunkSummary {
2022-09-09 19:40:33 +00:00
type Context = text ::BufferSnapshot ;
2022-09-09 15:52:42 +00:00
2022-09-30 17:32:54 +00:00
fn add_summary ( & mut self , other : & Self , buffer : & Self ::Context ) {
self . buffer_range . start = self
. buffer_range
. start
. min ( & other . buffer_range . start , buffer ) ;
self . buffer_range . end = self . buffer_range . end . max ( & other . buffer_range . end , buffer ) ;
2022-09-09 19:40:33 +00:00
}
}
2024-03-08 23:37:24 +00:00
#[ derive(Debug, Clone) ]
2022-09-15 23:06:45 +00:00
pub struct BufferDiff {
2022-09-16 18:49:24 +00:00
last_buffer_version : Option < clock ::Global > ,
2022-09-09 19:40:33 +00:00
tree : SumTree < DiffHunk < Anchor > > ,
}
2022-09-15 23:06:45 +00:00
impl BufferDiff {
2022-09-16 18:49:24 +00:00
pub fn new ( ) -> BufferDiff {
BufferDiff {
last_buffer_version : None ,
2022-09-15 23:06:45 +00:00
tree : SumTree ::new ( ) ,
}
}
2023-05-22 17:55:44 +00:00
pub fn is_empty ( & self ) -> bool {
self . tree . is_empty ( )
}
2022-12-15 05:17:28 +00:00
pub fn hunks_in_row_range < ' a > (
& ' a self ,
range : Range < u32 > ,
buffer : & ' a BufferSnapshot ,
) -> impl ' a + Iterator < Item = DiffHunk < u32 > > {
let start = buffer . anchor_before ( Point ::new ( range . start , 0 ) ) ;
let end = buffer . anchor_after ( Point ::new ( range . end , 0 ) ) ;
2023-05-20 01:09:47 +00:00
self . hunks_intersecting_range ( start .. end , buffer )
2022-12-15 05:17:28 +00:00
}
2022-12-15 22:09:09 +00:00
pub fn hunks_intersecting_range < ' a > (
2022-09-09 19:40:33 +00:00
& ' a self ,
2022-12-09 20:24:13 +00:00
range : Range < Anchor > ,
2022-09-09 19:40:33 +00:00
buffer : & ' a BufferSnapshot ,
) -> impl ' a + Iterator < Item = DiffHunk < u32 > > {
2022-09-30 17:32:54 +00:00
let mut cursor = self . tree . filter ::< _ , DiffHunkSummary > ( move | summary | {
2022-12-09 20:24:13 +00:00
let before_start = summary . buffer_range . end . cmp ( & range . start , buffer ) . is_lt ( ) ;
let after_end = summary . buffer_range . start . cmp ( & range . end , buffer ) . is_gt ( ) ;
2022-09-30 17:32:54 +00:00
! before_start & & ! after_end
} ) ;
2023-05-19 22:06:38 +00:00
let anchor_iter = std ::iter ::from_fn ( move | | {
2023-05-20 01:09:47 +00:00
cursor . next ( buffer ) ;
2023-05-19 22:06:38 +00:00
cursor . item ( )
} )
2023-05-19 23:52:57 +00:00
. flat_map ( move | hunk | {
[
2024-03-08 23:37:24 +00:00
(
& hunk . associated_range . start ,
hunk . diff_base_byte_range . start ,
) ,
( & hunk . associated_range . end , hunk . diff_base_byte_range . end ) ,
2023-05-19 23:52:57 +00:00
]
. into_iter ( )
2023-05-19 22:06:38 +00:00
} ) ;
2022-09-30 17:32:54 +00:00
2023-05-19 23:52:57 +00:00
let mut summaries = buffer . summaries_for_anchors_with_payload ::< Point , _ , _ > ( anchor_iter ) ;
2023-05-19 22:06:38 +00:00
iter ::from_fn ( move | | {
2023-05-19 23:52:57 +00:00
let ( start_point , start_base ) = summaries . next ( ) ? ;
2024-03-08 23:37:24 +00:00
let ( mut end_point , end_base ) = summaries . next ( ) ? ;
2023-05-19 22:06:38 +00:00
2024-03-08 23:37:24 +00:00
if end_point . column > 0 {
end_point . row + = 1 ;
}
2022-09-30 17:32:54 +00:00
Some ( DiffHunk {
2024-03-08 23:37:24 +00:00
associated_range : start_point . row .. end_point . row ,
2023-05-19 23:52:57 +00:00
diff_base_byte_range : start_base .. end_base ,
2024-03-08 23:37:24 +00:00
buffer_range : buffer . anchor_before ( start_point ) .. buffer . anchor_after ( end_point ) ,
buffer_id : buffer . remote_id ( ) ,
2022-09-30 17:32:54 +00:00
} )
2022-09-09 19:40:33 +00:00
} )
}
2023-05-20 01:09:47 +00:00
pub fn hunks_intersecting_range_rev < ' a > (
& ' a self ,
range : Range < Anchor > ,
buffer : & ' a BufferSnapshot ,
) -> impl ' a + Iterator < Item = DiffHunk < u32 > > {
let mut cursor = self . tree . filter ::< _ , DiffHunkSummary > ( move | summary | {
let before_start = summary . buffer_range . end . cmp ( & range . start , buffer ) . is_lt ( ) ;
let after_end = summary . buffer_range . start . cmp ( & range . end , buffer ) . is_gt ( ) ;
! before_start & & ! after_end
} ) ;
std ::iter ::from_fn ( move | | {
cursor . prev ( buffer ) ;
let hunk = cursor . item ( ) ? ;
2024-03-08 23:37:24 +00:00
let range = hunk . associated_range . to_point ( buffer ) ;
2023-05-20 01:09:47 +00:00
let end_row = if range . end . column > 0 {
range . end . row + 1
} else {
range . end . row
} ;
Some ( DiffHunk {
2024-03-08 23:37:24 +00:00
associated_range : range . start . row .. end_row ,
2023-05-20 01:09:47 +00:00
diff_base_byte_range : hunk . diff_base_byte_range . clone ( ) ,
2024-03-08 23:37:24 +00:00
buffer_range : hunk . buffer_range . clone ( ) ,
buffer_id : hunk . buffer_id ,
2023-05-20 01:09:47 +00:00
} )
} )
}
2022-10-07 16:20:54 +00:00
pub fn clear ( & mut self , buffer : & text ::BufferSnapshot ) {
self . last_buffer_version = Some ( buffer . version ( ) . clone ( ) ) ;
self . tree = SumTree ::new ( ) ;
}
2022-10-03 19:11:06 +00:00
pub async fn update ( & mut self , diff_base : & str , buffer : & text ::BufferSnapshot ) {
2022-09-14 22:44:00 +00:00
let mut tree = SumTree ::new ( ) ;
2022-09-12 19:38:44 +00:00
2022-09-14 22:44:00 +00:00
let buffer_text = buffer . as_rope ( ) . to_string ( ) ;
2024-01-21 13:55:08 +00:00
let patch = Self ::diff ( diff_base , & buffer_text ) ;
2022-09-14 22:44:00 +00:00
if let Some ( patch ) = patch {
2022-09-15 21:44:01 +00:00
let mut divergence = 0 ;
2022-09-14 22:44:00 +00:00
for hunk_index in 0 .. patch . num_hunks ( ) {
2022-09-15 21:44:01 +00:00
let hunk = Self ::process_patch_hunk ( & patch , hunk_index , buffer , & mut divergence ) ;
2022-09-14 22:44:00 +00:00
tree . push ( hunk , buffer ) ;
}
2022-09-14 00:41:38 +00:00
}
2022-09-15 23:06:45 +00:00
self . tree = tree ;
2022-09-16 18:49:24 +00:00
self . last_buffer_version = Some ( buffer . version ( ) . clone ( ) ) ;
2022-09-15 23:06:45 +00:00
}
#[ cfg(test) ]
fn hunks < ' a > ( & ' a self , text : & ' a BufferSnapshot ) -> impl ' a + Iterator < Item = DiffHunk < u32 > > {
2022-12-15 05:17:28 +00:00
let start = text . anchor_before ( Point ::new ( 0 , 0 ) ) ;
let end = text . anchor_after ( Point ::new ( u32 ::MAX , u32 ::MAX ) ) ;
2023-05-20 01:09:47 +00:00
self . hunks_intersecting_range ( start .. end , text )
2022-09-14 00:41:38 +00:00
}
fn diff < ' a > ( head : & ' a str , current : & ' a str ) -> Option < GitPatch < ' a > > {
2022-09-12 19:38:44 +00:00
let mut options = GitOptions ::default ( ) ;
options . context_lines ( 0 ) ;
2022-09-14 00:41:38 +00:00
let patch = GitPatch ::from_buffers (
head . as_bytes ( ) ,
2022-09-12 19:38:44 +00:00
None ,
2022-09-14 00:41:38 +00:00
current . as_bytes ( ) ,
2022-09-12 19:38:44 +00:00
None ,
Some ( & mut options ) ,
2022-09-14 00:41:38 +00:00
) ;
match patch {
Ok ( patch ) = > Some ( patch ) ,
2022-09-12 19:38:44 +00:00
2022-09-14 00:41:38 +00:00
Err ( err ) = > {
log ::error! ( " `GitPatch::from_buffers` failed: {} " , err ) ;
None
}
}
}
2024-03-03 16:52:58 +00:00
fn process_patch_hunk (
patch : & GitPatch < '_ > ,
2022-09-14 00:41:38 +00:00
hunk_index : usize ,
buffer : & text ::BufferSnapshot ,
2022-09-15 21:44:01 +00:00
buffer_row_divergence : & mut i64 ,
2022-09-14 00:41:38 +00:00
) -> DiffHunk < Anchor > {
2022-09-14 16:38:23 +00:00
let line_item_count = patch . num_lines_in_hunk ( hunk_index ) . unwrap ( ) ;
assert! ( line_item_count > 0 ) ;
let mut first_deletion_buffer_row : Option < u32 > = None ;
2022-09-15 20:17:17 +00:00
let mut buffer_row_range : Option < Range < u32 > > = None ;
2022-10-31 18:35:42 +00:00
let mut diff_base_byte_range : Option < Range < usize > > = None ;
2022-09-14 00:41:38 +00:00
2022-09-14 16:38:23 +00:00
for line_index in 0 .. line_item_count {
2022-09-14 00:41:38 +00:00
let line = patch . line_in_hunk ( hunk_index , line_index ) . unwrap ( ) ;
let kind = line . origin_value ( ) ;
let content_offset = line . content_offset ( ) as isize ;
2022-09-14 15:20:01 +00:00
let content_len = line . content ( ) . len ( ) as isize ;
2022-09-14 00:41:38 +00:00
2022-09-15 21:44:01 +00:00
if kind = = GitDiffLineType ::Addition {
* buffer_row_divergence + = 1 ;
let row = line . new_lineno ( ) . unwrap ( ) . saturating_sub ( 1 ) ;
2022-09-14 00:41:38 +00:00
2022-09-15 21:44:01 +00:00
match & mut buffer_row_range {
Some ( buffer_row_range ) = > buffer_row_range . end = row + 1 ,
None = > buffer_row_range = Some ( row .. row + 1 ) ,
2022-09-14 00:41:38 +00:00
}
2022-09-15 21:44:01 +00:00
}
2022-09-14 00:41:38 +00:00
2022-09-15 21:44:01 +00:00
if kind = = GitDiffLineType ::Deletion {
let end = content_offset + content_len ;
2022-09-01 21:22:12 +00:00
2022-10-31 18:35:42 +00:00
match & mut diff_base_byte_range {
2022-09-15 21:44:01 +00:00
Some ( head_byte_range ) = > head_byte_range . end = end as usize ,
2022-10-31 18:35:42 +00:00
None = > diff_base_byte_range = Some ( content_offset as usize .. end as usize ) ,
2022-09-14 00:41:38 +00:00
}
2022-09-15 21:44:01 +00:00
if first_deletion_buffer_row . is_none ( ) {
let old_row = line . old_lineno ( ) . unwrap ( ) . saturating_sub ( 1 ) ;
let row = old_row as i64 + * buffer_row_divergence ;
first_deletion_buffer_row = Some ( row as u32 ) ;
}
2022-10-13 04:42:53 +00:00
* buffer_row_divergence - = 1 ;
2022-09-14 16:38:23 +00:00
}
2022-09-14 00:41:38 +00:00
}
2022-09-01 21:22:12 +00:00
2022-09-14 00:41:38 +00:00
//unwrap_or deletion without addition
2022-09-15 21:44:01 +00:00
let buffer_row_range = buffer_row_range . unwrap_or_else ( | | {
2022-09-14 16:38:23 +00:00
//we cannot have an addition-less hunk without deletion(s) or else there would be no hunk
let row = first_deletion_buffer_row . unwrap ( ) ;
2022-09-15 20:17:17 +00:00
row .. row
2022-09-14 16:38:23 +00:00
} ) ;
2022-09-14 00:41:38 +00:00
//unwrap_or addition without deletion
2022-10-31 18:35:42 +00:00
let diff_base_byte_range = diff_base_byte_range . unwrap_or ( 0 .. 0 ) ;
2022-09-14 00:41:38 +00:00
2022-09-15 21:44:01 +00:00
let start = Point ::new ( buffer_row_range . start , 0 ) ;
let end = Point ::new ( buffer_row_range . end , 0 ) ;
2022-09-15 20:17:17 +00:00
let buffer_range = buffer . anchor_before ( start ) .. buffer . anchor_before ( end ) ;
2022-09-14 00:41:38 +00:00
DiffHunk {
2024-03-08 23:37:24 +00:00
associated_range : buffer_range . clone ( ) ,
2022-09-15 20:17:17 +00:00
buffer_range ,
2022-10-31 18:35:42 +00:00
diff_base_byte_range ,
2024-03-08 23:37:24 +00:00
buffer_id : buffer . remote_id ( ) ,
2022-09-01 21:22:12 +00:00
}
}
}
2022-09-09 19:40:33 +00:00
2022-09-30 19:50:55 +00:00
/// Range (crossing new lines), old, new
#[ cfg(any(test, feature = " test-support " )) ]
#[ track_caller ]
pub fn assert_hunks < Iter > (
diff_hunks : Iter ,
buffer : & BufferSnapshot ,
2022-10-03 19:11:06 +00:00
diff_base : & str ,
2022-09-30 19:50:55 +00:00
expected_hunks : & [ ( Range < u32 > , & str , & str ) ] ,
) where
Iter : Iterator < Item = DiffHunk < u32 > > ,
{
let actual_hunks = diff_hunks
. map ( | hunk | {
(
2024-03-08 23:37:24 +00:00
hunk . associated_range . clone ( ) ,
2022-10-24 20:33:43 +00:00
& diff_base [ hunk . diff_base_byte_range ] ,
2022-09-30 19:50:55 +00:00
buffer
. text_for_range (
2024-03-08 23:37:24 +00:00
Point ::new ( hunk . associated_range . start , 0 )
.. Point ::new ( hunk . associated_range . end , 0 ) ,
2022-09-30 19:50:55 +00:00
)
. collect ::< String > ( ) ,
)
} )
. collect ::< Vec < _ > > ( ) ;
let expected_hunks : Vec < _ > = expected_hunks
. iter ( )
. map ( | ( r , s , h ) | ( r . clone ( ) , * s , h . to_string ( ) ) )
. collect ( ) ;
assert_eq! ( actual_hunks , expected_hunks ) ;
}
2022-09-09 19:40:33 +00:00
#[ cfg(test) ]
mod tests {
2023-05-19 22:06:38 +00:00
use std ::assert_eq ;
2022-09-09 19:40:33 +00:00
use super ::* ;
2024-01-28 20:05:08 +00:00
use text ::{ Buffer , BufferId } ;
2022-09-09 19:40:33 +00:00
use unindent ::Unindent as _ ;
2022-09-28 15:43:33 +00:00
#[ test ]
2022-09-09 19:40:33 +00:00
fn test_buffer_diff_simple ( ) {
2022-10-03 19:11:06 +00:00
let diff_base = "
2022-09-09 19:40:33 +00:00
one
two
three
"
. unindent ( ) ;
let buffer_text = "
one
2022-09-30 17:32:54 +00:00
HELLO
2022-09-09 19:40:33 +00:00
three
"
. unindent ( ) ;
2024-01-28 20:05:08 +00:00
let mut buffer = Buffer ::new ( 0 , BufferId ::new ( 1 ) . unwrap ( ) , buffer_text ) ;
2022-09-16 18:49:24 +00:00
let mut diff = BufferDiff ::new ( ) ;
2022-10-03 19:11:06 +00:00
smol ::block_on ( diff . update ( & diff_base , & buffer ) ) ;
2022-09-30 17:32:54 +00:00
assert_hunks (
2022-09-30 19:50:55 +00:00
diff . hunks ( & buffer ) ,
2022-09-30 17:32:54 +00:00
& buffer ,
2022-10-03 19:11:06 +00:00
& diff_base ,
2022-09-30 17:32:54 +00:00
& [ ( 1 .. 2 , " two \n " , " HELLO \n " ) ] ,
) ;
2022-09-09 19:40:33 +00:00
buffer . edit ( [ ( 0 .. 0 , " point five \n " ) ] ) ;
2022-10-03 19:11:06 +00:00
smol ::block_on ( diff . update ( & diff_base , & buffer ) ) ;
2022-09-30 17:32:54 +00:00
assert_hunks (
2022-09-30 19:50:55 +00:00
diff . hunks ( & buffer ) ,
2022-09-30 17:32:54 +00:00
& buffer ,
2022-10-03 19:11:06 +00:00
& diff_base ,
2022-09-30 17:32:54 +00:00
& [ ( 0 .. 1 , " " , " point five \n " ) , ( 2 .. 3 , " two \n " , " HELLO \n " ) ] ,
) ;
2022-10-07 16:20:54 +00:00
diff . clear ( & buffer ) ;
assert_hunks ( diff . hunks ( & buffer ) , & buffer , & diff_base , & [ ] ) ;
2022-09-30 17:32:54 +00:00
}
#[ test ]
fn test_buffer_diff_range ( ) {
2022-10-03 19:11:06 +00:00
let diff_base = "
2022-09-30 17:32:54 +00:00
one
two
three
four
five
six
seven
eight
nine
ten
"
. unindent ( ) ;
let buffer_text = "
A
one
B
two
C
three
HELLO
four
five
SIXTEEN
seven
eight
WORLD
nine
ten
"
. unindent ( ) ;
2024-01-28 20:05:08 +00:00
let buffer = Buffer ::new ( 0 , BufferId ::new ( 1 ) . unwrap ( ) , buffer_text ) ;
2022-09-30 17:32:54 +00:00
let mut diff = BufferDiff ::new ( ) ;
2022-10-03 19:11:06 +00:00
smol ::block_on ( diff . update ( & diff_base , & buffer ) ) ;
2022-09-30 17:32:54 +00:00
assert_eq! ( diff . hunks ( & buffer ) . count ( ) , 8 ) ;
assert_hunks (
2023-05-20 01:09:47 +00:00
diff . hunks_in_row_range ( 7 .. 12 , & buffer ) ,
2022-09-30 17:32:54 +00:00
& buffer ,
2022-10-03 19:11:06 +00:00
& diff_base ,
2022-09-30 17:32:54 +00:00
& [
( 6 .. 7 , " " , " HELLO \n " ) ,
( 9 .. 10 , " six \n " , " SIXTEEN \n " ) ,
( 12 .. 13 , " " , " WORLD \n " ) ,
] ,
) ;
2022-09-09 21:32:19 +00:00
}
2022-09-09 19:40:33 +00:00
}