33// SPDX-License-Identifier: GPL-3.0-or-later
44
55use crate :: dialogs:: alert;
6- use crate :: git:: commit;
6+ use crate :: git:: { blame , commit, stash :: StashNum } ;
77use crate :: status_view:: context:: StatusRenderContext ;
88use crate :: status_view:: {
99 render:: ViewContainer , stage_view:: StageView , view:: View , CursorPosition ,
1010 Label as TextViewLabel ,
1111} ;
12- use crate :: { ApplyOp , CurrentWindow , Event , StageOp } ;
12+ use crate :: { ApplyOp , BlameLine , CurrentWindow , Event , HunkLineNo , StageOp } ;
1313use async_channel:: Sender ;
1414use git2:: Oid ;
1515
@@ -23,7 +23,11 @@ use log::{debug, info, trace};
2323
2424use std:: path:: PathBuf ;
2525
26- pub fn headerbar_factory ( sender : Sender < Event > , oid : Oid , stash_num : Option < usize > ) -> HeaderBar {
26+ pub fn headerbar_factory (
27+ sender : Sender < Event > ,
28+ oid : Oid ,
29+ stash_num : Option < StashNum > ,
30+ ) -> HeaderBar {
2731 let hb = HeaderBar :: builder ( ) . build ( ) ;
2832 let ( btn_tooltip, title) = if stash_num. is_some ( ) {
2933 ( "Apply stash" , "Stash" )
@@ -178,6 +182,7 @@ impl commit::CommitDiff {
178182 ctx : & mut StatusRenderContext < ' a > ,
179183 labels : & ' a mut [ TextViewLabel ] ,
180184 body_label : & ' a mut MultiLineLabel ,
185+ blame_line : Option < BlameLine > ,
181186 ) {
182187 let buffer = txt. buffer ( ) ;
183188 let mut iter = buffer. iter_at_offset ( 0 ) ;
@@ -193,14 +198,50 @@ impl commit::CommitDiff {
193198 // ??? why it was commented out?
194199 // body_label.update_content(&self.message, txt.calc_max_char_width());
195200 body_label. render ( & buffer, & mut iter, ctx) ;
196-
201+ let mut found_line_index : Option < ( usize , usize , usize ) > = None ;
197202 if !self . diff . files . is_empty ( ) {
198- self . diff . files [ 0 ] . view . make_current ( true ) ;
203+ if let Some ( blame_line) = blame_line {
204+ for ( f, file) in self . diff . files . iter ( ) . enumerate ( ) {
205+ if file. path == blame_line. file_path {
206+ file. view . expand ( true ) ;
207+ for ( h, hunk) in file. hunks . iter ( ) . enumerate ( ) {
208+ let mut found = false ;
209+ if found_line_index. is_none ( ) {
210+ for ( l, line) in hunk. lines . iter ( ) . enumerate ( ) {
211+ if let Some ( found_line_no) = line. new_line_no {
212+ if found_line_no >= blame_line. hunk_start
213+ && line. content ( hunk) == blame_line. content
214+ {
215+ line. view . make_current ( true ) ;
216+ found = true ;
217+ found_line_index. replace ( ( f, h, l) ) ;
218+ break ;
219+ }
220+ }
221+ }
222+ }
223+ if !found {
224+ hunk. view . expand ( false ) ;
225+ }
226+ }
227+ if found_line_index. is_some ( ) {
228+ break ;
229+ }
230+ }
231+ }
232+ } else {
233+ self . diff . files [ 0 ] . view . make_current ( true ) ;
234+ }
199235 }
200236
201237 self . diff . render ( & buffer, & mut iter, ctx) ;
202-
203- if !self . diff . files . is_empty ( ) {
238+ if let Some ( ( f, h, l) ) = found_line_index {
239+ let line_no = self . diff . files [ f] . hunks [ h] . lines [ l] . view . line_no . get ( ) ;
240+ let buffer = txt. buffer ( ) ;
241+ iter = buffer. iter_at_line ( line_no) . unwrap ( ) ;
242+ buffer. place_cursor ( & iter) ;
243+ txt. scroll_to_iter ( & mut iter, 0.0 , false , 0.0 , 0.0 ) ;
244+ } else if !self . diff . files . is_empty ( ) {
204245 let buffer = txt. buffer ( ) ;
205246 iter = buffer
206247 . iter_at_line ( self . diff . files [ 0 ] . view . line_no . get ( ) )
@@ -215,7 +256,8 @@ impl commit::CommitDiff {
215256pub fn show_commit_window (
216257 repo_path : PathBuf ,
217258 oid : Oid ,
218- stash_num : Option < usize > ,
259+ stash_num : Option < StashNum > ,
260+ blame_line : Option < BlameLine > ,
219261 app_window : CurrentWindow ,
220262 main_sender : Sender < Event > , // i need that to trigger revert and cherry-pick.
221263) -> Window {
@@ -228,7 +270,7 @@ pub fn show_commit_window(
228270 let mut builder = Window :: builder ( )
229271 . default_width ( MAX_WIDTH )
230272 . default_height ( 960 ) ;
231- match app_window {
273+ match app_window. clone ( ) {
232274 CurrentWindow :: Window ( w) => {
233275 builder = builder. transient_for ( & w) ;
234276 }
@@ -270,14 +312,14 @@ pub fn show_commit_window(
270312 let mut body_label: Option < MultiLineLabel > = None ;
271313
272314 let path = repo_path. clone ( ) ;
273-
274315 let mut cursor_position: CursorPosition = CursorPosition :: None ;
275316
276317 glib:: spawn_future_local ( {
277318 let window = window. clone ( ) ;
278319 let sender = sender. clone ( ) ;
320+ let path = path. clone ( ) ;
279321 async move {
280- let diff = gio:: spawn_blocking ( move || commit:: get_commit_diff ( path, oid) )
322+ let diff = gio:: spawn_blocking ( move || commit:: get_commit_diff ( path. clone ( ) , oid) )
281323 . await
282324 . unwrap_or_else ( |e| {
283325 alert ( format ! ( "{:?}" , e) ) . present ( Some ( & window) ) ;
@@ -300,6 +342,7 @@ pub fn show_commit_window(
300342 ] ;
301343
302344 glib:: spawn_future_local ( {
345+ let window = window. clone ( ) ;
303346 async move {
304347 while let Ok ( event) = receiver. recv ( ) . await {
305348 let mut ctx = crate :: StatusRenderContext :: new ( & txt) ;
@@ -324,6 +367,7 @@ pub fn show_commit_window(
324367 & mut ctx,
325368 & mut labels,
326369 body_label. as_mut ( ) . unwrap ( ) ,
370+ blame_line. clone ( ) ,
327371 ) ;
328372 cursor_position = CursorPosition :: from_context ( & ctx) ;
329373 // it should be called after cursor in ViewContainer
@@ -356,12 +400,6 @@ pub fn show_commit_window(
356400 Event :: TextViewResize ( w) => {
357401 info ! ( "TextViewResize {} {:?}" , w, ctx) ;
358402 }
359- Event :: TextCharVisibleWidth ( w) => {
360- info ! ( "TextCharVisibleWidth {}" , w) ;
361- if let Some ( d) = & mut diff {
362- d. render ( & txt, & mut ctx, & mut labels, body_label. as_mut ( ) . unwrap ( ) ) ;
363- }
364- }
365403 Event :: Debug => {
366404 let buffer = txt. buffer ( ) ;
367405 let pos = buffer. cursor_position ( ) ;
@@ -410,6 +448,61 @@ pub fn show_commit_window(
410448 . expect ( "cant send through channel" ) ;
411449 }
412450 }
451+ Event :: Blame => {
452+ let mut line_no: Option < HunkLineNo > = None ;
453+ let mut ofile_path: Option < PathBuf > = None ;
454+ let mut oline_content: Option < String > = None ;
455+ if let CursorPosition :: CursorLine ( _, file_idx, hunk_idx, line_idx) =
456+ cursor_position
457+ {
458+ if let Some ( diff) = & diff {
459+ let file = & diff. diff . files [ file_idx] ;
460+ let hunk = & file. hunks [ hunk_idx] ;
461+ ofile_path. replace ( file. path . clone ( ) ) ;
462+ let line = & hunk. lines [ line_idx] ;
463+ oline_content. replace ( line. content ( hunk) . to_string ( ) ) ;
464+ // IMPORTANT - here we use new_line_no
465+ line_no = line. new_line_no ;
466+ }
467+ }
468+ if let Some ( line_no) = line_no {
469+ glib:: spawn_future_local ( {
470+ let path = path. clone ( ) ;
471+ let sender = main_sender. clone ( ) ;
472+ let file_path = ofile_path. clone ( ) . unwrap ( ) ;
473+ let window = window. clone ( ) ;
474+ async move {
475+ let ooid = gio:: spawn_blocking ( {
476+ let file_path = file_path. clone ( ) ;
477+ move || blame ( path, file_path. clone ( ) , line_no, Some ( oid) )
478+ } )
479+ . await
480+ . unwrap ( ) ;
481+ match ooid {
482+ Ok ( ( blame_oid, hunk_line_start) ) => {
483+ if blame_oid == oid {
484+ alert ( format ! ( "This is the same commit {:?}" , oid) )
485+ . present ( Some ( & window) ) ;
486+ return ;
487+ }
488+ sender
489+ . send_blocking ( crate :: Event :: ShowOid (
490+ blame_oid,
491+ None ,
492+ Some ( BlameLine {
493+ file_path,
494+ hunk_start : hunk_line_start,
495+ content : oline_content. unwrap ( ) ,
496+ } ) ,
497+ ) )
498+ . expect ( "Could not send through channel" ) ;
499+ }
500+ Err ( e) => alert ( e) . present ( Some ( & window) ) ,
501+ }
502+ }
503+ } ) ;
504+ }
505+ }
413506 _ => {
414507 trace ! ( "unhandled event in commit_view {:?}" , event) ;
415508 }
0 commit comments