lib / blame.tclon commit git-gui: Use arror cursor in blame viewer file data (37ebc93)
   1# git-gui blame viewer
   2# Copyright (C) 2006, 2007 Shawn Pearce
   3
   4class blame {
   5
   6field commit  ; # input commit to blame
   7field path    ; # input filename to view in $commit
   8
   9field w
  10field w_line
  11field w_cgrp
  12field w_load
  13field w_file
  14field w_cmit
  15field status
  16
  17field highlight_line   -1 ; # current line selected
  18field highlight_commit {} ; # sha1 of commit selected
  19
  20field total_lines       0  ; # total length of file
  21field blame_lines       0  ; # number of lines computed
  22field commit_count      0  ; # number of commits in $commit_list
  23field commit_list      {}  ; # list of commit sha1 in receipt order
  24field order                ; # array commit -> receipt order
  25field header               ; # array commit,key -> header field
  26field line_commit          ; # array line -> sha1 commit
  27field line_file            ; # array line -> file name
  28
  29field r_commit      ; # commit currently being parsed
  30field r_orig_line   ; # original line number
  31field r_final_line  ; # final line number
  32field r_line_count  ; # lines in this region
  33
  34variable active_color #98e1a0
  35variable group_colors {
  36        #cbcbcb
  37        #e1e1e1
  38}
  39
  40constructor new {i_commit i_path} {
  41        global cursor_ptr
  42
  43        set commit $i_commit
  44        set path   $i_path
  45
  46        make_toplevel top w
  47        wm title $top "[appname] ([reponame]): File Viewer"
  48        set status "Loading $commit:$path..."
  49
  50        label $w.path -text "$commit:$path" \
  51                -anchor w \
  52                -justify left \
  53                -borderwidth 1 \
  54                -relief sunken \
  55                -font font_uibold
  56        pack $w.path -side top -fill x
  57
  58        frame $w.out
  59        set w_load $w.out.loaded_t
  60        text $w_load \
  61                -background white -borderwidth 0 \
  62                -state disabled \
  63                -wrap none \
  64                -height 40 \
  65                -width 1 \
  66                -font font_diff
  67        $w_load tag conf annotated -background grey
  68
  69        set w_line $w.out.linenumber_t
  70        text $w_line \
  71                -background white -borderwidth 0 \
  72                -state disabled \
  73                -wrap none \
  74                -height 40 \
  75                -width 5 \
  76                -font font_diff
  77        $w_line tag conf linenumber -justify right
  78
  79        set w_cgrp $w.out.commit_t
  80        text $w_cgrp \
  81                -background white -borderwidth 0 \
  82                -state disabled \
  83                -wrap none \
  84                -height 40 \
  85                -width 4 \
  86                -font font_diff
  87
  88        set w_file $w.out.file_t
  89        text $w_file \
  90                -background white -borderwidth 0 \
  91                -state disabled \
  92                -wrap none \
  93                -height 40 \
  94                -width 80 \
  95                -xscrollcommand [list $w.out.sbx set] \
  96                -font font_diff
  97
  98        scrollbar $w.out.sbx -orient h -command [list $w_file xview]
  99        scrollbar $w.out.sby -orient v \
 100                -command [list scrollbar2many [list \
 101                $w_load \
 102                $w_line \
 103                $w_cgrp \
 104                $w_file \
 105                ] yview]
 106        grid \
 107                $w_cgrp \
 108                $w_line \
 109                $w_load \
 110                $w_file \
 111                $w.out.sby \
 112                -sticky nsew
 113        grid conf $w.out.sbx -column 3 -sticky we
 114        grid columnconfigure $w.out 3 -weight 1
 115        grid rowconfigure $w.out 0 -weight 1
 116        pack $w.out -fill both -expand 1
 117
 118        label $w.status \
 119                -textvariable @status \
 120                -anchor w \
 121                -justify left \
 122                -borderwidth 1 \
 123                -relief sunken
 124        pack $w.status -side bottom -fill x
 125
 126        frame $w.cm
 127        set w_cmit $w.cm.t
 128        text $w_cmit \
 129                -background white -borderwidth 0 \
 130                -state disabled \
 131                -wrap none \
 132                -height 10 \
 133                -width 80 \
 134                -xscrollcommand [list $w.cm.sbx set] \
 135                -yscrollcommand [list $w.cm.sby set] \
 136                -font font_diff
 137        scrollbar $w.cm.sbx -orient h -command [list $w_cmit xview]
 138        scrollbar $w.cm.sby -orient v -command [list $w_cmit yview]
 139        pack $w.cm.sby -side right -fill y
 140        pack $w.cm.sbx -side bottom -fill x
 141        pack $w_cmit -expand 1 -fill both
 142        pack $w.cm -side bottom -fill x
 143
 144        menu $w.ctxm -tearoff 0
 145        $w.ctxm add command \
 146                -label "Copy Commit" \
 147                -command [cb _copycommit]
 148
 149        foreach i [list \
 150                $w_cgrp \
 151                $w_load \
 152                $w_line \
 153                $w_file] {
 154                $i conf -cursor $cursor_ptr
 155                $i conf -yscrollcommand \
 156                        [list many2scrollbar [list \
 157                        $w_cgrp \
 158                        $w_load \
 159                        $w_line \
 160                        $w_file \
 161                        ] yview $w.out.sby]
 162                bind $i <Button-1> "[cb _click $i @%x,%y]; focus $i"
 163                bind_button3 $i "
 164                        set cursorX %x
 165                        set cursorY %y
 166                        set cursorW %W
 167                        tk_popup $w.ctxm %X %Y
 168                "
 169        }
 170
 171        foreach i [list \
 172                $w_cgrp \
 173                $w_load \
 174                $w_line \
 175                $w_file \
 176                $w_cmit] {
 177                bind $i <Key-Up>        {catch {%W yview scroll -1 units};break}
 178                bind $i <Key-Down>      {catch {%W yview scroll  1 units};break}
 179                bind $i <Key-Left>      {catch {%W xview scroll -1 units};break}
 180                bind $i <Key-Right>     {catch {%W xview scroll  1 units};break}
 181                bind $i <Key-k>         {catch {%W yview scroll -1 units};break}
 182                bind $i <Key-j>         {catch {%W yview scroll  1 units};break}
 183                bind $i <Key-h>         {catch {%W xview scroll -1 units};break}
 184                bind $i <Key-l>         {catch {%W xview scroll  1 units};break}
 185                bind $i <Control-Key-b> {catch {%W yview scroll -1 pages};break}
 186                bind $i <Control-Key-f> {catch {%W yview scroll  1 pages};break}
 187        }
 188
 189        bind $w_cmit <Button-1> [list focus $w_cmit]
 190        bind $top <Visibility> [list focus $top]
 191        bind $top <Destroy> [list delete_this $this]
 192
 193        if {$commit eq {}} {
 194                set fd [open $path r]
 195        } else {
 196                set cmd [list git cat-file blob "$commit:$path"]
 197                set fd [open "| $cmd" r]
 198        }
 199        fconfigure $fd -blocking 0 -translation lf -encoding binary
 200        fileevent $fd readable [cb _read_file $fd]
 201}
 202
 203method _read_file {fd} {
 204        $w_load conf -state normal
 205        $w_cgrp conf -state normal
 206        $w_line conf -state normal
 207        $w_file conf -state normal
 208        while {[gets $fd line] >= 0} {
 209                regsub "\r\$" $line {} line
 210                incr total_lines
 211
 212                if {$total_lines > 1} {
 213                        $w_load insert end "\n"
 214                        $w_cgrp insert end "\n"
 215                        $w_line insert end "\n"
 216                        $w_file insert end "\n"
 217                }
 218
 219                $w_line insert end "$total_lines" linenumber
 220                $w_file insert end "$line"
 221        }
 222        $w_load conf -state disabled
 223        $w_cgrp conf -state disabled
 224        $w_line conf -state disabled
 225        $w_file conf -state disabled
 226
 227        if {[eof $fd]} {
 228                close $fd
 229                _status $this
 230                set cmd [list git blame -M -C --incremental]
 231                if {$commit eq {}} {
 232                        lappend cmd --contents $path
 233                } else {
 234                        lappend cmd $commit
 235                }
 236                lappend cmd -- $path
 237                set fd [open "| $cmd" r]
 238                fconfigure $fd -blocking 0 -translation lf -encoding binary
 239                fileevent $fd readable [cb _read_blame $fd]
 240        }
 241} ifdeleted { catch {close $fd} }
 242
 243method _read_blame {fd} {
 244        variable group_colors
 245
 246        $w_cgrp conf -state normal
 247        while {[gets $fd line] >= 0} {
 248                if {[regexp {^([a-z0-9]{40}) (\d+) (\d+) (\d+)$} $line line \
 249                        cmit original_line final_line line_count]} {
 250                        set r_commit     $cmit
 251                        set r_orig_line  $original_line
 252                        set r_final_line $final_line
 253                        set r_line_count $line_count
 254
 255                        if {[catch {set g $order($cmit)}]} {
 256                                set bg [lindex $group_colors 0]
 257                                set group_colors [lrange $group_colors 1 end]
 258                                lappend group_colors $bg
 259
 260                                $w_cgrp tag conf g$cmit -background $bg
 261                                $w_line tag conf g$cmit -background $bg
 262                                $w_file tag conf g$cmit -background $bg
 263
 264                                set order($cmit) $commit_count
 265                                incr commit_count
 266                                lappend commit_list $cmit
 267                        }
 268                } elseif {[string match {filename *} $line]} {
 269                        set file [string range $line 9 end]
 270                        set n    $r_line_count
 271                        set lno  $r_final_line
 272                        set cmit $r_commit
 273
 274                        if {[regexp {^0{40}$} $cmit]} {
 275                                set abbr work
 276                        } else {
 277                                set abbr [string range $cmit 0 4]
 278                        }
 279
 280                        if {![catch {set ncmit $line_commit([expr {$lno - 1}])}]} {
 281                                if {$ncmit eq $cmit} {
 282                                        set abbr |
 283                                }
 284                        }
 285
 286                        while {$n > 0} {
 287                                set lno_e "$lno.0 lineend + 1c"
 288                                if {[catch {set g g$line_commit($lno)}]} {
 289                                        $w_load tag add annotated $lno.0 $lno_e
 290                                } else {
 291                                        $w_cgrp tag remove g$g $lno.0 $lno_e
 292                                        $w_line tag remove g$g $lno.0 $lno_e
 293                                        $w_file tag remove g$g $lno.0 $lno_e
 294
 295                                        $w_cgrp tag remove a$g $lno.0 $lno_e
 296                                        $w_line tag remove a$g $lno.0 $lno_e
 297                                        $w_file tag remove a$g $lno.0 $lno_e
 298                                }
 299
 300                                set line_commit($lno) $cmit
 301                                set line_file($lno)   $file
 302
 303                                $w_cgrp delete $lno.0 "$lno.0 lineend"
 304                                $w_cgrp insert $lno.0 $abbr
 305                                set abbr |
 306
 307                                $w_cgrp tag add g$cmit $lno.0 $lno_e
 308                                $w_line tag add g$cmit $lno.0 $lno_e
 309                                $w_file tag add g$cmit $lno.0 $lno_e
 310
 311                                $w_cgrp tag add a$cmit $lno.0 $lno_e
 312                                $w_line tag add a$cmit $lno.0 $lno_e
 313                                $w_file tag add a$cmit $lno.0 $lno_e
 314
 315                                if {$highlight_line == -1} {
 316                                        if {[lindex [$w_file yview] 0] == 0} {
 317                                                $w_file see $lno.0
 318                                                _showcommit $this $lno
 319                                        }
 320                                } elseif {$highlight_line == $lno} {
 321                                        _showcommit $this $lno
 322                                }
 323
 324                                incr n -1
 325                                incr lno
 326                                incr blame_lines
 327                        }
 328
 329                        if {![catch {set ncmit $line_commit($lno)}]} {
 330                                if {$ncmit eq $cmit} {
 331                                        $w_cgrp delete $lno.0 "$lno.0 lineend + 1c"
 332                                        $w_cgrp insert $lno.0 "|\n"
 333                                }
 334                        }
 335
 336                        set hc $highlight_commit
 337                        if {$hc ne {}
 338                                && [expr {$order($hc) + 1}] == $order($cmit)} {
 339                                _showcommit $this $highlight_line
 340                        }
 341                } elseif {[regexp {^([a-z-]+) (.*)$} $line line key data]} {
 342                        set header($r_commit,$key) $data
 343                }
 344        }
 345        $w_cgrp conf -state disabled
 346
 347        if {[eof $fd]} {
 348                close $fd
 349                set status {Annotation complete.}
 350        } else {
 351                _status $this
 352        }
 353} ifdeleted { catch {close $fd} }
 354
 355method _status {} {
 356        set have  $blame_lines
 357        set total $total_lines
 358        set pdone 0
 359        if {$total} {set pdone [expr {100 * $have / $total}]}
 360
 361        set status [format \
 362                "Loading annotations... %i of %i lines annotated (%2i%%)" \
 363                $have $total $pdone]
 364}
 365
 366method _click {cur_w pos} {
 367        set lno [lindex [split [$cur_w index $pos] .] 0]
 368        if {$lno eq {}} return
 369        _showcommit $this $lno
 370}
 371
 372method _showcommit {lno} {
 373        global repo_config
 374        variable active_color
 375
 376        if {$highlight_commit ne {}} {
 377                set cmit $highlight_commit
 378                $w_cgrp tag conf a$cmit -background {}
 379                $w_line tag conf a$cmit -background {}
 380                $w_file tag conf a$cmit -background {}
 381        }
 382
 383        $w_cmit conf -state normal
 384        $w_cmit delete 0.0 end
 385        if {[catch {set cmit $line_commit($lno)}]} {
 386                set cmit {}
 387                $w_cmit insert end "Loading annotation..."
 388        } else {
 389                $w_cgrp tag conf a$cmit -background $active_color
 390                $w_line tag conf a$cmit -background $active_color
 391                $w_file tag conf a$cmit -background $active_color
 392
 393                set author_name {}
 394                set author_email {}
 395                set author_time {}
 396                catch {set author_name $header($cmit,author)}
 397                catch {set author_email $header($cmit,author-mail)}
 398                catch {set author_time [clock format \
 399                        $header($cmit,author-time) \
 400                        -format {%Y-%m-%d %H:%M:%S}
 401                ]}
 402
 403                set committer_name {}
 404                set committer_email {}
 405                set committer_time {}
 406                catch {set committer_name $header($cmit,committer)}
 407                catch {set committer_email $header($cmit,committer-mail)}
 408                catch {set committer_time [clock format \
 409                        $header($cmit,committer-time) \
 410                        -format {%Y-%m-%d %H:%M:%S}
 411                ]}
 412
 413                if {[catch {set msg $header($cmit,message)}]} {
 414                        set msg {}
 415                        catch {
 416                                set fd [open "| git cat-file commit $cmit" r]
 417                                fconfigure $fd -encoding binary -translation lf
 418                                if {[catch {set enc $repo_config(i18n.commitencoding)}]} {
 419                                        set enc utf-8
 420                                }
 421                                while {[gets $fd line] > 0} {
 422                                        if {[string match {encoding *} $line]} {
 423                                                set enc [string tolower [string range $line 9 end]]
 424                                        }
 425                                }
 426                                set msg [encoding convertfrom $enc [read $fd]]
 427                                set msg [string trim $msg]
 428                                close $fd
 429
 430                                set author_name [encoding convertfrom $enc $author_name]
 431                                set committer_name [encoding convertfrom $enc $committer_name]
 432
 433                                set header($cmit,author) $author_name
 434                                set header($cmit,committer) $committer_name
 435                        }
 436                        set header($cmit,message) $msg
 437                }
 438
 439                $w_cmit insert end "commit $cmit
 440Author: $author_name $author_email  $author_time
 441Committer: $committer_name $committer_email  $committer_time
 442Original File: [escape_path $line_file($lno)]
 443
 444$msg"
 445        }
 446        $w_cmit conf -state disabled
 447
 448        set highlight_line $lno
 449        set highlight_commit $cmit
 450}
 451
 452method _copycommit {} {
 453        set pos @$::cursorX,$::cursorY
 454        set lno [lindex [split [$::cursorW index $pos] .] 0]
 455        if {![catch {set commit $line_commit($lno)}]} {
 456                clipboard clear
 457                clipboard append \
 458                        -format STRING \
 459                        -type STRING \
 460                        -- $commit
 461        }
 462}
 463
 464}