gitk: Use a text widget for the file list
[git.git] / gitk
diff --git a/gitk b/gitk
index 4ee5122..bd205f8 100755 (executable)
--- a/gitk
+++ b/gitk
@@ -293,10 +293,16 @@ proc readrefs {} {
            match id path]} {
            continue
        }
+       if {[regexp {^remotes/.*/HEAD$} $path match]} {
+           continue
+       }
        if {![regexp {^(tags|heads)/(.*)$} $path match type name]} {
            set type others
            set name $path
        }
+       if {[regexp {^remotes/} $path match]} {
+           set type heads
+       }
        if {$type == "tags"} {
            set tagids($name) $id
            lappend idtags($id) $name
@@ -360,9 +366,11 @@ proc makewindow {} {
     menu .bar.view -font $uifont
     .bar add cascade -label "View" -menu .bar.view
     .bar.view add command -label "New view..." -command newview
+    .bar.view add command -label "Edit view..." -command editview
     .bar.view add command -label "Delete view" -command delview -state disabled
     .bar.view add separator
-    .bar.view add command -label "All files" -command {showview 0}
+    .bar.view add radiobutton -label "All files" -command {showview 0} \
+       -variable selectedview -value 0
     menu .bar.help
     .bar add cascade -label "Help" -menu .bar.help
     .bar.help add command -label "About gitk" -command about
@@ -473,7 +481,7 @@ proc makewindow {} {
     set ctext .ctop.cdet.left.ctext
     text $ctext -bg white -state disabled -font $textfont \
        -width $geometry(ctextw) -height $geometry(ctexth) \
-       -yscrollcommand ".ctop.cdet.left.sb set" -wrap none
+       -yscrollcommand scrolltext -wrap none
     scrollbar .ctop.cdet.left.sb -command "$ctext yview"
     pack .ctop.cdet.left.sb -side right -fill y
     pack $ctext -side left -fill both -expand 1
@@ -507,11 +515,16 @@ proc makewindow {} {
 
     frame .ctop.cdet.right
     set cflist .ctop.cdet.right.cfiles
-    listbox $cflist -bg white -selectmode extended -width $geometry(cflistw) \
-       -yscrollcommand ".ctop.cdet.right.sb set" -font $mainfont
+    set indent [font measure $mainfont "nn"]
+    text $cflist -width $geometry(cflistw) -background white -font $mainfont \
+       -tabs [list $indent [expr {2 * $indent}]] \
+       -yscrollcommand ".ctop.cdet.right.sb set" \
+       -cursor [. cget -cursor] \
+       -spacing1 1 -spacing3 1
     scrollbar .ctop.cdet.right.sb -command "$cflist yview"
     pack .ctop.cdet.right.sb -side right -fill y
     pack $cflist -side left -fill both -expand 1
+    $cflist tag configure highlight -background yellow
     .ctop.cdet add .ctop.cdet.right
     bind .ctop.cdet <Configure> {resizecdetpanes %W %w}
 
@@ -563,12 +576,13 @@ proc makewindow {} {
     bind . <Control-KP_Add> {incrfont 1}
     bind . <Control-minus> {incrfont -1}
     bind . <Control-KP_Subtract> {incrfont -1}
-    bind $cflist <<ListboxSelect>> listboxsel
     bind . <Destroy> {savestuff %W}
     bind . <Button-1> "click %W"
     bind $fstring <Key-Return> dofind
     bind $sha1entry <Key-Return> gotocommit
     bind $sha1entry <<PasteSelection>> clearsha1
+    bind $cflist <1> {sel_flist %W %x %y; break}
+    bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
 
     set maincursor [. cget -cursor]
     set textcursor [$ctext cget -cursor]
@@ -632,6 +646,7 @@ proc savestuff {w} {
     global canv canv2 canv3 ctext cflist mainfont textfont uifont
     global stuffsaved findmergefiles maxgraphpct
     global maxwidth
+    global viewname viewfiles viewperm nextviewnum
 
     if {$stuffsaved} return
     if {![winfo viewable .]} return
@@ -655,6 +670,13 @@ proc savestuff {w} {
        set wid [expr {([winfo width $cflist] - 11) \
                           / [font measure [$cflist cget -font] "0"]}]
        puts $f "set geometry(cflistw) $wid"
+       puts -nonewline $f "set permviews {"
+       for {set v 0} {$v < $nextviewnum} {incr v} {
+           if {$viewperm($v)} {
+               puts $f "{[list $viewname($v) $viewfiles($v)]}"
+           }
+       }
+       puts $f "}"
        close $f
        file rename -force "~/.gitk-new" "~/.gitk"
     }
@@ -796,28 +818,155 @@ f                Scroll diff view to next file
     pack $w.ok -side bottom
 }
 
+# Procedures for manipulating the file list window at the
+# bottom right of the overall window.
+proc init_flist {first} {
+    global cflist cflist_top cflist_bot selectedline difffilestart
+
+    $cflist conf -state normal
+    $cflist delete 0.0 end
+    if {$first ne {}} {
+       $cflist insert end $first
+       set cflist_top 1
+       set cflist_bot 1
+       $cflist tag add highlight 1.0 "1.0 lineend"
+    } else {
+       catch {unset cflist_top}
+    }
+    $cflist conf -state disabled
+    set difffilestart {}
+}
+
+proc add_flist {f} {
+    global flistmode cflist
+
+    $cflist conf -state normal
+    if {$flistmode eq "flat"} {
+       $cflist insert end "\n$f"
+    }
+    $cflist conf -state disabled
+}
+
+proc sel_flist {w x y} {
+    global flistmode ctext difffilestart cflist cflist_top
+
+    if {![info exists cflist_top]} return
+    set l [lindex [split [$w index "@$x,$y"] "."] 0]
+    if {$flistmode eq "flat"} {
+       if {$l == 1} {
+           $ctext yview 1.0
+       } else {
+           catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]}
+       }
+       highlight_flist $l
+    }
+}
+
+proc scrolltext {f0 f1} {
+    global cflist_top
+
+    .ctop.cdet.left.sb set $f0 $f1
+    if {[info exists cflist_top]} {
+       highlight_flist $cflist_top
+    }
+}
+
+# Given an index $tl in the $ctext window, this works out which line
+# of the $cflist window displays the filename whose patch is shown
+# at the given point in the $ctext window.  $ll is a hint about which
+# line it might be, and is used as the starting point of the search.
+proc ctext_index {tl ll} {
+    global ctext difffilestart
+
+    while {$ll >= 2 && [$ctext compare $tl < \
+                           [lindex $difffilestart [expr {$ll - 2}]]]} {
+       incr ll -1
+    }
+    set nfiles [llength $difffilestart]
+    while {$ll - 1 < $nfiles && [$ctext compare $tl >= \
+                           [lindex $difffilestart [expr {$ll - 1}]]]} {
+       incr ll
+    }
+    return $ll
+}
+
+proc highlight_flist {ll} {
+    global ctext cflist cflist_top cflist_bot difffilestart
+
+    if {![info exists difffilestart] || [llength $difffilestart] == 0} return
+    set ll [ctext_index [$ctext index @0,1] $ll]
+    set lb $cflist_bot
+    if {$lb < $ll} {
+       set lb $ll
+    }
+    set y [expr {[winfo height $ctext] - 2}]
+    set lb [ctext_index [$ctext index @0,$y] $lb]
+    if {$ll != $cflist_top || $lb != $cflist_bot} {
+       $cflist tag remove highlight $cflist_top.0 "$cflist_bot.0 lineend"
+       for {set l $ll} {$l <= $lb} {incr l} {
+           $cflist tag add highlight $l.0 "$l.0 lineend"
+       }
+       set cflist_top $ll
+       set cflist_bot $lb
+    }
+}
+
+# Code to implement multiple views
+
 proc newview {} {
-    global newviewname nextviewnum newviewtop
+    global nextviewnum newviewname newviewperm uifont
 
     set top .gitkview
     if {[winfo exists $top]} {
        raise $top
        return
     }
-    set newviewtop $top
+    set newviewname($nextviewnum) "View $nextviewnum"
+    set newviewperm($nextviewnum) 0
+    vieweditor $top $nextviewnum "Gitk view definition" 
+}
+
+proc editview {} {
+    global curview
+    global viewname viewperm newviewname newviewperm
+
+    set top .gitkvedit-$curview
+    if {[winfo exists $top]} {
+       raise $top
+       return
+    }
+    set newviewname($curview) $viewname($curview)
+    set newviewperm($curview) $viewperm($curview)
+    vieweditor $top $curview "Gitk: edit view $viewname($curview)"
+}
+
+proc vieweditor {top n title} {
+    global newviewname newviewperm viewfiles
+    global uifont
+
     toplevel $top
-    wm title $top "Gitk view definition"
-    label $top.nl -text "Name"
-    entry $top.name -width 20 -textvariable newviewname
-    set newviewname "View $nextviewnum"
-    grid $top.nl $top.name -sticky w
-    label $top.l -text "Files and directories to include:"
-    grid $top.l - -sticky w -pady 10
-    text $top.t -width 30 -height 10
-    grid $top.t - -sticky w
+    wm title $top $title
+    label $top.nl -text "Name" -font $uifont
+    entry $top.name -width 20 -textvariable newviewname($n)
+    grid $top.nl $top.name -sticky w -pady 5
+    checkbutton $top.perm -text "Remember this view" -variable newviewperm($n)
+    grid $top.perm - -pady 5 -sticky w
+    message $top.l -aspect 500 -font $uifont \
+       -text "Enter files and directories to include, one per line:"
+    grid $top.l - -sticky w
+    text $top.t -width 40 -height 10 -background white
+    if {[info exists viewfiles($n)]} {
+       foreach f $viewfiles($n) {
+           $top.t insert end $f
+           $top.t insert end "\n"
+       }
+       $top.t delete {end - 1c} end
+       $top.t mark set insert 0.0
+    }
+    grid $top.t - -sticky w -padx 5
     frame $top.buts
-    button $top.buts.ok -text "OK" -command newviewok
-    button $top.buts.can -text "Cancel" -command newviewcan
+    button $top.buts.ok -text "OK" -command [list newviewok $top $n]
+    button $top.buts.can -text "Cancel" -command [list destroy $top]
     grid $top.buts.ok $top.buts.can
     grid columnconfigure $top.buts 0 -weight 1 -uniform a
     grid columnconfigure $top.buts 1 -weight 1 -uniform a
@@ -825,47 +974,67 @@ proc newview {} {
     focus $top.t
 }
 
-proc newviewok {} {
-    global newviewtop nextviewnum
-    global viewname viewfiles
+proc viewmenuitem {n} {
+    set nmenu [.bar.view index end]
+    set targetcmd [list showview $n]
+    for {set i 6} {$i <= $nmenu} {incr i} {
+       if {[.bar.view entrycget $i -command] eq $targetcmd} {
+           return $i
+       }
+    }
+    return {}
+}
+
+proc newviewok {top n} {
+    global nextviewnum newviewperm newviewname
+    global viewname viewfiles viewperm selectedview curview
 
-    set n $nextviewnum
-    incr nextviewnum
-    set viewname($n) [$newviewtop.name get]
     set files {}
-    foreach f [split [$newviewtop.t get 0.0 end] "\n"] {
+    foreach f [split [$top.t get 0.0 end] "\n"] {
        set ft [string trim $f]
        if {$ft ne {}} {
            lappend files $ft
        }
     }
-    set viewfiles($n) $files
-    catch {destroy $newviewtop}
-    unset newviewtop
-    .bar.view add command -label $viewname($n) -command [list showview $n]
-    after idle showview $n
-}
-
-proc newviewcan {} {
-    global newviewtop
-
-    catch {destroy $newviewtop}
-    unset newviewtop
+    if {![info exists viewfiles($n)]} {
+       # creating a new view
+       incr nextviewnum
+       set viewname($n) $newviewname($n)
+       set viewperm($n) $newviewperm($n)
+       set viewfiles($n) $files
+       .bar.view add radiobutton -label $viewname($n) \
+           -command [list showview $n] -variable selectedview -value $n
+       after idle showview $n
+    } else {
+       # editing an existing view
+       set viewperm($n) $newviewperm($n)
+       if {$newviewname($n) ne $viewname($n)} {
+           set viewname($n) $newviewname($n)
+           set i [viewmenuitem $n]
+           if {$i ne {}} {
+               .bar.view entryconf $i -label $viewname($n)
+           }
+       }
+       if {$files ne $viewfiles($n)} {
+           set viewfiles($n) $files
+           if {$curview == $n} {
+               after idle updatecommits
+           }
+       }
+    }
+    catch {destroy $top}
 }
 
 proc delview {} {
-    global curview viewdata
+    global curview viewdata viewperm
 
     if {$curview == 0} return
-    set nmenu [.bar.view index end]
-    set targetcmd [list showview $curview]
-    for {set i 5} {$i <= $nmenu} {incr i} {
-       if {[.bar.view entrycget $i -command] eq $targetcmd} {
-           .bar.view delete $i
-           break
-       }
+    set i [viewmenuitem $curview]
+    if {$i ne {}} {
+       .bar.view delete $i
     }
     set viewdata($curview) {}
+    set viewperm($curview) 0
     showview 0
 }
 
@@ -898,6 +1067,7 @@ proc showview {n} {
     global pending_select phase
     global commitidx rowlaidout rowoptim linesegends leftover
     global commfd nextupdate
+    global selectedview
 
     if {$n == $curview} return
     set selid {}
@@ -939,7 +1109,9 @@ proc showview {n} {
     clear_display
 
     set curview $n
+    set selectedview $n
     .bar.view entryconf 2 -state [expr {$n == 0? "disabled": "normal"}]
+    .bar.view entryconf 3 -state [expr {$n == 0? "disabled": "normal"}]
 
     if {![info exists viewdata($n)]} {
        set pending_select $selid
@@ -1007,6 +1179,11 @@ proc showview {n} {
     } else {
        . config -cursor watch
        settextcursor watch
+       if {$phase eq "getcommits"} {
+           global mainfont
+           $canv create text 3 3 -anchor nw -text "Reading commits..." \
+               -font $mainfont -tags textitems
+       }
     }
 }
 
@@ -2018,6 +2195,14 @@ proc drawtags {id x xt y1} {
            set xl [expr {$xl - $delta/2}]
            $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
                -width 1 -outline black -fill $col -tags tag.$id
+           if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} {
+               set rwid [font measure $mainfont $remoteprefix]
+               set xi [expr {$x + 1}]
+               set yti [expr {$yt + 1}]
+               set xri [expr {$x + $rwid}]
+               $canv create polygon $xi $yti $xri $yti $xri $yb $xi $yb \
+                       -width 0 -fill "#ffddaa" -tags tag.$id
+           }
        }
        set t [$canv create text $xl $y1 -anchor w -text $tag \
                   -font $mainfont -tags tag.$id]
@@ -2634,7 +2819,7 @@ proc selectline {l isnew} {
     global canv canv2 canv3 ctext commitinfo selectedline
     global displayorder linehtag linentag linedtag
     global canvy0 linespc parentlist childlist
-    global cflist currentid sha1entry
+    global currentid sha1entry
     global commentend idtags linknum
     global mergemax numcommits pending_select
 
@@ -2757,8 +2942,7 @@ proc selectline {l isnew} {
     $ctext conf -state disabled
     set commentend [$ctext index "end - 1c"]
 
-    $cflist delete 0 end
-    $cflist insert end "Comments"
+    init_flist "Comments"
     if {[llength $olds] <= 1} {
        startdiff $id
     } else {
@@ -2876,12 +3060,11 @@ proc goforw {} {
 
 proc mergediff {id l} {
     global diffmergeid diffopts mdifffd
-    global difffilestart diffids
+    global diffids
     global parentlist
 
     set diffmergeid $id
     set diffids $id
-    catch {unset difffilestart}
     # this doesn't seem to actually affect anything...
     set env(GIT_DIFF_OPTS) $diffopts
     set cmd [concat | git-diff-tree --no-commit-id --cc $id]
@@ -2916,11 +3099,10 @@ proc getmergediffline {mdf id np} {
        # start of a new file
        $ctext insert end "\n"
        set here [$ctext index "end - 1c"]
-       set i [$cflist index end]
-       $ctext mark set fmark.$i $here
-       $ctext mark gravity fmark.$i left
-       set difffilestart([expr {$i-1}]) $here
-       $cflist insert end $fname
+       $ctext mark set f:$fname $here
+       $ctext mark gravity f:$fname left
+       lappend difffilestart $here
+       add_flist $fname
        set l [expr {(78 - [string length $fname]) / 2}]
        set pad [string range "----------------------------------------" 1 $l]
        $ctext insert end "$pad $fname $pad\n" filesep
@@ -2991,7 +3173,7 @@ proc startdiff {ids} {
 proc addtocflist {ids} {
     global treediffs cflist
     foreach f $treediffs($ids) {
-       $cflist insert end $f
+       add_flist $f
     }
     getblobdiffs $ids
 }
@@ -3031,7 +3213,7 @@ proc gettreediffline {gdtf ids} {
 
 proc getblobdiffs {ids} {
     global diffopts blobdifffd diffids env curdifftag curtagstart
-    global difffilestart nextupdate diffinhdr treediffs
+    global nextupdate diffinhdr treediffs
 
     set env(GIT_DIFF_OPTS) $diffopts
     set cmd [concat | git-diff-tree --no-commit-id -r -p -C $ids]
@@ -3044,7 +3226,6 @@ proc getblobdiffs {ids} {
     set blobdifffd($ids) $bdf
     set curdifftag Comments
     set curtagstart 0.0
-    catch {unset difffilestart}
     fileevent $bdf readable [list getblobdiffline $bdf $diffids]
     set nextupdate [expr {[clock clicks -milliseconds] + 100}]
 }
@@ -3072,24 +3253,15 @@ proc getblobdiffline {bdf ids} {
        # start of a new file
        $ctext insert end "\n"
        $ctext tag add $curdifftag $curtagstart end
-       set curtagstart [$ctext index "end - 1c"]
-       set header $newname
        set here [$ctext index "end - 1c"]
-       set i [lsearch -exact $treediffs($diffids) $fname]
-       if {$i >= 0} {
-           set difffilestart($i) $here
-           incr i
-           $ctext mark set fmark.$i $here
-           $ctext mark gravity fmark.$i left
-       }
+       set curtagstart $here
+       set header $newname
+       lappend difffilestart $here
+       $ctext mark set f:$fname $here
+       $ctext mark gravity f:$fname left
        if {$newname != $fname} {
-           set i [lsearch -exact $treediffs($diffids) $newname]
-           if {$i >= 0} {
-               set difffilestart($i) $here
-               incr i
-               $ctext mark set fmark.$i $here
-               $ctext mark gravity fmark.$i left
-           }
+           $ctext mark set f:$newfname $here
+           $ctext mark gravity f:$newfname left
        }
        set curdifftag "f:$fname"
        $ctext tag delete $curdifftag
@@ -3138,26 +3310,11 @@ proc getblobdiffline {bdf ids} {
 proc nextfile {} {
     global difffilestart ctext
     set here [$ctext index @0,0]
-    for {set i 0} {[info exists difffilestart($i)]} {incr i} {
-       if {[$ctext compare $difffilestart($i) > $here]} {
-           if {![info exists pos]
-               || [$ctext compare $difffilestart($i) < $pos]} {
-               set pos $difffilestart($i)
-           }
+    foreach loc $difffilestart {
+       if {[$ctext compare $loc > $here]} {
+           $ctext yview $loc
        }
     }
-    if {[info exists pos]} {
-       $ctext yview $pos
-    }
-}
-
-proc listboxsel {} {
-    global ctext cflist currentid
-    if {![info exists currentid]} return
-    set sel [lsort [$cflist curselection]]
-    if {$sel eq {}} return
-    set first [lindex $sel 0]
-    catch {$ctext yview fmark.$first}
 }
 
 proc setcoords {} {
@@ -3368,7 +3525,7 @@ proc arrowjump {id n y} {
 }
 
 proc lineclick {x y id isnew} {
-    global ctext commitinfo childlist commitrow cflist canv thickerline
+    global ctext commitinfo childlist commitrow canv thickerline
 
     if {![info exists commitinfo($id)] && ![getcommit $id]} return
     unmarkmatches
@@ -3425,8 +3582,7 @@ proc lineclick {x y id isnew} {
        }
     }
     $ctext conf -state disabled
-
-    $cflist delete 0 end
+    init_flist {}
 }
 
 proc normalline {} {
@@ -3484,15 +3640,14 @@ proc diffvssel {dirn} {
 }
 
 proc doseldiff {oldid newid} {
-    global ctext cflist
+    global ctext
     global commitinfo
 
     $ctext conf -state normal
     $ctext delete 0.0 end
     $ctext mark set fmark.0 0.0
     $ctext mark gravity fmark.0 left
-    $cflist delete 0 end
-    $cflist insert end "Top"
+    init_flist "Top"
     $ctext insert end "From "
     $ctext tag conf link -foreground blue -underline 1
     $ctext tag bind link <Enter> { %W configure -cursor hand2 }
@@ -3778,7 +3933,7 @@ proc rereadrefs {} {
 }
 
 proc showtag {tag isnew} {
-    global ctext cflist tagcontents tagids linknum
+    global ctext tagcontents tagids linknum
 
     if {$isnew} {
        addtohistory [list showtag $tag 0]
@@ -3793,7 +3948,7 @@ proc showtag {tag isnew} {
     }
     appendwithlinks $text
     $ctext conf -state disabled
-    $cflist delete 0 end
+    init_flist {}
 }
 
 proc doquit {} {
@@ -4175,6 +4330,7 @@ set fastdate 0
 set uparrowlen 7
 set downarrowlen 7
 set mingaplen 30
+set flistmode "flat"
 
 set colors {green red blue magenta darkgrey brown orange}
 
@@ -4209,7 +4365,9 @@ set optim_delay 16
 
 set nextviewnum 1
 set curview 0
+set selectedview 0
 set viewfiles(0) {}
+set viewperm(0) 0
 
 set stopped 0
 set stuffsaved 0
@@ -4232,10 +4390,26 @@ if {[lindex $revtreeargs end] eq "--"} {
 if {$cmdline_files ne {}} {
     # create a view for the files/dirs specified on the command line
     set curview 1
+    set selectedview 1
     set nextviewnum 2
     set viewname(1) "Command line"
     set viewfiles(1) $cmdline_files
-    .bar.view add command -label $viewname(1) -command {showview 1}
+    set viewperm(1) 0
+    .bar.view add radiobutton -label $viewname(1) -command {showview 1} \
+       -variable selectedview -value 1
     .bar.view entryconf 2 -state normal
+    .bar.view entryconf 3 -state normal
+}
+
+if {[info exists permviews]} {
+    foreach v $permviews {
+       set n $nextviewnum
+       incr nextviewnum
+       set viewname($n) [lindex $v 0]
+       set viewfiles($n) [lindex $v 1]
+       set viewperm($n) 1
+       .bar.view add radiobutton -label $viewname($n) \
+           -command [list showview $n] -variable selectedview -value $n
+    }
 }
 getcommits