git-guion commit git-gui: Corrected keyboard bindings on Windows, improved state management. (e210e67)
   1#!/bin/sh
   2# Tcl ignores the next line -*- tcl -*- \
   3exec wish "$0" -- "$@"
   4
   5# Copyright (C) 2006 Shawn Pearce, Paul Mackerras.  All rights reserved.
   6# This program is free software; it may be used, copied, modified
   7# and distributed under the terms of the GNU General Public Licence,
   8# either version 2, or (at your option) any later version.
   9
  10######################################################################
  11##
  12## task management
  13
  14set status_active 0
  15set diff_active 0
  16set checkin_active 0
  17set update_index_fd {}
  18
  19set disable_on_lock [list]
  20set index_lock_type none
  21
  22proc lock_index {type} {
  23        global index_lock_type disable_on_lock
  24
  25        if {$index_lock_type == {none}} {
  26                set index_lock_type $type
  27                foreach w $disable_on_lock {
  28                        uplevel #0 $w disabled
  29                }
  30                return 1
  31        } elseif {$index_lock_type == {begin-update} && $type == {update}} {
  32                set index_lock_type $type
  33                return 1
  34        }
  35        return 0
  36}
  37
  38proc unlock_index {} {
  39        global index_lock_type disable_on_lock
  40
  41        set index_lock_type none
  42        foreach w $disable_on_lock {
  43                uplevel #0 $w normal
  44        }
  45}
  46
  47######################################################################
  48##
  49## status
  50
  51proc update_status {} {
  52        global gitdir HEAD commit_type
  53        global ui_index ui_other ui_status_value ui_comm
  54        global status_active file_states
  55
  56        if {$status_active || ![lock_index read]} return
  57
  58        array unset file_states
  59        foreach w [list $ui_index $ui_other] {
  60                $w conf -state normal
  61                $w delete 0.0 end
  62                $w conf -state disabled
  63        }
  64
  65        if {[catch {set HEAD [exec git rev-parse --verify HEAD]}]} {
  66                set commit_type initial
  67        } else {
  68                set commit_type normal
  69        }
  70
  71        if {![$ui_comm edit modified]
  72            || [string trim [$ui_comm get 0.0 end]] == {}} {
  73                if {[load_message GITGUI_MSG]} {
  74                } elseif {[load_message MERGE_MSG]} {
  75                } elseif {[load_message SQUASH_MSG]} {
  76                }
  77                $ui_comm edit modified false
  78        }
  79
  80        set status_active 1
  81        set ui_status_value {Refreshing file status...}
  82        set fd_rf [open "| git update-index -q --unmerged --refresh" r]
  83        fconfigure $fd_rf -blocking 0 -translation binary
  84        fileevent $fd_rf readable [list read_refresh $fd_rf]
  85}
  86
  87proc read_refresh {fd} {
  88        global gitdir HEAD commit_type
  89        global ui_index ui_other ui_status_value ui_comm
  90        global status_active file_states
  91
  92        read $fd
  93        if {![eof $fd]} return
  94        close $fd
  95
  96        set ls_others [list | git ls-files --others -z \
  97                --exclude-per-directory=.gitignore]
  98        set info_exclude [file join $gitdir info exclude]
  99        if {[file readable $info_exclude]} {
 100                lappend ls_others "--exclude-from=$info_exclude"
 101        }
 102
 103        set status_active 3
 104        set ui_status_value {Scanning for modified files ...}
 105        set fd_di [open "| git diff-index --cached -z $HEAD" r]
 106        set fd_df [open "| git diff-files -z" r]
 107        set fd_lo [open $ls_others r]
 108
 109        fconfigure $fd_di -blocking 0 -translation binary
 110        fconfigure $fd_df -blocking 0 -translation binary
 111        fconfigure $fd_lo -blocking 0 -translation binary
 112        fileevent $fd_di readable [list read_diff_index $fd_di]
 113        fileevent $fd_df readable [list read_diff_files $fd_df]
 114        fileevent $fd_lo readable [list read_ls_others $fd_lo]
 115}
 116
 117proc load_message {file} {
 118        global gitdir ui_comm
 119
 120        set f [file join $gitdir $file]
 121        if {[file exists $f]} {
 122                if {[catch {set fd [open $f r]}]} {
 123                        return 0
 124                }
 125                set content [read $fd]
 126                close $fd
 127                $ui_comm delete 0.0 end
 128                $ui_comm insert end $content
 129                return 1
 130        }
 131        return 0
 132}
 133
 134proc read_diff_index {fd} {
 135        global buf_rdi
 136
 137        append buf_rdi [read $fd]
 138        set pck [split $buf_rdi "\0"]
 139        set buf_rdi [lindex $pck end]
 140        foreach {m p} [lrange $pck 0 end-1] {
 141                if {$m != {} && $p != {}} {
 142                        display_file $p [string index $m end]_
 143                }
 144        }
 145        status_eof $fd buf_rdi
 146}
 147
 148proc read_diff_files {fd} {
 149        global buf_rdf
 150
 151        append buf_rdf [read $fd]
 152        set pck [split $buf_rdf "\0"]
 153        set buf_rdf [lindex $pck end]
 154        foreach {m p} [lrange $pck 0 end-1] {
 155                if {$m != {} && $p != {}} {
 156                        display_file $p _[string index $m end]
 157                }
 158        }
 159        status_eof $fd buf_rdf
 160}
 161
 162proc read_ls_others {fd} {
 163        global buf_rlo
 164
 165        append buf_rlo [read $fd]
 166        set pck [split $buf_rlo "\0"]
 167        set buf_rlo [lindex $pck end]
 168        foreach p [lrange $pck 0 end-1] {
 169                display_file $p _O
 170        }
 171        status_eof $fd buf_rlo
 172}
 173
 174proc status_eof {fd buf} {
 175        global status_active $buf
 176        global ui_fname_value ui_status_value
 177
 178        if {[eof $fd]} {
 179                set $buf {}
 180                close $fd
 181                if {[incr status_active -1] == 0} {
 182                        unlock_index
 183                        set ui_status_value {Ready.}
 184                        if {$ui_fname_value != {}} {
 185                                show_diff $ui_fname_value
 186                        }
 187                }
 188        }
 189}
 190
 191######################################################################
 192##
 193## diff
 194
 195proc clear_diff {} {
 196        global ui_diff ui_fname_value ui_fstatus_value
 197
 198        $ui_diff conf -state normal
 199        $ui_diff delete 0.0 end
 200        $ui_diff conf -state disabled
 201        set ui_fname_value {}
 202        set ui_fstatus_value {}
 203}
 204
 205proc show_diff {path} {
 206        global file_states HEAD diff_3way diff_active
 207        global ui_diff ui_fname_value ui_fstatus_value ui_status_value
 208
 209        if {$diff_active || ![lock_index read]} return
 210
 211        clear_diff
 212        set s $file_states($path)
 213        set m [lindex $s 0]
 214        set diff_3way 0
 215        set diff_active 1
 216        set ui_fname_value $path
 217        set ui_fstatus_value [mapdesc $m $path]
 218        set ui_status_value "Loading diff of $path..."
 219
 220        set cmd [list | git diff-index -p $HEAD -- $path]
 221        switch $m {
 222        AM {
 223        }
 224        MM {
 225                set cmd [list | git diff-index -p -c $HEAD $path]
 226        }
 227        _O {
 228                if {[catch {
 229                                set fd [open $path r]
 230                                set content [read $fd]
 231                                close $fd
 232                        } err ]} {
 233                        set diff_active 0
 234                        unlock_index
 235                        set ui_status_value "Unable to display $path"
 236                        error_popup "Error loading file:\n$err"
 237                        return
 238                }
 239                $ui_diff conf -state normal
 240                $ui_diff insert end $content
 241                $ui_diff conf -state disabled
 242                return
 243        }
 244        }
 245
 246        if {[catch {set fd [open $cmd r]} err]} {
 247                set diff_active 0
 248                unlock_index
 249                set ui_status_value "Unable to display $path"
 250                error_popup "Error loading diff:\n$err"
 251                return
 252        }
 253
 254        fconfigure $fd -blocking 0 -translation auto
 255        fileevent $fd readable [list read_diff $fd]
 256}
 257
 258proc read_diff {fd} {
 259        global ui_diff ui_status_value diff_3way diff_active
 260
 261        while {[gets $fd line] >= 0} {
 262                if {[string match {diff --git *} $line]} continue
 263                if {[string match {diff --combined *} $line]} continue
 264                if {[string match {--- *} $line]} continue
 265                if {[string match {+++ *} $line]} continue
 266                if {[string match index* $line]} {
 267                        if {[string first , $line] >= 0} {
 268                                set diff_3way 1
 269                        }
 270                }
 271
 272                $ui_diff conf -state normal
 273                if {!$diff_3way} {
 274                        set x [string index $line 0]
 275                        switch -- $x {
 276                        "@" {set tags da}
 277                        "+" {set tags dp}
 278                        "-" {set tags dm}
 279                        default {set tags {}}
 280                        }
 281                } else {
 282                        set x [string range $line 0 1]
 283                        switch -- $x {
 284                        default {set tags {}}
 285                        "@@" {set tags da}
 286                        "++" {set tags dp; set x " +"}
 287                        " +" {set tags {di bold}; set x "++"}
 288                        "+ " {set tags dni; set x "-+"}
 289                        "--" {set tags dm; set x " -"}
 290                        " -" {set tags {dm bold}; set x "--"}
 291                        "- " {set tags di; set x "+-"}
 292                        default {set tags {}}
 293                        }
 294                        set line [string replace $line 0 1 $x]
 295                }
 296                $ui_diff insert end $line $tags
 297                $ui_diff insert end "\n"
 298                $ui_diff conf -state disabled
 299        }
 300
 301        if {[eof $fd]} {
 302                close $fd
 303                set diff_active 0
 304                unlock_index
 305                set ui_status_value {Ready.}
 306        }
 307}
 308
 309######################################################################
 310##
 311## ui helpers
 312
 313proc mapcol {state path} {
 314        global all_cols
 315
 316        if {[catch {set r $all_cols($state)}]} {
 317                puts "error: no column for state={$state} $path"
 318                return o
 319        }
 320        return $r
 321}
 322
 323proc mapicon {state path} {
 324        global all_icons
 325
 326        if {[catch {set r $all_icons($state)}]} {
 327                puts "error: no icon for state={$state} $path"
 328                return file_plain
 329        }
 330        return $r
 331}
 332
 333proc mapdesc {state path} {
 334        global all_descs
 335
 336        if {[catch {set r $all_descs($state)}]} {
 337                puts "error: no desc for state={$state} $path"
 338                return $state
 339        }
 340        return $r
 341}
 342
 343proc bsearch {w path} {
 344        set hi [expr [lindex [split [$w index end] .] 0] - 2]
 345        if {$hi == 0} {
 346                return -1
 347        }
 348        set lo 0
 349        while {$lo < $hi} {
 350                set mi [expr [expr $lo + $hi] / 2]
 351                set ti [expr $mi + 1]
 352                set cmp [string compare [$w get $ti.1 $ti.end] $path]
 353                if {$cmp < 0} {
 354                        set lo $ti
 355                } elseif {$cmp == 0} {
 356                        return $mi
 357                } else {
 358                        set hi $mi
 359                }
 360        }
 361        return -[expr $lo + 1]
 362}
 363
 364proc merge_state {path state} {
 365        global file_states
 366
 367        if {[array names file_states -exact $path] == {}}  {
 368                set o __
 369                set s [list $o none none]
 370        } else {
 371                set s $file_states($path)
 372                set o [lindex $s 0]
 373        }
 374
 375        set m [lindex $s 0]
 376        if {[string index $state 0] == "_"} {
 377                set state [string index $m 0][string index $state 1]
 378        } elseif {[string index $state 0] == "*"} {
 379                set state _[string index $state 1]
 380        }
 381
 382        if {[string index $state 1] == "_"} {
 383                set state [string index $state 0][string index $m 1]
 384        } elseif {[string index $state 1] == "*"} {
 385                set state [string index $state 0]_
 386        }
 387
 388        set file_states($path) [lreplace $s 0 0 $state]
 389        return $o
 390}
 391
 392proc display_file {path state} {
 393        global ui_index ui_other file_states
 394
 395        set old_m [merge_state $path $state]
 396        set s $file_states($path)
 397        set m [lindex $s 0]
 398
 399        if {[mapcol $m $path] == "o"} {
 400                set ii 1
 401                set ai 2
 402                set iw $ui_index
 403                set aw $ui_other
 404        } else {
 405                set ii 2
 406                set ai 1
 407                set iw $ui_other
 408                set aw $ui_index
 409        }
 410
 411        set d [lindex $s $ii]
 412        if {$d != "none"} {
 413                set lno [bsearch $iw $path]
 414                if {$lno >= 0} {
 415                        incr lno
 416                        $iw conf -state normal
 417                        $iw delete $lno.0 [expr $lno + 1].0
 418                        $iw conf -state disabled
 419                        set s [lreplace $s $ii $ii none]
 420                }
 421        }
 422
 423        set d [lindex $s $ai]
 424        if {$d == "none"} {
 425                set lno [expr abs([bsearch $aw $path] + 1) + 1]
 426                $aw conf -state normal
 427                set ico [$aw image create $lno.0 \
 428                        -align center -padx 5 -pady 1 \
 429                        -image [mapicon $m $path]]
 430                $aw insert $lno.1 "$path\n"
 431                $aw conf -state disabled
 432                set file_states($path) [lreplace $s $ai $ai [list $ico]]
 433        } elseif {[mapicon $m $path] != [mapicon $old_m $path]} {
 434                set ico [lindex $d 0]
 435                $aw image conf $ico -image [mapicon $m $path]
 436        }
 437}
 438
 439proc with_update_index {body} {
 440        global update_index_fd
 441
 442        if {$update_index_fd == {}} {
 443                if {![lock_index update]} return
 444                set update_index_fd [open \
 445                        "| git update-index --add --remove -z --stdin" \
 446                        w]
 447                fconfigure $update_index_fd -translation binary
 448                uplevel 1 $body
 449                close $update_index_fd
 450                set update_index_fd {}
 451                unlock_index
 452        } else {
 453                uplevel 1 $body
 454        }
 455}
 456
 457proc update_index {path} {
 458        global update_index_fd
 459
 460        if {$update_index_fd == {}} {
 461                error {not in with_update_index}
 462        } else {
 463                puts -nonewline $update_index_fd "$path\0"
 464        }
 465}
 466
 467proc toggle_mode {path} {
 468        global file_states
 469
 470        set s $file_states($path)
 471        set m [lindex $s 0]
 472
 473        switch -- $m {
 474        AM -
 475        _O {set new A*}
 476        _M -
 477        MM {set new M*}
 478        _D {set new D*}
 479        default {return}
 480        }
 481
 482        with_update_index {update_index $path}
 483        display_file $path $new
 484}
 485
 486######################################################################
 487##
 488## icons
 489
 490set filemask {
 491#define mask_width 14
 492#define mask_height 15
 493static unsigned char mask_bits[] = {
 494   0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
 495   0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
 496   0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
 497}
 498
 499image create bitmap file_plain -background white -foreground black -data {
 500#define plain_width 14
 501#define plain_height 15
 502static unsigned char plain_bits[] = {
 503   0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
 504   0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
 505   0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
 506} -maskdata $filemask
 507
 508image create bitmap file_mod -background white -foreground blue -data {
 509#define mod_width 14
 510#define mod_height 15
 511static unsigned char mod_bits[] = {
 512   0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
 513   0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
 514   0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
 515} -maskdata $filemask
 516
 517image create bitmap file_fulltick -background white -foreground "#007000" -data {
 518#define file_fulltick_width 14
 519#define file_fulltick_height 15
 520static unsigned char file_fulltick_bits[] = {
 521   0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
 522   0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
 523   0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
 524} -maskdata $filemask
 525
 526image create bitmap file_parttick -background white -foreground "#005050" -data {
 527#define parttick_width 14
 528#define parttick_height 15
 529static unsigned char parttick_bits[] = {
 530   0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
 531   0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
 532   0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
 533} -maskdata $filemask
 534
 535image create bitmap file_question -background white -foreground black -data {
 536#define file_question_width 14
 537#define file_question_height 15
 538static unsigned char file_question_bits[] = {
 539   0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
 540   0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
 541   0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
 542} -maskdata $filemask
 543
 544image create bitmap file_removed -background white -foreground red -data {
 545#define file_removed_width 14
 546#define file_removed_height 15
 547static unsigned char file_removed_bits[] = {
 548   0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
 549   0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
 550   0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
 551} -maskdata $filemask
 552
 553image create bitmap file_merge -background white -foreground blue -data {
 554#define file_merge_width 14
 555#define file_merge_height 15
 556static unsigned char file_merge_bits[] = {
 557   0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
 558   0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
 559   0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
 560} -maskdata $filemask
 561
 562set max_status_desc 0
 563foreach i {
 564                {__ i plain    "Unmodified"}
 565                {_M i mod      "Modified"}
 566                {M_ i fulltick "Checked in"}
 567                {MM i parttick "Partially checked in"}
 568
 569                {_O o plain    "Untracked"}
 570                {A_ o fulltick "Added"}
 571                {AM o parttick "Partially added"}
 572
 573                {_D i question "Missing"}
 574                {D_ i removed  "Removed"}
 575                {DD i removed  "Removed"}
 576                {DO i removed  "Removed (still exists)"}
 577
 578                {UM i merge    "Merge conflicts"}
 579                {U_ i merge    "Merge conflicts"}
 580        } {
 581        if {$max_status_desc < [string length [lindex $i 3]]} {
 582                set max_status_desc [string length [lindex $i 3]]
 583        }
 584        set all_cols([lindex $i 0]) [lindex $i 1]
 585        set all_icons([lindex $i 0]) file_[lindex $i 2]
 586        set all_descs([lindex $i 0]) [lindex $i 3]
 587}
 588unset filemask i
 589
 590######################################################################
 591##
 592## util
 593
 594proc error_popup {msg} {
 595        set w .error
 596        toplevel $w
 597        wm transient $w .
 598        show_msg $w $w $msg
 599}
 600
 601proc show_msg {w top msg} {
 602        message $w.m -text $msg -justify center -aspect 400
 603        pack $w.m -side top -fill x -padx 20 -pady 20
 604        button $w.ok -text OK -command "destroy $top"
 605        pack $w.ok -side bottom -fill x
 606        bind $top <Visibility> "grab $top; focus $top"
 607        bind $top <Key-Return> "destroy $top"
 608        tkwait window $top
 609}
 610
 611######################################################################
 612##
 613## ui commands
 614
 615set starting_gitk_msg {Please wait... Starting gitk...}
 616proc do_gitk {} {
 617        global tcl_platform ui_status_value starting_gitk_msg
 618
 619        set ui_status_value $starting_gitk_msg
 620        after 5000 {
 621                if {$ui_status_value == $starting_gitk_msg} {
 622                        set ui_status_value {Ready.}
 623                }
 624        }
 625
 626    if {$tcl_platform(platform) == "windows"} {
 627                exec sh -c gitk &
 628        } else {
 629                exec gitk &
 630        }
 631}
 632
 633proc do_quit {} {
 634        global gitdir ui_comm
 635
 636        set save [file join $gitdir GITGUI_MSG]
 637        if {[$ui_comm edit modified]
 638            && [string trim [$ui_comm get 0.0 end]] != {}} {
 639                catch {
 640                        set fd [open $save w]
 641                        puts $fd [string trim [$ui_comm get 0.0 end]]
 642                        close $fd
 643                }
 644        } elseif {[file exists $save]} {
 645                file delete $save
 646        }
 647
 648        destroy .
 649}
 650
 651proc do_rescan {} {
 652        update_status
 653}
 654
 655proc do_checkin_all {} {
 656        global checkin_active ui_status_value
 657
 658        if {$checkin_active || ![lock_index begin-update]} return
 659
 660        set checkin_active 1
 661        set ui_status_value {Checking in all files...}
 662        after 1 {
 663                with_update_index {
 664                        foreach path [array names file_states] {
 665                                set s $file_states($path)
 666                                set m [lindex $s 0]
 667                                switch -- $m {
 668                                AM -
 669                                MM -
 670                                _M -
 671                                _D {toggle_mode $path}
 672                                }
 673                        }
 674                }
 675                set checkin_active 0
 676                set ui_status_value {Ready.}
 677        }
 678}
 679
 680proc do_signoff {} {
 681        global ui_comm
 682
 683        catch {
 684                set me [exec git var GIT_COMMITTER_IDENT]
 685                if {[regexp {(.*) [0-9]+ [-+0-9]+$} $me me name]} {
 686                        set str "Signed-off-by: $name"
 687                        if {[$ui_comm get {end -1c linestart} {end -1c}] != $str} {
 688                                $ui_comm insert end "\n"
 689                                $ui_comm insert end $str
 690                                $ui_comm see end
 691                        }
 692                }
 693        }
 694}
 695
 696# shift == 1: left click
 697#          3: right click  
 698proc click {w x y shift wx wy} {
 699        global ui_index ui_other
 700
 701        set pos [split [$w index @$x,$y] .]
 702        set lno [lindex $pos 0]
 703        set col [lindex $pos 1]
 704        set path [$w get $lno.1 $lno.end]
 705        if {$path == {}} return
 706
 707        if {$col > 0 && $shift == 1} {
 708                $ui_index tag remove in_diff 0.0 end
 709                $ui_other tag remove in_diff 0.0 end
 710                $w tag add in_diff $lno.0 [expr $lno + 1].0
 711                show_diff $path
 712        }
 713}
 714
 715proc unclick {w x y} {
 716        set pos [split [$w index @$x,$y] .]
 717        set lno [lindex $pos 0]
 718        set col [lindex $pos 1]
 719        set path [$w get $lno.1 $lno.end]
 720        if {$path == {}} return
 721
 722        if {$col == 0} {
 723                toggle_mode $path
 724        }
 725}
 726
 727######################################################################
 728##
 729## ui init
 730
 731set mainfont {Helvetica 10}
 732set difffont {Courier 10}
 733set maincursor [. cget -cursor]
 734
 735switch -- $tcl_platform(platform) {
 736windows {set M1B Control; set M1T Ctrl}
 737default {set M1B M1; set M1T M1}
 738}
 739
 740# -- Menu Bar
 741menu .mbar -tearoff 0
 742.mbar add cascade -label Project -menu .mbar.project
 743.mbar add cascade -label Commit -menu .mbar.commit
 744.mbar add cascade -label Fetch -menu .mbar.fetch
 745.mbar add cascade -label Pull -menu .mbar.pull
 746. configure -menu .mbar
 747
 748# -- Project Menu
 749menu .mbar.project
 750.mbar.project add command -label Visualize \
 751        -command do_gitk \
 752        -font $mainfont
 753.mbar.project add command -label Quit \
 754        -command do_quit \
 755        -accelerator $M1T-Q \
 756        -font $mainfont
 757
 758# -- Commit Menu
 759menu .mbar.commit
 760.mbar.commit add command -label Rescan \
 761        -command do_rescan \
 762        -accelerator F5 \
 763        -font $mainfont
 764lappend disable_on_lock \
 765        [list .mbar.commit entryconf [.mbar.commit index last] -state]
 766.mbar.commit add command -label {Check-in All Files} \
 767        -command do_checkin_all \
 768        -accelerator $M1T-U \
 769        -font $mainfont
 770lappend disable_on_lock \
 771        [list .mbar.commit entryconf [.mbar.commit index last] -state]
 772.mbar.commit add command -label {Sign Off} \
 773        -command do_signoff \
 774        -accelerator $M1T-S \
 775        -font $mainfont
 776.mbar.commit add command -label Commit \
 777        -command do_commit \
 778        -accelerator $M1T-Return \
 779        -font $mainfont
 780lappend disable_on_lock \
 781        [list .mbar.commit entryconf [.mbar.commit index last] -state]
 782
 783# -- Fetch Menu
 784menu .mbar.fetch
 785
 786# -- Pull Menu
 787menu .mbar.pull
 788
 789# -- Main Window Layout
 790panedwindow .vpane -orient vertical
 791panedwindow .vpane.files -orient horizontal
 792.vpane add .vpane.files -sticky nsew -height 100 -width 400
 793pack .vpane -anchor n -side top -fill both -expand 1
 794
 795# -- Index File List
 796set ui_index .vpane.files.index.list
 797frame .vpane.files.index -height 100 -width 400
 798label .vpane.files.index.title -text {Modified Files} \
 799        -background green \
 800        -font $mainfont
 801text $ui_index -background white -borderwidth 0 \
 802        -width 40 -height 10 \
 803        -font $mainfont \
 804        -yscrollcommand {.vpane.files.index.sb set} \
 805        -cursor $maincursor \
 806        -state disabled
 807scrollbar .vpane.files.index.sb -command [list $ui_index yview]
 808pack .vpane.files.index.title -side top -fill x
 809pack .vpane.files.index.sb -side right -fill y
 810pack $ui_index -side left -fill both -expand 1
 811.vpane.files add .vpane.files.index -sticky nsew
 812
 813# -- Other (Add) File List
 814set ui_other .vpane.files.other.list
 815frame .vpane.files.other -height 100 -width 100
 816label .vpane.files.other.title -text {Untracked Files} \
 817        -background red \
 818        -font $mainfont
 819text $ui_other -background white -borderwidth 0 \
 820        -width 40 -height 10 \
 821        -font $mainfont \
 822        -yscrollcommand {.vpane.files.other.sb set} \
 823        -cursor $maincursor \
 824        -state disabled
 825scrollbar .vpane.files.other.sb -command [list $ui_other yview]
 826pack .vpane.files.other.title -side top -fill x
 827pack .vpane.files.other.sb -side right -fill y
 828pack $ui_other -side left -fill both -expand 1
 829.vpane.files add .vpane.files.other -sticky nsew
 830
 831$ui_index tag conf in_diff -font [concat $mainfont bold]
 832$ui_other tag conf in_diff -font [concat $mainfont bold]
 833
 834# -- Diff Header
 835set ui_fname_value {}
 836set ui_fstatus_value {}
 837frame .vpane.diff -height 200 -width 400
 838frame .vpane.diff.header
 839label .vpane.diff.header.l1 -text {File:} -font $mainfont
 840label .vpane.diff.header.l2 -textvariable ui_fname_value \
 841        -anchor w \
 842        -justify left \
 843        -font $mainfont
 844label .vpane.diff.header.l3 -text {Status:} -font $mainfont
 845label .vpane.diff.header.l4 -textvariable ui_fstatus_value \
 846        -width $max_status_desc \
 847        -anchor w \
 848        -justify left \
 849        -font $mainfont
 850pack .vpane.diff.header.l1 -side left
 851pack .vpane.diff.header.l2 -side left -fill x
 852pack .vpane.diff.header.l4 -side right
 853pack .vpane.diff.header.l3 -side right
 854
 855# -- Diff Body
 856frame .vpane.diff.body
 857set ui_diff .vpane.diff.body.t
 858text $ui_diff -background white -borderwidth 0 \
 859        -width 80 -height 15 -wrap none \
 860        -font $difffont \
 861        -xscrollcommand {.vpane.diff.body.sbx set} \
 862        -yscrollcommand {.vpane.diff.body.sby set} \
 863        -cursor $maincursor \
 864        -state disabled
 865scrollbar .vpane.diff.body.sbx -orient horizontal \
 866        -command [list $ui_diff xview]
 867scrollbar .vpane.diff.body.sby -orient vertical \
 868        -command [list $ui_diff yview]
 869pack .vpane.diff.body.sbx -side bottom -fill x
 870pack .vpane.diff.body.sby -side right -fill y
 871pack $ui_diff -side left -fill both -expand 1
 872pack .vpane.diff.header -side top -fill x
 873pack .vpane.diff.body -side bottom -fill both -expand 1
 874.vpane add .vpane.diff -stick nsew
 875
 876$ui_diff tag conf dm -foreground red
 877$ui_diff tag conf dp -foreground blue
 878$ui_diff tag conf da -font [concat $difffont bold]
 879$ui_diff tag conf di -foreground "#00a000"
 880$ui_diff tag conf dni -foreground "#a000a0"
 881$ui_diff tag conf bold -font [concat $difffont bold]
 882
 883# -- Commit Area
 884frame .vpane.commarea -height 150
 885.vpane add .vpane.commarea -stick nsew
 886
 887# -- Commit Area Buttons
 888frame .vpane.commarea.buttons
 889label .vpane.commarea.buttons.l -text {} \
 890        -anchor w \
 891        -justify left \
 892        -font $mainfont
 893pack .vpane.commarea.buttons.l -side top -fill x
 894pack .vpane.commarea.buttons -side left -fill y
 895
 896button .vpane.commarea.buttons.rescan -text {Rescan} \
 897        -command do_rescan \
 898        -font $mainfont
 899pack .vpane.commarea.buttons.rescan -side top -fill x
 900lappend disable_on_lock {.vpane.commarea.buttons.rescan conf -state}
 901
 902button .vpane.commarea.buttons.ciall -text {Check-in All} \
 903        -command do_checkin_all \
 904        -font $mainfont
 905pack .vpane.commarea.buttons.ciall -side top -fill x
 906lappend disable_on_lock {.vpane.commarea.buttons.ciall conf -state}
 907
 908button .vpane.commarea.buttons.signoff -text {Sign Off} \
 909        -command do_signoff \
 910        -font $mainfont
 911pack .vpane.commarea.buttons.signoff -side top -fill x
 912
 913button .vpane.commarea.buttons.commit -text {Commit} \
 914        -command do_commit \
 915        -font $mainfont
 916pack .vpane.commarea.buttons.commit -side top -fill x
 917lappend disable_on_lock {.vpane.commarea.buttons.commit conf -state}
 918
 919# -- Commit Message Buffer
 920frame .vpane.commarea.buffer
 921set ui_comm .vpane.commarea.buffer.t
 922label .vpane.commarea.buffer.l -text {Commit Message:} \
 923        -anchor w \
 924        -justify left \
 925        -font $mainfont
 926text $ui_comm -background white -borderwidth 1 \
 927        -relief sunken \
 928        -width 75 -height 10 -wrap none \
 929        -font $difffont \
 930        -yscrollcommand {.vpane.commarea.buffer.sby set} \
 931        -cursor $maincursor
 932scrollbar .vpane.commarea.buffer.sby -command [list $ui_comm yview]
 933pack .vpane.commarea.buffer.l -side top -fill x
 934pack .vpane.commarea.buffer.sby -side right -fill y
 935pack $ui_comm -side left -fill y
 936pack .vpane.commarea.buffer -side left -fill y
 937
 938# -- Status Bar
 939set ui_status_value {Initializing...}
 940label .status -textvariable ui_status_value \
 941        -anchor w \
 942        -justify left \
 943        -borderwidth 1 \
 944        -relief sunken \
 945        -font $mainfont
 946pack .status -anchor w -side bottom -fill x
 947
 948# -- Key Bindings
 949bind . <Destroy> do_quit
 950bind . <Key-F5> do_rescan
 951bind . <$M1B-Key-r> do_rescan
 952bind . <$M1B-Key-R> do_rescan
 953bind . <$M1B-Key-s> do_signoff
 954bind . <$M1B-Key-S> do_signoff
 955bind . <$M1B-Key-u> do_checkin_all
 956bind . <$M1B-Key-U> do_checkin_all
 957bind . <$M1B-Key-Return> do_commit
 958bind . <$M1B-Key-q> do_quit
 959bind . <$M1B-Key-Q> do_quit
 960foreach i [list $ui_index $ui_other] {
 961        bind $i <Button-1> {click %W %x %y 1 %X %Y; break}
 962        bind $i <Button-3> {click %W %x %y 3 %X %Y; break}
 963        bind $i <ButtonRelease-1> {unclick %W %x %y; break}
 964}
 965unset i M1B M1T
 966
 967######################################################################
 968##
 969## main
 970
 971if {[catch {set gitdir [exec git rev-parse --git-dir]} err]} {
 972        show_msg {} . "Cannot find the git directory: $err"
 973        exit 1
 974}
 975
 976wm title . "git-ui ([file normalize [file dirname $gitdir]])"
 977focus -force $ui_comm
 978update_status