]> asedeno.scripts.mit.edu Git - git.git/blob - lib/blame.tcl
git-gui: Remove the loaded column from the blame viewer
[git.git] / lib / blame.tcl
1 # git-gui blame viewer
2 # Copyright (C) 2006, 2007 Shawn Pearce
3
4 class blame {
5
6 image create photo ::blame::img_back_arrow -data {R0lGODlhGAAYAIUAAPwCBEzKXFTSZIz+nGzmhGzqfGTidIT+nEzGXHTqhGzmfGzifFzadETCVES+VARWDFzWbHzyjAReDGTadFTOZDSyRDyyTCymPARaFGTedFzSbDy2TCyqRCyqPARaDAyCHES6VDy6VCyiPAR6HCSeNByWLARyFARiDARqFGTifARiFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAAALAAAAAAYABgAAAajQIBwSCwaj8ikcsk0BppJwRPqHEypQwHBis0WDAdEFyBIKBaMAKLBdjQeSkFBYTBAIvgEoS6JmhUTEwIUDQ4VFhcMGEhyCgoZExoUaxsWHB0THkgfAXUGAhoBDSAVFR0XBnCbDRmgog0hpSIiDJpJIyEQhBUcJCIlwA22SSYVogknEg8eD82qSigdDSknY0IqJQXPYxIl1dZCGNvWw+Dm510GQQAh/mhDcmVhdGVkIGJ5IEJNUFRvR0lGIFBybyB2ZXJzaW9uIDIuNQ0KqSBEZXZlbENvciAxOTk3LDE5OTguIEFsbCByaWdodHMgcmVzZXJ2ZWQuDQpodHRwOi8vd3d3LmRldmVsY29yLmNvbQA7}
7
8 field commit    ; # input commit to blame
9 field path      ; # input filename to view in $commit
10 field history {}; # viewer history: {commit path}
11
12 field w          ; # top window in this viewer
13 field w_back     ; # our back button
14 field w_path     ; # label showing the current file path
15 field w_line     ; # text column: all line numbers
16 field w_cgrp     ; # text column: abbreviated commit SHA-1s
17 field w_file     ; # text column: actual file data
18 field w_cmit     ; # pane showing commit message
19 field status     ; # text variable bound to status bar
20 field old_height ; # last known height of $w.file_pane
21
22 field current_fd       {} ; # background process running
23 field highlight_line   -1 ; # current line selected
24 field highlight_commit {} ; # sha1 of commit selected
25
26 field total_lines       0  ; # total length of file
27 field blame_lines       0  ; # number of lines computed
28 field commit_count      0  ; # number of commits in $commit_list
29 field commit_list      {}  ; # list of commit sha1 in receipt order
30 field order                ; # array commit -> receipt order
31 field header               ; # array commit,key -> header field
32 field line_commit          ; # array line -> sha1 commit
33 field line_file            ; # array line -> file name
34
35 field r_commit      ; # commit currently being parsed
36 field r_orig_line   ; # original line number
37 field r_final_line  ; # final line number
38 field r_line_count  ; # lines in this region
39
40 field tooltip_wm     {} ; # Current tooltip toplevel, if open
41 field tooltip_timer  {} ; # Current timer event for our tooltip
42 field tooltip_commit {} ; # Commit in tooltip
43 field tooltip_text   {} ; # Text in current tooltip
44
45 variable active_color #98e1a0
46 variable group_colors {
47         #cbcbcb
48         #e1e1e1
49 }
50
51 constructor new {i_commit i_path} {
52         variable active_color
53         global cursor_ptr
54
55         set commit $i_commit
56         set path   $i_path
57
58         make_toplevel top w
59         wm title $top "[appname] ([reponame]): File Viewer"
60
61         frame $w.header -background orange
62         label $w.header.commit_l \
63                 -text {Commit:} \
64                 -background orange \
65                 -anchor w \
66                 -justify left
67         set w_back $w.header.commit_b
68         label $w_back \
69                 -image ::blame::img_back_arrow \
70                 -borderwidth 0 \
71                 -relief flat \
72                 -state disabled \
73                 -background orange \
74                 -activebackground orange
75         bind $w_back <Button-1> "
76                 if {\[$w_back cget -state\] eq {normal}} {
77                         [cb _history_menu]
78                 }
79                 "
80         label $w.header.commit \
81                 -textvariable @commit \
82                 -background orange \
83                 -anchor w \
84                 -justify left
85         label $w.header.path_l \
86                 -text {File:} \
87                 -background orange \
88                 -anchor w \
89                 -justify left
90         set w_path $w.header.path
91         label $w_path \
92                 -background orange \
93                 -anchor w \
94                 -justify left
95         pack $w.header.commit_l -side left
96         pack $w_back -side left
97         pack $w.header.commit -side left
98         pack $w_path -fill x -side right
99         pack $w.header.path_l -side right
100
101         panedwindow $w.file_pane -orient vertical
102         frame $w.file_pane.out
103         frame $w.file_pane.cm
104         $w.file_pane add $w.file_pane.out \
105                 -sticky nsew \
106                 -minsize 100 \
107                 -height 100 \
108                 -width 100
109         $w.file_pane add $w.file_pane.cm \
110                 -sticky nsew \
111                 -minsize 25 \
112                 -height 25 \
113                 -width 100
114
115         set w_line $w.file_pane.out.linenumber_t
116         text $w_line \
117                 -background white -borderwidth 0 \
118                 -state disabled \
119                 -wrap none \
120                 -height 40 \
121                 -width 5 \
122                 -font font_diff
123         $w_line tag conf linenumber -justify right
124
125         set w_cgrp $w.file_pane.out.commit_t
126         text $w_cgrp \
127                 -background white -borderwidth 0 \
128                 -state disabled \
129                 -wrap none \
130                 -height 40 \
131                 -width 4 \
132                 -font font_diff
133         $w_cgrp tag conf curr_commit
134         $w_cgrp tag conf prior_commit \
135                 -foreground blue \
136                 -underline 1
137         $w_cgrp tag bind prior_commit \
138                 <Button-1> \
139                 "[cb _load_commit @%x,%y];break"
140
141         set w_file $w.file_pane.out.file_t
142         text $w_file \
143                 -background white -borderwidth 0 \
144                 -state disabled \
145                 -wrap none \
146                 -height 40 \
147                 -width 80 \
148                 -xscrollcommand [list $w.file_pane.out.sbx set] \
149                 -font font_diff
150
151         scrollbar $w.file_pane.out.sbx \
152                 -orient h \
153                 -command [list $w_file xview]
154         scrollbar $w.file_pane.out.sby \
155                 -orient v \
156                 -command [list scrollbar2many [list \
157                 $w_line \
158                 $w_cgrp \
159                 $w_file \
160                 ] yview]
161         grid \
162                 $w_cgrp \
163                 $w_line \
164                 $w_file \
165                 $w.file_pane.out.sby \
166                 -sticky nsew
167         grid conf $w.file_pane.out.sbx -column 2 -sticky we
168         grid columnconfigure $w.file_pane.out 2 -weight 1
169         grid rowconfigure $w.file_pane.out 0 -weight 1
170
171         set w_cmit $w.file_pane.cm.t
172         text $w_cmit \
173                 -background white -borderwidth 0 \
174                 -state disabled \
175                 -wrap none \
176                 -height 10 \
177                 -width 80 \
178                 -xscrollcommand [list $w.file_pane.cm.sbx set] \
179                 -yscrollcommand [list $w.file_pane.cm.sby set] \
180                 -font font_diff
181         $w_cmit tag conf header_key \
182                 -tabs {3c} \
183                 -background $active_color \
184                 -font font_uibold
185         $w_cmit tag conf header_val \
186                 -background $active_color \
187                 -font font_ui
188         $w_cmit tag raise sel
189         scrollbar $w.file_pane.cm.sbx \
190                 -orient h \
191                 -command [list $w_cmit xview]
192         scrollbar $w.file_pane.cm.sby \
193                 -orient v \
194                 -command [list $w_cmit yview]
195         pack $w.file_pane.cm.sby -side right -fill y
196         pack $w.file_pane.cm.sbx -side bottom -fill x
197         pack $w_cmit -expand 1 -fill both
198
199         frame $w.status \
200                 -borderwidth 1 \
201                 -relief sunken
202         label $w.status.l \
203                 -textvariable @status \
204                 -anchor w \
205                 -justify left
206         pack $w.status.l -side left
207
208         menu $w.ctxm -tearoff 0
209         $w.ctxm add command \
210                 -label "Copy Commit" \
211                 -command [cb _copycommit]
212
213         foreach i [list \
214                 $w_cgrp \
215                 $w_line \
216                 $w_file] {
217                 $i conf -cursor $cursor_ptr
218                 $i conf -yscrollcommand \
219                         [list many2scrollbar [list \
220                         $w_cgrp \
221                         $w_line \
222                         $w_file \
223                         ] yview $w.file_pane.out.sby]
224                 bind $i <Button-1> "
225                         [cb _hide_tooltip]
226                         [cb _click $i @%x,%y]
227                         focus $i
228                 "
229                 bind $i <Any-Motion>  [cb _show_tooltip $i @%x,%y]
230                 bind $i <Any-Enter>   [cb _hide_tooltip]
231                 bind $i <Any-Leave>   [cb _hide_tooltip]
232                 bind_button3 $i "
233                         [cb _hide_tooltip]
234                         set cursorX %x
235                         set cursorY %y
236                         set cursorW %W
237                         tk_popup $w.ctxm %X %Y
238                 "
239         }
240
241         foreach i [list \
242                 $w_cgrp \
243                 $w_line \
244                 $w_file \
245                 $w_cmit] {
246                 bind $i <Key-Up>        {catch {%W yview scroll -1 units};break}
247                 bind $i <Key-Down>      {catch {%W yview scroll  1 units};break}
248                 bind $i <Key-Left>      {catch {%W xview scroll -1 units};break}
249                 bind $i <Key-Right>     {catch {%W xview scroll  1 units};break}
250                 bind $i <Key-k>         {catch {%W yview scroll -1 units};break}
251                 bind $i <Key-j>         {catch {%W yview scroll  1 units};break}
252                 bind $i <Key-h>         {catch {%W xview scroll -1 units};break}
253                 bind $i <Key-l>         {catch {%W xview scroll  1 units};break}
254                 bind $i <Control-Key-b> {catch {%W yview scroll -1 pages};break}
255                 bind $i <Control-Key-f> {catch {%W yview scroll  1 pages};break}
256         }
257
258         bind $w_cmit <Button-1> [list focus $w_cmit]
259         bind $top <Visibility> [list focus $top]
260         bind $w_file <Destroy> [list delete_this $this]
261
262         grid configure $w.header -sticky ew
263         grid configure $w.file_pane -sticky nsew
264         grid configure $w.status -sticky ew
265         grid columnconfigure $top 0 -weight 1
266         grid rowconfigure $top 0 -weight 0
267         grid rowconfigure $top 1 -weight 1
268         grid rowconfigure $top 2 -weight 0
269
270         set req_w [winfo reqwidth  $top]
271         set req_h [winfo reqheight $top]
272         if {$req_w < 600} {set req_w 600}
273         if {$req_h < 400} {set req_h 400}
274         set g "${req_w}x${req_h}"
275         wm geometry $top $g
276         update
277
278         set old_height [winfo height $w.file_pane]
279         $w.file_pane sash place 0 \
280                 [lindex [$w.file_pane sash coord 0] 0] \
281                 [expr {int($old_height * 0.70)}]
282         bind $w.file_pane <Configure> \
283         "if {{$w.file_pane} eq {%W}} {[cb _resize %h]}"
284
285         _load $this
286 }
287
288 method _load {} {
289         _hide_tooltip $this
290
291         if {$total_lines != 0 || $current_fd ne {}} {
292                 if {$current_fd ne {}} {
293                         catch {close $current_fd}
294                         set current_fd {}
295                 }
296
297                 set highlight_line -1
298                 set highlight_commit {}
299                 set total_lines 0
300                 set blame_lines 0
301                 set commit_count 0
302                 set commit_list {}
303                 array unset order
304                 array unset line_commit
305                 array unset line_file
306
307                 $w_cgrp conf -state normal
308                 $w_line conf -state normal
309                 $w_file conf -state normal
310
311                 $w_cgrp delete 0.0 end
312                 $w_line delete 0.0 end
313                 $w_file delete 0.0 end
314
315                 $w_cgrp conf -state disabled
316                 $w_line conf -state disabled
317                 $w_file conf -state disabled
318         }
319
320         if {[winfo exists $w.status.c]} {
321                 $w.status.c coords bar 0 0 0 20
322         } else {
323                 canvas $w.status.c \
324                         -width 100 \
325                         -height [expr {int([winfo reqheight $w.status.l] * 0.6)}] \
326                         -borderwidth 1 \
327                         -relief groove \
328                         -highlightt 0
329                 $w.status.c create rectangle 0 0 0 20 -tags bar -fill navy
330                 pack $w.status.c -side right
331         }
332
333         if {$history eq {}} {
334                 $w_back conf -state disabled
335         } else {
336                 $w_back conf -state normal
337         }
338         lappend history [list $commit $path]
339
340         set status "Loading $commit:[escape_path $path]..."
341         $w_path conf -text [escape_path $path]
342         if {$commit eq {}} {
343                 set fd [open $path r]
344         } else {
345                 set cmd [list git cat-file blob "$commit:$path"]
346                 set fd [open "| $cmd" r]
347         }
348         fconfigure $fd -blocking 0 -translation lf -encoding binary
349         fileevent $fd readable [cb _read_file $fd]
350         set current_fd $fd
351 }
352
353 method _history_menu {} {
354         set m $w.backmenu
355         if {[winfo exists $m]} {
356                 $m delete 0 end
357         } else {
358                 menu $m -tearoff 0
359         }
360
361         for {set i [expr {[llength $history] - 2}]
362                 } {$i >= 0} {incr i -1} {
363                 set e [lindex $history $i]
364                 set c [lindex $e 0]
365                 set f [lindex $e 1]
366
367                 if {[regexp {^[0-9a-f]{40}$} $c]} {
368                         set t [string range $c 0 8]...
369                 } else {
370                         set t $c
371                 }
372                 if {![catch {set summary $header($c,summary)}]} {
373                         append t " $summary"
374                         if {[string length $t] > 70} {
375                                 set t [string range $t 0 66]...
376                         }
377                 }
378
379                 $m add command -label $t -command [cb _goback $i $c $f]
380         }
381         set X [winfo rootx $w_back]
382         set Y [expr {[winfo rooty $w_back] + [winfo height $w_back]}]
383         tk_popup $m $X $Y
384 }
385
386 method _goback {i c f} {
387         set history [lrange $history 0 [expr {$i - 1}]]
388         set commit $c
389         set path $f
390         _load $this
391 }
392
393 method _read_file {fd} {
394         if {$fd ne $current_fd} {
395                 catch {close $fd}
396                 return
397         }
398
399         $w_cgrp conf -state normal
400         $w_line conf -state normal
401         $w_file conf -state normal
402         while {[gets $fd line] >= 0} {
403                 regsub "\r\$" $line {} line
404                 incr total_lines
405
406                 if {$total_lines > 1} {
407                         $w_cgrp insert end "\n"
408                         $w_line insert end "\n"
409                         $w_file insert end "\n"
410                 }
411
412                 $w_line insert end "$total_lines" linenumber
413                 $w_file insert end "$line"
414         }
415         $w_cgrp conf -state disabled
416         $w_line conf -state disabled
417         $w_file conf -state disabled
418
419         if {[eof $fd]} {
420                 close $fd
421                 _status $this
422                 set cmd {nice git blame -M -C --incremental}
423                 if {$commit eq {}} {
424                         lappend cmd --contents $path
425                 } else {
426                         lappend cmd $commit
427                 }
428                 lappend cmd -- $path
429                 set fd [open "| $cmd" r]
430                 fconfigure $fd -blocking 0 -translation lf -encoding binary
431                 fileevent $fd readable [cb _read_blame $fd]
432                 set current_fd $fd
433         }
434 } ifdeleted { catch {close $fd} }
435
436 method _read_blame {fd} {
437         variable group_colors
438
439         if {$fd ne $current_fd} {
440                 catch {close $fd}
441                 return
442         }
443
444         $w_cgrp conf -state normal
445         while {[gets $fd line] >= 0} {
446                 if {[regexp {^([a-z0-9]{40}) (\d+) (\d+) (\d+)$} $line line \
447                         cmit original_line final_line line_count]} {
448                         set r_commit     $cmit
449                         set r_orig_line  $original_line
450                         set r_final_line $final_line
451                         set r_line_count $line_count
452
453                         if {[catch {set g $order($cmit)}]} {
454                                 set bg [lindex $group_colors 0]
455                                 set group_colors [lrange $group_colors 1 end]
456                                 lappend group_colors $bg
457
458                                 $w_cgrp tag conf g$cmit -background $bg
459                                 $w_line tag conf g$cmit -background $bg
460                                 $w_file tag conf g$cmit -background $bg
461
462                                 set order($cmit) $commit_count
463                                 incr commit_count
464                                 lappend commit_list $cmit
465                         }
466                 } elseif {[string match {filename *} $line]} {
467                         set file [string range $line 9 end]
468                         set n    $r_line_count
469                         set lno  $r_final_line
470                         set cmit $r_commit
471
472                         if {[regexp {^0{40}$} $cmit]} {
473                                 set commit_abbr work
474                                 set commit_type curr_commit
475                         } elseif {$cmit eq $commit} {
476                                 set commit_abbr this
477                                 set commit_type curr_commit
478                         } else {
479                                 set commit_type prior_commit
480                                 set commit_abbr [string range $cmit 0 4]
481                         }
482
483                         set author_abbr {}
484                         set a_name {}
485                         catch {set a_name $header($cmit,author)}
486                         while {$a_name ne {}} {
487                                 if {![regexp {^([[:upper:]])} $a_name _a]} break
488                                 append author_abbr $_a
489                                 unset _a
490                                 if {![regsub \
491                                         {^[[:upper:]][^\s]*\s+} \
492                                         $a_name {} a_name ]} break
493                         }
494                         if {$author_abbr eq {}} {
495                                 set author_abbr { |}
496                         } else {
497                                 set author_abbr [string range $author_abbr 0 3]
498                                 while {[string length $author_abbr] < 4} {
499                                         set author_abbr " $author_abbr"
500                                 }
501                         }
502                         unset a_name
503
504                         set first_lno $lno
505                         while {
506                            ![catch {set ncmit $line_commit([expr {$first_lno - 1}])}]
507                         && ![catch {set nfile $line_file([expr {$first_lno - 1}])}]
508                         && $ncmit eq $cmit
509                         && $nfile eq $file
510                         } {
511                                 incr first_lno -1
512                         }
513
514                         while {$n > 0} {
515                                 set lno_e "$lno.0 lineend + 1c"
516                                 if {![catch {set g g$line_commit($lno)}]} {
517                                         $w_cgrp tag remove g$g $lno.0 $lno_e
518                                         $w_line tag remove g$g $lno.0 $lno_e
519                                         $w_file tag remove g$g $lno.0 $lno_e
520
521                                         $w_cgrp tag remove a$g $lno.0 $lno_e
522                                         $w_line tag remove a$g $lno.0 $lno_e
523                                         $w_file tag remove a$g $lno.0 $lno_e
524                                 }
525
526                                 set line_commit($lno) $cmit
527                                 set line_file($lno)   $file
528
529                                 $w_cgrp delete $lno.0 "$lno.0 lineend"
530                                 if {$lno == $first_lno} {
531                                         $w_cgrp insert $lno.0 $commit_abbr $commit_type
532                                 } elseif {$lno == [expr {$first_lno + 1}]} {
533                                         $w_cgrp insert $lno.0 $author_abbr
534                                 } else {
535                                         $w_cgrp insert $lno.0 { |}
536                                 }
537
538                                 $w_cgrp tag add g$cmit $lno.0 $lno_e
539                                 $w_line tag add g$cmit $lno.0 $lno_e
540                                 $w_file tag add g$cmit $lno.0 $lno_e
541
542                                 $w_cgrp tag add a$cmit $lno.0 $lno_e
543                                 $w_line tag add a$cmit $lno.0 $lno_e
544                                 $w_file tag add a$cmit $lno.0 $lno_e
545
546                                 if {$highlight_line == -1} {
547                                         if {[lindex [$w_file yview] 0] == 0} {
548                                                 $w_file see $lno.0
549                                                 _showcommit $this $lno
550                                         }
551                                 } elseif {$highlight_line == $lno} {
552                                         _showcommit $this $lno
553                                 }
554
555                                 incr n -1
556                                 incr lno
557                                 incr blame_lines
558                         }
559
560                         while {
561                            ![catch {set ncmit $line_commit($lno)}]
562                         && ![catch {set nfile $line_file($lno)}]
563                         && $ncmit eq $cmit
564                         && $nfile eq $file
565                         } {
566                                 $w_cgrp delete $lno.0 "$lno.0 lineend"
567
568                                 if {$lno == $first_lno} {
569                                         $w_cgrp insert $lno.0 $commit_abbr $commit_type
570                                 } elseif {$lno == [expr {$first_lno + 1}]} {
571                                         $w_cgrp insert $lno.0 $author_abbr
572                                 } else {
573                                         $w_cgrp insert $lno.0 { |}
574                                 }
575                                 incr lno
576                         }
577
578                 } elseif {[regexp {^([a-z-]+) (.*)$} $line line key data]} {
579                         set header($r_commit,$key) $data
580                 }
581         }
582         $w_cgrp conf -state disabled
583
584         if {[eof $fd]} {
585                 close $fd
586                 set current_fd {}
587                 set status {Annotation complete.}
588                 destroy $w.status.c
589         } else {
590                 _status $this
591         }
592 } ifdeleted { catch {close $fd} }
593
594 method _status {} {
595         set have  $blame_lines
596         set total $total_lines
597         set pdone 0
598         if {$total} {set pdone [expr {100 * $have / $total}]}
599
600         set status [format \
601                 "Loading annotations... %i of %i lines annotated (%2i%%)" \
602                 $have $total $pdone]
603         $w.status.c coords bar 0 0 $pdone 20
604 }
605
606 method _click {cur_w pos} {
607         set lno [lindex [split [$cur_w index $pos] .] 0]
608         if {$lno eq {}} return
609         _showcommit $this $lno
610 }
611
612 method _load_commit {pos} {
613         set lno [lindex [split [$w_cgrp index $pos] .] 0]
614         if {[catch {set cmit $line_commit($lno)}]} return
615         if {[catch {set file $line_file($lno)  }]} return
616
617         set commit $cmit
618         set path $file
619         _load $this
620 }
621
622 method _showcommit {lno} {
623         global repo_config
624         variable active_color
625
626         if {$highlight_commit ne {}} {
627                 set cmit $highlight_commit
628                 $w_cgrp tag conf a$cmit -background {}
629                 $w_line tag conf a$cmit -background {}
630                 $w_file tag conf a$cmit -background {}
631         }
632
633         $w_cmit conf -state normal
634         $w_cmit delete 0.0 end
635         if {[catch {set cmit $line_commit($lno)}]} {
636                 set cmit {}
637                 $w_cmit insert end "Loading annotation..."
638         } else {
639                 $w_cgrp tag conf a$cmit -background $active_color
640                 $w_line tag conf a$cmit -background $active_color
641                 $w_file tag conf a$cmit -background $active_color
642
643                 set author_name {}
644                 set author_email {}
645                 set author_time {}
646                 catch {set author_name $header($cmit,author)}
647                 catch {set author_email $header($cmit,author-mail)}
648                 catch {set author_time [clock format \
649                         $header($cmit,author-time) \
650                         -format {%Y-%m-%d %H:%M:%S}
651                 ]}
652
653                 set committer_name {}
654                 set committer_email {}
655                 set committer_time {}
656                 catch {set committer_name $header($cmit,committer)}
657                 catch {set committer_email $header($cmit,committer-mail)}
658                 catch {set committer_time [clock format \
659                         $header($cmit,committer-time) \
660                         -format {%Y-%m-%d %H:%M:%S}
661                 ]}
662
663                 if {[catch {set msg $header($cmit,message)}]} {
664                         set msg {}
665                         catch {
666                                 set fd [open "| git cat-file commit $cmit" r]
667                                 fconfigure $fd -encoding binary -translation lf
668                                 if {[catch {set enc $repo_config(i18n.commitencoding)}]} {
669                                         set enc utf-8
670                                 }
671                                 while {[gets $fd line] > 0} {
672                                         if {[string match {encoding *} $line]} {
673                                                 set enc [string tolower [string range $line 9 end]]
674                                         }
675                                 }
676                                 set msg [encoding convertfrom $enc [read $fd]]
677                                 set msg [string trim $msg]
678                                 close $fd
679
680                                 set author_name [encoding convertfrom $enc $author_name]
681                                 set committer_name [encoding convertfrom $enc $committer_name]
682
683                                 set header($cmit,author) $author_name
684                                 set header($cmit,committer) $committer_name
685                         }
686                         set header($cmit,message) $msg
687                 }
688
689                 $w_cmit insert end "commit $cmit\n" header_key
690                 $w_cmit insert end "Author:\t" header_key
691                 $w_cmit insert end "$author_name $author_email" header_val
692                 $w_cmit insert end "$author_time\n" header_val
693
694                 $w_cmit insert end "Committer:\t" header_key
695                 $w_cmit insert end "$committer_name $committer_email" header_val
696                 $w_cmit insert end "$committer_time\n" header_val
697
698                 if {$line_file($lno) ne $path} {
699                         $w_cmit insert end "Original File:\t" header_key
700                         $w_cmit insert end "[escape_path $line_file($lno)]\n" header_val
701                 }
702
703                 $w_cmit insert end "\n$msg"
704         }
705         $w_cmit conf -state disabled
706
707         set highlight_line $lno
708         set highlight_commit $cmit
709
710         if {$highlight_commit eq $tooltip_commit} {
711                 _hide_tooltip $this
712         }
713 }
714
715 method _copycommit {} {
716         set pos @$::cursorX,$::cursorY
717         set lno [lindex [split [$::cursorW index $pos] .] 0]
718         if {![catch {set commit $line_commit($lno)}]} {
719                 clipboard clear
720                 clipboard append \
721                         -format STRING \
722                         -type STRING \
723                         -- $commit
724         }
725 }
726
727 method _show_tooltip {cur_w pos} {
728         set lno [lindex [split [$cur_w index $pos] .] 0]
729         if {[catch {set cmit $line_commit($lno)}]} {
730                 _hide_tooltip $this
731                 return
732         }
733
734         if {$cmit eq $highlight_commit} {
735                 _hide_tooltip $this
736                 return
737         }
738
739         if {$cmit eq $tooltip_commit} {
740                 _position_tooltip $this
741         } elseif {$tooltip_wm ne {}} {
742                 _open_tooltip $this $cur_w
743         } elseif {$tooltip_timer eq {}} {
744                 set tooltip_timer [after 1000 [cb _open_tooltip $cur_w]]
745         }
746 }
747
748 method _open_tooltip {cur_w} {
749         set tooltip_timer {}
750         set pos_x [winfo pointerx $cur_w]
751         set pos_y [winfo pointery $cur_w]
752         if {[winfo containing $pos_x $pos_y] ne $cur_w} {
753                 _hide_tooltip $this
754                 return
755         }
756
757         set pos @[join [list \
758                 [expr {$pos_x - [winfo rootx $cur_w]}] \
759                 [expr {$pos_y - [winfo rooty $cur_w]}]] ,]
760         set lno [lindex [split [$cur_w index $pos] .] 0]
761         set cmit $line_commit($lno)
762
763         set author_name {}
764         set author_email {}
765         set author_time {}
766         catch {set author_name $header($cmit,author)}
767         catch {set author_email $header($cmit,author-mail)}
768         catch {set author_time [clock format \
769                 $header($cmit,author-time) \
770                 -format {%Y-%m-%d %H:%M:%S}
771         ]}
772
773         set committer_name {}
774         set committer_email {}
775         set committer_time {}
776         catch {set committer_name $header($cmit,committer)}
777         catch {set committer_email $header($cmit,committer-mail)}
778         catch {set committer_time [clock format \
779                 $header($cmit,committer-time) \
780                 -format {%Y-%m-%d %H:%M:%S}
781         ]}
782
783         set summary {}
784         catch {set summary $header($cmit,summary)}
785
786         set tooltip_commit $cmit
787         set tooltip_text "commit $cmit
788 $author_name $author_email  $author_time
789 $summary"
790
791         set file $line_file($lno)
792         if {$file ne $path} {
793                 append tooltip_text "
794
795 Original File: $file"
796         }
797
798         if {$tooltip_wm ne "$cur_w.tooltip"} {
799                 _hide_tooltip $this
800
801                 set tooltip_wm [toplevel $cur_w.tooltip -borderwidth 1]
802                 wm overrideredirect $tooltip_wm 1
803                 wm transient $tooltip_wm [winfo toplevel $cur_w]
804                 pack [label $tooltip_wm.label \
805                         -background lightyellow \
806                         -foreground black \
807                         -textvariable @tooltip_text \
808                         -justify left]
809         }
810         _position_tooltip $this
811 }
812
813 method _position_tooltip {} {
814         set req_w [winfo reqwidth  $tooltip_wm.label]
815         set req_h [winfo reqheight $tooltip_wm.label]
816         set pos_x [expr {[winfo pointerx .] +  5}]
817         set pos_y [expr {[winfo pointery .] + 10}]
818
819         set g "${req_w}x${req_h}"
820         if {$pos_x >= 0} {append g +}
821         append g $pos_x
822         if {$pos_y >= 0} {append g +}
823         append g $pos_y
824
825         wm geometry $tooltip_wm $g
826         raise $tooltip_wm
827 }
828
829 method _hide_tooltip {} {
830         if {$tooltip_wm ne {}} {
831                 destroy $tooltip_wm
832                 set tooltip_wm {}
833                 set tooltip_commit {}
834         }
835         if {$tooltip_timer ne {}} {
836                 after cancel $tooltip_timer
837                 set tooltip_timer {}
838         }
839 }
840
841 method _resize {new_height} {
842         set diff [expr {$new_height - $old_height}]
843         if {$diff == 0} return
844
845         set my [expr {[winfo height $w.file_pane] - 25}]
846         set o [$w.file_pane sash coord 0]
847         set ox [lindex $o 0]
848         set oy [expr {[lindex $o 1] + $diff}]
849         if {$oy < 0}   {set oy 0}
850         if {$oy > $my} {set oy $my}
851         $w.file_pane sash place 0 $ox $oy
852
853         set old_height $new_height
854 }
855
856 }