2 # Copyright (C) 2006, 2007 Shawn Pearce
6 field commit
; # input commit to blame
7 field path
; # input filename to view in $commit
17 field highlight_line
-1 ; # current line selected
18 field highlight_commit
{} ; # sha1 of commit selected
20 field total_lines
0 ; # total length of file
21 field blame_lines
0 ; # number of lines computed
22 field commit_count
0 ; # number of commits in $commit_list
23 field commit_list
{} ; # list of commit sha1 in receipt order
24 field order
; # array commit -> receipt order
25 field header
; # array commit,key -> header field
26 field line_commit
; # array line -> sha1 commit
27 field line_file
; # array line -> file name
29 field r_commit
; # commit currently being parsed
30 field r_orig_line
; # original line number
31 field r_final_line
; # final line number
32 field r_line_count
; # lines in this region
34 field tooltip_wm
{} ; # Current tooltip toplevel, if open
35 field tooltip_timer
{} ; # Current timer event for our tooltip
36 field tooltip_commit
{} ; # Commit in tooltip
37 field tooltip_text
{} ; # Text in current tooltip
39 variable active_color
#98e1a0
40 variable group_colors
{
45 constructor new
{i_commit i_path
} {
52 wm title
$top "[appname] ([reponame]): File Viewer"
53 set status
"Loading $commit:$path..."
55 label $w.path
-text "$commit:$path" \
61 pack $w.path
-side top
-fill x
64 set w_load
$w.out.loaded_t
66 -background white
-borderwidth 0 \
72 $w_load tag conf annotated
-background grey
74 set w_line
$w.out.linenumber_t
76 -background white
-borderwidth 0 \
82 $w_line tag conf linenumber
-justify right
84 set w_cgrp
$w.out.commit_t
86 -background white
-borderwidth 0 \
93 set w_file
$w.out.file_t
95 -background white
-borderwidth 0 \
100 -xscrollcommand [list $w.out.sbx
set] \
103 scrollbar $w.out.sbx
-orient h
-command [list $w_file xview
]
104 scrollbar $w.out.sby
-orient v
\
105 -command [list scrollbar2many
[list \
118 grid conf
$w.out.sbx
-column 3 -sticky we
119 grid columnconfigure
$w.out
3 -weight 1
120 grid rowconfigure
$w.out
0 -weight 1
121 pack $w.out
-fill both
-expand 1
124 -textvariable @status
\
129 pack $w.status
-side bottom
-fill x
134 -background white
-borderwidth 0 \
139 -xscrollcommand [list $w.cm.sbx
set] \
140 -yscrollcommand [list $w.cm.sby
set] \
142 scrollbar $w.cm.sbx
-orient h
-command [list $w_cmit xview
]
143 scrollbar $w.cm.sby
-orient v
-command [list $w_cmit yview
]
144 pack $w.cm.sby
-side right
-fill y
145 pack $w.cm.sbx
-side bottom
-fill x
146 pack $w_cmit -expand 1 -fill both
147 pack $w.cm
-side bottom
-fill x
149 menu $w.ctxm
-tearoff 0
150 $w.ctxm add command
\
151 -label "Copy Commit" \
152 -command [cb _copycommit
]
159 $i conf
-cursor $cursor_ptr
160 $i conf
-yscrollcommand \
161 [list many2scrollbar
[list \
169 [cb _click $i @%x,%y]
172 bind $i <Any-Motion
> [cb _show_tooltip
$i @%x
,%y
]
173 bind $i <Any-Enter
> [cb _hide_tooltip
]
174 bind $i <Any-Leave
> [cb _hide_tooltip
]
180 tk_popup $w.ctxm %X %Y
190 bind $i <Key-Up
> {catch {%W yview scroll
-1 units
};break}
191 bind $i <Key-Down
> {catch {%W yview scroll
1 units
};break}
192 bind $i <Key-Left
> {catch {%W xview scroll
-1 units
};break}
193 bind $i <Key-Right
> {catch {%W xview scroll
1 units
};break}
194 bind $i <Key-k
> {catch {%W yview scroll
-1 units
};break}
195 bind $i <Key-j
> {catch {%W yview scroll
1 units
};break}
196 bind $i <Key-h
> {catch {%W xview scroll
-1 units
};break}
197 bind $i <Key-l
> {catch {%W xview scroll
1 units
};break}
198 bind $i <Control-Key-b
> {catch {%W yview scroll
-1 pages
};break}
199 bind $i <Control-Key-f
> {catch {%W yview scroll
1 pages
};break}
202 bind $w_cmit <Button-1
> [list focus $w_cmit]
203 bind $top <Visibility
> [list focus $top]
204 bind $top <Destroy
> [list delete_this
$this]
207 set fd
[open $path r
]
209 set cmd
[list git cat-file blob
"$commit:$path"]
210 set fd
[open "| $cmd" r
]
212 fconfigure $fd -blocking 0 -translation lf
-encoding binary
213 fileevent $fd readable
[cb _read_file
$fd]
216 method _read_file
{fd
} {
217 $w_load conf
-state normal
218 $w_cgrp conf
-state normal
219 $w_line conf
-state normal
220 $w_file conf
-state normal
221 while {[gets $fd line
] >= 0} {
222 regsub "\r\$" $line {} line
225 if {$total_lines > 1} {
226 $w_load insert end
"\n"
227 $w_cgrp insert end
"\n"
228 $w_line insert end
"\n"
229 $w_file insert end
"\n"
232 $w_line insert end
"$total_lines" linenumber
233 $w_file insert end
"$line"
235 $w_load conf
-state disabled
236 $w_cgrp conf
-state disabled
237 $w_line conf
-state disabled
238 $w_file conf
-state disabled
243 set cmd
[list git blame
-M -C --incremental]
245 lappend cmd
--contents $path
250 set fd
[open "| $cmd" r
]
251 fconfigure $fd -blocking 0 -translation lf
-encoding binary
252 fileevent $fd readable
[cb _read_blame
$fd]
254 } ifdeleted
{ catch {close $fd} }
256 method _read_blame
{fd
} {
257 variable group_colors
259 $w_cgrp conf
-state normal
260 while {[gets $fd line
] >= 0} {
261 if {[regexp {^
([a-z0-9
]{40}) (\d
+) (\d
+) (\d
+)$} $line line
\
262 cmit original_line final_line line_count
]} {
264 set r_orig_line
$original_line
265 set r_final_line
$final_line
266 set r_line_count
$line_count
268 if {[catch {set g
$order($cmit)}]} {
269 set bg
[lindex $group_colors 0]
270 set group_colors
[lrange $group_colors 1 end
]
271 lappend group_colors
$bg
273 $w_cgrp tag conf g
$cmit -background $bg
274 $w_line tag conf g
$cmit -background $bg
275 $w_file tag conf g
$cmit -background $bg
277 set order
($cmit) $commit_count
279 lappend commit_list
$cmit
281 } elseif
{[string match
{filename *} $line]} {
282 set file [string range
$line 9 end
]
284 set lno
$r_final_line
287 if {[regexp {^
0{40}$} $cmit]} {
290 set abbr
[string range
$cmit 0 4]
293 if {![catch {set ncmit
$line_commit([expr {$lno - 1}])}]} {
294 if {$ncmit eq
$cmit} {
300 set lno_e
"$lno.0 lineend + 1c"
301 if {[catch {set g g
$line_commit($lno)}]} {
302 $w_load tag add annotated
$lno.0 $lno_e
304 $w_cgrp tag remove g
$g $lno.0 $lno_e
305 $w_line tag remove g
$g $lno.0 $lno_e
306 $w_file tag remove g
$g $lno.0 $lno_e
308 $w_cgrp tag remove a
$g $lno.0 $lno_e
309 $w_line tag remove a
$g $lno.0 $lno_e
310 $w_file tag remove a
$g $lno.0 $lno_e
313 set line_commit
($lno) $cmit
314 set line_file
($lno) $file
316 $w_cgrp delete
$lno.0 "$lno.0 lineend"
317 $w_cgrp insert
$lno.0 $abbr
320 $w_cgrp tag add g
$cmit $lno.0 $lno_e
321 $w_line tag add g
$cmit $lno.0 $lno_e
322 $w_file tag add g
$cmit $lno.0 $lno_e
324 $w_cgrp tag add a
$cmit $lno.0 $lno_e
325 $w_line tag add a
$cmit $lno.0 $lno_e
326 $w_file tag add a
$cmit $lno.0 $lno_e
328 if {$highlight_line == -1} {
329 if {[lindex [$w_file yview
] 0] == 0} {
331 _showcommit
$this $lno
333 } elseif
{$highlight_line == $lno} {
334 _showcommit
$this $lno
342 if {![catch {set ncmit
$line_commit($lno)}]} {
343 if {$ncmit eq
$cmit} {
344 $w_cgrp delete
$lno.0 "$lno.0 lineend + 1c"
345 $w_cgrp insert
$lno.0 "|\n"
349 set hc
$highlight_commit
351 && [expr {$order($hc) + 1}] == $order($cmit)} {
352 _showcommit
$this $highlight_line
354 } elseif
{[regexp {^
([a-z-
]+) (.
*)$} $line line key data
]} {
355 set header
($r_commit,$key) $data
358 $w_cgrp conf
-state disabled
362 set status
{Annotation complete.
}
366 } ifdeleted
{ catch {close $fd} }
369 set have
$blame_lines
370 set total
$total_lines
372 if {$total} {set pdone
[expr {100 * $have / $total}]}
375 "Loading annotations... %i of %i lines annotated (%2i%%)" \
379 method _click
{cur_w pos
} {
380 set lno
[lindex [split [$cur_w index
$pos] .
] 0]
381 if {$lno eq
{}} return
382 _showcommit
$this $lno
385 method _showcommit
{lno
} {
387 variable active_color
389 if {$highlight_commit ne
{}} {
390 set cmit
$highlight_commit
391 $w_cgrp tag conf a
$cmit -background {}
392 $w_line tag conf a
$cmit -background {}
393 $w_file tag conf a
$cmit -background {}
396 $w_cmit conf
-state normal
397 $w_cmit delete
0.0 end
398 if {[catch {set cmit
$line_commit($lno)}]} {
400 $w_cmit insert end
"Loading annotation..."
402 $w_cgrp tag conf a
$cmit -background $active_color
403 $w_line tag conf a
$cmit -background $active_color
404 $w_file tag conf a
$cmit -background $active_color
409 catch {set author_name
$header($cmit,author
)}
410 catch {set author_email
$header($cmit,author-mail
)}
411 catch {set author_time
[clock format \
412 $header($cmit,author-time
) \
413 -format {%Y-
%m-
%d
%H
:%M
:%S
}
416 set committer_name
{}
417 set committer_email
{}
418 set committer_time
{}
419 catch {set committer_name
$header($cmit,committer
)}
420 catch {set committer_email
$header($cmit,committer-mail
)}
421 catch {set committer_time
[clock format \
422 $header($cmit,committer-time
) \
423 -format {%Y-
%m-
%d
%H
:%M
:%S
}
426 if {[catch {set msg
$header($cmit,message)}]} {
429 set fd
[open "| git cat-file commit $cmit" r
]
430 fconfigure $fd -encoding binary -translation lf
431 if {[catch {set enc
$repo_config(i18n.commitencoding
)}]} {
434 while {[gets $fd line
] > 0} {
435 if {[string match
{encoding *} $line]} {
436 set enc
[string tolower
[string range
$line 9 end
]]
439 set msg
[encoding convertfrom
$enc [read $fd]]
440 set msg
[string trim
$msg]
443 set author_name
[encoding convertfrom
$enc $author_name]
444 set committer_name
[encoding convertfrom
$enc $committer_name]
446 set header
($cmit,author
) $author_name
447 set header
($cmit,committer
) $committer_name
449 set header
($cmit,message) $msg
452 $w_cmit insert end
"commit $cmit
453 Author: $author_name $author_email $author_time
454 Committer: $committer_name $committer_email $committer_time
455 Original File: [escape_path $line_file($lno)]
459 $w_cmit conf
-state disabled
461 set highlight_line
$lno
462 set highlight_commit
$cmit
464 if {$highlight_commit eq
$tooltip_commit} {
469 method _copycommit
{} {
470 set pos
@$::cursorX,$::cursorY
471 set lno
[lindex [split [$::cursorW index
$pos] .
] 0]
472 if {![catch {set commit
$line_commit($lno)}]} {
481 method _show_tooltip
{cur_w pos
} {
482 set lno
[lindex [split [$cur_w index
$pos] .
] 0]
483 if {[catch {set cmit
$line_commit($lno)}]} {
488 if {$cmit eq
$highlight_commit} {
493 if {$cmit eq
$tooltip_commit} {
494 _position_tooltip
$this
495 } elseif
{$tooltip_wm ne
{}} {
496 _open_tooltip
$this $cur_w
497 } elseif
{$tooltip_timer eq
{}} {
498 set tooltip_timer
[after 1000 [cb _open_tooltip
$cur_w]]
502 method _open_tooltip
{cur_w
} {
504 set pos_x
[winfo pointerx
$cur_w]
505 set pos_y
[winfo pointery
$cur_w]
506 if {[winfo containing
$pos_x $pos_y] ne
$cur_w} {
511 set pos
@[join [list \
512 [expr {$pos_x - [winfo rootx
$cur_w]}] \
513 [expr {$pos_y - [winfo rooty
$cur_w]}]] ,]
514 set lno
[lindex [split [$cur_w index
$pos] .
] 0]
515 set cmit
$line_commit($lno)
520 catch {set author_name
$header($cmit,author
)}
521 catch {set author_email
$header($cmit,author-mail
)}
522 catch {set author_time
[clock format \
523 $header($cmit,author-time
) \
524 -format {%Y-
%m-
%d
%H
:%M
:%S
}
527 set committer_name
{}
528 set committer_email
{}
529 set committer_time
{}
530 catch {set committer_name
$header($cmit,committer
)}
531 catch {set committer_email
$header($cmit,committer-mail
)}
532 catch {set committer_time
[clock format \
533 $header($cmit,committer-time
) \
534 -format {%Y-
%m-
%d
%H
:%M
:%S
}
538 catch {set summary
$header($cmit,summary
)}
540 set tooltip_commit
$cmit
541 set tooltip_text
"commit $cmit
542 $author_name $author_email $author_time
545 if {$tooltip_wm ne
"$cur_w.tooltip"} {
548 set tooltip_wm
[toplevel $cur_w.tooltip
-borderwidth 1]
549 wm overrideredirect
$tooltip_wm 1
550 wm transient
$tooltip_wm [winfo toplevel $cur_w]
551 pack [label $tooltip_wm.
label \
552 -background lightyellow
\
554 -textvariable @tooltip_text
\
557 _position_tooltip
$this
560 method _position_tooltip
{} {
561 set req_w
[winfo reqwidth
$tooltip_wm.
label]
562 set req_h
[winfo reqheight
$tooltip_wm.
label]
563 set pos_x
[expr {[winfo pointerx .
] + 5}]
564 set pos_y
[expr {[winfo pointery .
] + 10}]
566 set g
"${req_w}x${req_h}"
567 if {$pos_x >= 0} {append g
+}
569 if {$pos_y >= 0} {append g
+}
572 wm geometry
$tooltip_wm $g
576 method _hide_tooltip
{} {
577 if {$tooltip_wm ne
{}} {
580 set tooltip_commit
{}
582 if {$tooltip_timer ne
{}} {
583 after cancel
$tooltip_timer