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