gitk: Highlight paths of interest in tree view as well
[git.git] / gitk
diff --git a/gitk b/gitk
index 28f8233..2e04504 100755 (executable)
--- a/gitk
+++ b/gitk
@@ -19,13 +19,13 @@ proc gitdir {} {
 proc start_rev_list {view} {
     global startmsecs nextupdate ncmupdate
     global commfd leftover tclencoding datemode
-    global revtreeargs viewfiles commitidx
+    global viewargs viewfiles commitidx
 
     set startmsecs [clock clicks -milliseconds]
     set nextupdate [expr {$startmsecs + 100}]
     set ncmupdate 1
     set commitidx($view) 0
-    set args $revtreeargs
+    set args $viewargs($view)
     if {$viewfiles($view) ne {}} {
        set args [concat $args "--" $viewfiles($view)]
     }
@@ -69,9 +69,7 @@ proc getcommits {} {
     set phase getcommits
     initlayout
     start_rev_list $curview
-    $canv delete all
-    $canv create text 3 3 -anchor nw -text "Reading commits..." \
-       -font $mainfont -tags textitems
+    show_status "Reading commits..."
 }
 
 proc getcommitlines {fd view}  {
@@ -84,26 +82,33 @@ proc getcommitlines {fd view}  {
     set stuff [read $fd]
     if {$stuff == {}} {
        if {![eof $fd]} return
+       global viewname
        unset commfd($view)
+       notbusy $view
        # set it blocking so we wait for the process to terminate
        fconfigure $fd -blocking 1
-       if {![catch {close $fd} err]} {
-           notbusy $view
-           if {$view == $curview} {
-               after idle finishcommits
+       if {[catch {close $fd} err]} {
+           set fv {}
+           if {$view != $curview} {
+               set fv " for the \"$viewname($view)\" view"
            }
-           return
+           if {[string range $err 0 4] == "usage"} {
+               set err "Gitk: error reading commits$fv:\
+                       bad arguments to git-rev-list."
+               if {$viewname($view) eq "Command line"} {
+                   append err \
+                       "  (Note: arguments to gitk are passed to git-rev-list\
+                        to allow selection of commits to be displayed.)"
+               }
+           } else {
+               set err "Error reading commits$fv: $err"
+           }
+           error_popup $err
        }
-       if {[string range $err 0 4] == "usage"} {
-           set err \
-               "Gitk: error reading commits: bad arguments to git-rev-list.\
-               (Note: arguments to gitk are passed to git-rev-list\
-               to allow selection of commits to be displayed.)"
-       } else {
-           set err "Error reading commits: $err"
+       if {$view == $curview} {
+           after idle finishcommits
        }
-       error_popup $err
-       exit 1
+       return
     }
     set start 0
     set gotsome 0
@@ -182,7 +187,7 @@ proc getcommitlines {fd view}  {
        if {$view == $curview} {
            layoutmore
        } elseif {[info exists hlview] && $view == $hlview} {
-           highlightmore
+           vhighlightmore
        }
     }
     if {[clock clicks -milliseconds] >= $nextupdate} {
@@ -217,8 +222,8 @@ proc readcommit {id} {
 }
 
 proc updatecommits {} {
-    global viewdata curview revtreeargs phase displayorder
-    global children commitrow
+    global viewdata curview phase displayorder
+    global children commitrow selectedline thickerline
 
     if {$phase ne {}} {
        stop_rev_list
@@ -230,6 +235,8 @@ proc updatecommits {} {
        catch {unset commitrow($n,$id)}
     }
     set curview -1
+    catch {unset selectedline}
+    catch {unset thickerline}
     catch {unset viewdata($n)}
     readrefs
     showview $n
@@ -352,10 +359,7 @@ proc readrefs {} {
     close $refd
 }
 
-proc error_popup msg {
-    set w .error
-    toplevel $w
-    wm transient $w .
+proc show_error {w msg} {
     message $w.m -text $msg -justify center -aspect 400
     pack $w.m -side top -fill x -padx 20 -pady 20
     button $w.ok -text OK -command "destroy $w"
@@ -365,6 +369,13 @@ proc error_popup msg {
     tkwait window $w
 }
 
+proc error_popup msg {
+    set w .error
+    toplevel $w
+    wm transient $w .
+    show_error $w $msg
+}
+
 proc makewindow {} {
     global canv canv2 canv3 linespc charspc ctext cflist
     global textfont mainfont uifont
@@ -372,6 +383,7 @@ proc makewindow {} {
     global entries sha1entry sha1string sha1but
     global maincursor textcursor curtextcursor
     global rowctxmenu mergemax
+    global highlight_files highlight_names
 
     menu .bar
     .bar add cascade -label "File" -menu .bar.file
@@ -387,20 +399,14 @@ proc makewindow {} {
     .bar.edit configure -font $uifont
 
     menu .bar.view -font $uifont
-    menu .bar.view.hl -font $uifont -tearoff 0
     .bar add cascade -label "View" -menu .bar.view
     .bar.view add command -label "New view..." -command {newview 0}
     .bar.view add command -label "Edit view..." -command editview \
        -state disabled
     .bar.view add command -label "Delete view" -command delview -state disabled
-    .bar.view add cascade -label "Highlight" -menu .bar.view.hl
     .bar.view add separator
     .bar.view add radiobutton -label "All files" -command {showview 0} \
        -variable selectedview -value 0
-    .bar.view.hl add command -label "New view..." -command {newview 1}
-    .bar.view.hl add command -label "Remove" -command delhighlight \
-       -state disabled
-    .bar.view.hl add separator
     
     menu .bar.help
     .bar add cascade -label "Help" -menu .bar.help
@@ -427,6 +433,8 @@ proc makewindow {} {
     }
     frame .ctop.top
     frame .ctop.top.bar
+    frame .ctop.top.lbar
+    pack .ctop.top.lbar -side bottom -fill x
     pack .ctop.top.bar -side bottom -fill x
     set cscroll .ctop.top.csb
     scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
@@ -488,7 +496,7 @@ proc makewindow {} {
     set findstring {}
     set fstring .ctop.top.bar.findstring
     lappend entries $fstring
-    entry $fstring -width 30 -font $textfont -textvariable findstring -font $textfont
+    entry $fstring -width 30 -font $textfont -textvariable findstring
     pack $fstring -side left -expand 1 -fill x
     set findtype Exact
     set findtypemenu [tk_optionMenu .ctop.top.bar.findtype \
@@ -506,6 +514,31 @@ proc makewindow {} {
     # for making sure type==Exact whenever loc==Pickaxe
     trace add variable findloc write findlocchange
 
+    label .ctop.top.lbar.flabel -text "Highlight:  Commits touching paths:" \
+       -font $uifont
+    pack .ctop.top.lbar.flabel -side left -fill y
+    entry .ctop.top.lbar.fent -width 25 -font $textfont \
+       -textvariable highlight_files
+    trace add variable highlight_files write hfiles_change
+    lappend entries .ctop.top.lbar.fent
+    pack .ctop.top.lbar.fent -side left -fill x -expand 1
+    label .ctop.top.lbar.vlabel -text " OR in view" -font $uifont
+    pack .ctop.top.lbar.vlabel -side left -fill y
+    global viewhlmenu selectedhlview
+    set viewhlmenu [tk_optionMenu .ctop.top.lbar.vhl selectedhlview None]
+    $viewhlmenu entryconf 0 -command delvhighlight
+    $viewhlmenu conf -font $uifont
+    .ctop.top.lbar.vhl conf -font $uifont
+    pack .ctop.top.lbar.vhl -side left -fill y
+    label .ctop.top.lbar.alabel -text " OR author/committer:" \
+       -font $uifont
+    pack .ctop.top.lbar.alabel -side left -fill y
+    entry .ctop.top.lbar.aent -width 20 -font $textfont \
+       -textvariable highlight_names
+    trace add variable highlight_names write hnames_change
+    lappend entries .ctop.top.lbar.aent
+    pack .ctop.top.lbar.aent -side right -fill x -expand 1
+
     panedwindow .ctop.cdet -orient horizontal
     .ctop add .ctop.cdet
     frame .ctop.cdet.left
@@ -564,6 +597,7 @@ proc makewindow {} {
     pack $cflist -side left -fill both -expand 1
     $cflist tag configure highlight \
        -background [$cflist cget -selectbackground]
+    $cflist tag configure bold -font [concat $mainfont bold]
     .ctop.cdet add .ctop.cdet.right
     bind .ctop.cdet <Configure> {resizecdetpanes %W %w}
 
@@ -655,6 +689,7 @@ proc canvscan {op w x y} {
 proc scrollcanv {cscroll f0 f1} {
     $cscroll set $f0 $f1
     drawfrac $f0 $f1
+    flushhighlights
 }
 
 # when we make a key binding for the toplevel, make sure
@@ -686,7 +721,7 @@ proc savestuff {w} {
     global canv canv2 canv3 ctext cflist mainfont textfont uifont
     global stuffsaved findmergefiles maxgraphpct
     global maxwidth
-    global viewname viewfiles viewperm nextviewnum
+    global viewname viewfiles viewargs viewperm nextviewnum
     global cmitmode
 
     if {$stuffsaved} return
@@ -715,7 +750,7 @@ proc savestuff {w} {
        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 "{[list $viewname($v) $viewfiles($v) $viewargs($v)]}"
            }
        }
        puts $f "}"
@@ -915,7 +950,7 @@ proc treeview {w l openlevs} {
                $w insert end $str
                $w image create end -align center -image $bm -padx 1 \
                    -name a:$ix
-               $w insert end $d
+               $w insert end $d [highlight_tag $prefix]
                $w mark set s:$ix "end -1c"
                $w mark gravity s:$ix left
            }
@@ -927,7 +962,7 @@ proc treeview {w l openlevs} {
                set str "\n"
                for {set i 0} {$i < $lev} {incr i} {append str "\t"}
                $w insert end $str
-               $w insert end $tail
+               $w insert end $tail [highlight_tag $f]
            }
            lappend treecontents($prefix) $tail
        }
@@ -964,6 +999,22 @@ proc linetoelt {l} {
     }
 }
 
+proc highlight_tree {y prefix} {
+    global treeheight treecontents cflist
+
+    foreach e $treecontents($prefix) {
+       set path $prefix$e
+       if {[highlight_tag $path] ne {}} {
+           $cflist tag add bold $y.0 "$y.0 lineend"
+       }
+       incr y
+       if {[string index $e end] eq "/" && $treeheight($path) > 1} {
+           set y [highlight_tree $y $path]
+       }
+    }
+    return $y
+}
+
 proc treeclosedir {w dir} {
     global treediropen treeheight treeparent treeindex
 
@@ -997,8 +1048,8 @@ proc treeopendir {w dir} {
        incr treeheight($x) $n
     }
     foreach e $treecontents($dir) {
+       set de $dir$e
        if {[string index $e end] eq "/"} {
-           set de $dir$e
            set iy $treeindex($de)
            $w mark set d:$iy e:$ix
            $w mark gravity d:$iy left
@@ -1006,13 +1057,13 @@ proc treeopendir {w dir} {
            set treediropen($de) 0
            $w image create e:$ix -align center -image tri-rt -padx 1 \
                -name a:$iy
-           $w insert e:$ix $e
+           $w insert e:$ix $e [highlight_tag $de]
            $w mark set s:$iy e:$ix
            $w mark gravity s:$iy left
            set treeheight($de) 1
        } else {
            $w insert e:$ix $str
-           $w insert e:$ix $e
+           $w insert e:$ix $e [highlight_tag $de]
        }
     }
     $w mark gravity e:$ix left
@@ -1108,20 +1159,56 @@ proc init_flist {first} {
     set difffilestart {}
 }
 
-proc add_flist {fl} {
-    global flistmode cflist
+proc highlight_tag {f} {
+    global highlight_paths
+
+    foreach p $highlight_paths {
+       if {[string match $p $f]} {
+           return "bold"
+       }
+    }
+    return {}
+}
+
+proc highlight_filelist {} {
+    global cmitmode cflist
 
     $cflist conf -state normal
-    if {$flistmode eq "flat"} {
-       foreach f $fl {
-           $cflist insert end "\n$f"
+    if {$cmitmode ne "tree"} {
+       set end [lindex [split [$cflist index end] .] 0]
+       for {set l 2} {$l < $end} {incr l} {
+           set line [$cflist get $l.0 "$l.0 lineend"]
+           if {[highlight_tag $line] ne {}} {
+               $cflist tag add bold $l.0 "$l.0 lineend"
+           }
        }
+    } else {
+       highlight_tree 2 {}
+    }
+    $cflist conf -state disabled
+}
+
+proc unhighlight_filelist {} {
+    global cflist
+
+    $cflist conf -state normal
+    $cflist tag remove bold 1.0 end
+    $cflist conf -state disabled
+}
+
+proc add_flist {fl} {
+    global cflist
+
+    $cflist conf -state normal
+    foreach f $fl {
+       $cflist insert end "\n"
+       $cflist insert end $f [highlight_tag $f]
     }
     $cflist conf -state disabled
 }
 
 proc sel_flist {w x y} {
-    global flistmode ctext difffilestart cflist cflist_top cmitmode
+    global ctext difffilestart cflist cflist_top cmitmode
 
     if {$cmitmode eq "tree"} return
     if {![info exists cflist_top]} return
@@ -1136,10 +1223,105 @@ proc sel_flist {w x y} {
     }
 }
 
+# Functions for adding and removing shell-type quoting
+
+proc shellquote {str} {
+    if {![string match "*\['\"\\ \t]*" $str]} {
+       return $str
+    }
+    if {![string match "*\['\"\\]*" $str]} {
+       return "\"$str\""
+    }
+    if {![string match "*'*" $str]} {
+       return "'$str'"
+    }
+    return "\"[string map {\" \\\" \\ \\\\} $str]\""
+}
+
+proc shellarglist {l} {
+    set str {}
+    foreach a $l {
+       if {$str ne {}} {
+           append str " "
+       }
+       append str [shellquote $a]
+    }
+    return $str
+}
+
+proc shelldequote {str} {
+    set ret {}
+    set used -1
+    while {1} {
+       incr used
+       if {![regexp -start $used -indices "\['\"\\\\ \t]" $str first]} {
+           append ret [string range $str $used end]
+           set used [string length $str]
+           break
+       }
+       set first [lindex $first 0]
+       set ch [string index $str $first]
+       if {$first > $used} {
+           append ret [string range $str $used [expr {$first - 1}]]
+           set used $first
+       }
+       if {$ch eq " " || $ch eq "\t"} break
+       incr used
+       if {$ch eq "'"} {
+           set first [string first "'" $str $used]
+           if {$first < 0} {
+               error "unmatched single-quote"
+           }
+           append ret [string range $str $used [expr {$first - 1}]]
+           set used $first
+           continue
+       }
+       if {$ch eq "\\"} {
+           if {$used >= [string length $str]} {
+               error "trailing backslash"
+           }
+           append ret [string index $str $used]
+           continue
+       }
+       # here ch == "\""
+       while {1} {
+           if {![regexp -start $used -indices "\[\"\\\\]" $str first]} {
+               error "unmatched double-quote"
+           }
+           set first [lindex $first 0]
+           set ch [string index $str $first]
+           if {$first > $used} {
+               append ret [string range $str $used [expr {$first - 1}]]
+               set used $first
+           }
+           if {$ch eq "\""} break
+           incr used
+           append ret [string index $str $used]
+           incr used
+       }
+    }
+    return [list $used $ret]
+}
+
+proc shellsplit {str} {
+    set l {}
+    while {1} {
+       set str [string trimleft $str]
+       if {$str eq {}} break
+       set dq [shelldequote $str]
+       set n [lindex $dq 0]
+       set word [lindex $dq 1]
+       set str [string range $str $n end]
+       lappend l $word
+    }
+    return $l
+}
+
 # Code to implement multiple views
 
 proc newview {ishighlight} {
     global nextviewnum newviewname newviewperm uifont newishighlight
+    global newviewargs revtreeargs
 
     set newishighlight $ishighlight
     set top .gitkview
@@ -1149,12 +1331,14 @@ proc newview {ishighlight} {
     }
     set newviewname($nextviewnum) "View $nextviewnum"
     set newviewperm($nextviewnum) 0
+    set newviewargs($nextviewnum) [shellarglist $revtreeargs]
     vieweditor $top $nextviewnum "Gitk view definition" 
 }
 
 proc editview {} {
     global curview
     global viewname viewperm newviewname newviewperm
+    global viewargs newviewargs
 
     set top .gitkvedit-$curview
     if {[winfo exists $top]} {
@@ -1163,6 +1347,7 @@ proc editview {} {
     }
     set newviewname($curview) $viewname($curview)
     set newviewperm($curview) $viewperm($curview)
+    set newviewargs($curview) [shellarglist $viewargs($curview)]
     vieweditor $top $curview "Gitk: edit view $viewname($curview)"
 }
 
@@ -1177,7 +1362,13 @@ proc vieweditor {top n title} {
     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 \
+    message $top.al -aspect 1000 -font $uifont \
+       -text "Commits to include (arguments to git-rev-list):"
+    grid $top.al - -sticky w -pady 5
+    entry $top.args -width 50 -textvariable newviewargs($n) \
+       -background white
+    grid $top.args - -sticky ew -padx 5
+    message $top.l -aspect 1000 -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
@@ -1189,7 +1380,7 @@ proc vieweditor {top n title} {
        $top.t delete {end - 1c} end
        $top.t mark set insert 0.0
     }
-    grid $top.t - -sticky w -padx 5
+    grid $top.t - -sticky ew -padx 5
     frame $top.buts
     button $top.buts.ok -text "OK" -command [list newviewok $top $n]
     button $top.buts.can -text "Cancel" -command [list destroy $top]
@@ -1200,25 +1391,36 @@ proc vieweditor {top n title} {
     focus $top.t
 }
 
-proc doviewmenu {m first cmd op args} {
+proc doviewmenu {m first cmd op argv} {
     set nmenu [$m index end]
     for {set i $first} {$i <= $nmenu} {incr i} {
        if {[$m entrycget $i -command] eq $cmd} {
-           eval $m $op $i $args
+           eval $m $op $i $argv
            break
        }
     }
 }
 
 proc allviewmenus {n op args} {
-    doviewmenu .bar.view 6 [list showview $n] $op $args
-    doviewmenu .bar.view.hl 3 [list addhighlight $n] $op $args
+    global viewhlmenu
+
+    doviewmenu .bar.view 7 [list showview $n] $op $args
+    doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
 }
 
 proc newviewok {top n} {
     global nextviewnum newviewperm newviewname newishighlight
     global viewname viewfiles viewperm selectedview curview
+    global viewargs newviewargs viewhlmenu
 
+    if {[catch {
+       set newargs [shellsplit $newviewargs($n)]
+    } err]} {
+       error_popup "Error in commit selection arguments: $err"
+       wm raise $top
+       focus $top
+       return
+    }
     set files {}
     foreach f [split [$top.t get 0.0 end] "\n"] {
        set ft [string trim $f]
@@ -1232,21 +1434,26 @@ proc newviewok {top n} {
        set viewname($n) $newviewname($n)
        set viewperm($n) $newviewperm($n)
        set viewfiles($n) $files
+       set viewargs($n) $newargs
        addviewmenu $n
        if {!$newishighlight} {
            after idle showview $n
        } else {
-           after idle addhighlight $n
+           after idle addvhighlight $n
        }
     } else {
        # editing an existing view
        set viewperm($n) $newviewperm($n)
        if {$newviewname($n) ne $viewname($n)} {
            set viewname($n) $newviewname($n)
-           allviewmenus $n entryconf -label $viewname($n)
+           doviewmenu .bar.view 7 [list showview $n] \
+               entryconf [list -label $viewname($n)]
+           doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
+               entryconf [list -label $viewname($n) -value $viewname($n)]
        }
-       if {$files ne $viewfiles($n)} {
+       if {$files ne $viewfiles($n) || $newargs ne $viewargs($n)} {
            set viewfiles($n) $files
+           set viewargs($n) $newargs
            if {$curview == $n} {
                after idle updatecommits
            }
@@ -1256,9 +1463,13 @@ proc newviewok {top n} {
 }
 
 proc delview {} {
-    global curview viewdata viewperm
+    global curview viewdata viewperm hlview selectedhlview
 
     if {$curview == 0} return
+    if {[info exists hlview] && $hlview == $curview} {
+       set selectedhlview None
+       unset hlview
+    }
     allviewmenus $curview delete
     set viewdata($curview) {}
     set viewperm($curview) 0
@@ -1266,12 +1477,12 @@ proc delview {} {
 }
 
 proc addviewmenu {n} {
-    global viewname
+    global viewname viewhlmenu
 
     .bar.view add radiobutton -label $viewname($n) \
        -command [list showview $n] -variable selectedview -value $n
-    .bar.view.hl add radiobutton -label $viewname($n) \
-       -command [list addhighlight $n] -variable selectedhlview -value $n
+    $viewhlmenu add radiobutton -label $viewname($n) \
+       -command [list addvhighlight $n] -variable selectedhlview
 }
 
 proc flatten {var} {
@@ -1303,8 +1514,9 @@ proc showview {n} {
     global pending_select phase
     global commitidx rowlaidout rowoptim linesegends
     global commfd nextupdate
-    global selectedview hlview selectedhlview
+    global selectedview
     global vparentlist vchildlist vdisporder vcmitlisted
+    global hlview selectedhlview
 
     if {$n == $curview} return
     set selid {}
@@ -1343,14 +1555,15 @@ proc showview {n} {
     catch {unset matchinglines}
     catch {unset treediffs}
     clear_display
+    if {[info exists hlview] && $hlview == $n} {
+       unset hlview
+       set selectedhlview None
+    }
 
     set curview $n
     set selectedview $n
-    set selectedhlview -1
-    .bar.view entryconf 1 -state [expr {$n == 0? "disabled": "normal"}]
     .bar.view entryconf 2 -state [expr {$n == 0? "disabled": "normal"}]
-    catch {unset hlview}
-    .bar.view.hl entryconf 1 -state disabled
+    .bar.view entryconf 3 -state [expr {$n == 0? "disabled": "normal"}]
 
     if {![info exists viewdata($n)]} {
        set pending_select $selid
@@ -1403,30 +1616,78 @@ proc showview {n} {
     selectline $row 0
     if {$phase ne {}} {
        if {$phase eq "getcommits"} {
-           global mainfont
-           $canv create text 3 3 -anchor nw -text "Reading commits..." \
-               -font $mainfont -tags textitems
+           show_status "Reading commits..."
        }
        if {[info exists commfd($n)]} {
            layoutmore
        } else {
            finishcommits
        }
+    } elseif {$numcommits == 0} {
+       show_status "No commits selected"
     }
 }
 
-proc addhighlight {n} {
-    global hlview curview viewdata highlighted highlightedrows
-    global selectedhlview
+# Stuff relating to the highlighting facility
+
+proc ishighlighted {row} {
+    global vhighlights fhighlights nhighlights
+
+    if {[info exists nhighlights($row)] && $nhighlights($row) > 0} {
+       return $nhighlights($row)
+    }
+    if {[info exists vhighlights($row)] && $vhighlights($row) > 0} {
+       return $vhighlights($row)
+    }
+    if {[info exists fhighlights($row)] && $fhighlights($row) > 0} {
+       return $fhighlights($row)
+    }
+    return 0
+}
+
+proc bolden {row font} {
+    global canv linehtag selectedline
+
+    $canv itemconf $linehtag($row) -font $font
+    if {$row == $selectedline} {
+       $canv delete secsel
+       set t [eval $canv create rect [$canv bbox $linehtag($row)] \
+                  -outline {{}} -tags secsel \
+                  -fill [$canv cget -selectbackground]]
+       $canv lower $t
+    }
+}
+
+proc bolden_name {row font} {
+    global canv2 linentag selectedline
+
+    $canv2 itemconf $linentag($row) -font $font
+    if {$row == $selectedline} {
+       $canv2 delete secsel
+       set t [eval $canv2 create rect [$canv2 bbox $linentag($row)] \
+                  -outline {{}} -tags secsel \
+                  -fill [$canv2 cget -selectbackground]]
+       $canv2 lower $t
+    }
+}
+
+proc unbolden {rows} {
+    global mainfont
+
+    foreach row $rows {
+       if {![ishighlighted $row]} {
+           bolden $row $mainfont
+       }
+    }
+}
+
+proc addvhighlight {n} {
+    global hlview curview viewdata vhl_done vhighlights commitidx
 
     if {[info exists hlview]} {
-       delhighlight
+       delvhighlight
     }
     set hlview $n
-    set selectedhlview $n
-    .bar.view.hl entryconf 1 -state normal
-    set highlighted($n) 0
-    set highlightedrows {}
     if {$n != $curview && ![info exists viewdata($n)]} {
        set viewdata($n) [list getcommits {{}} {{}} {} {} {} 0 0 0 {}]
        set vparentlist($n) {}
@@ -1434,34 +1695,29 @@ proc addhighlight {n} {
        set vdisporder($n) {}
        set vcmitlisted($n) {}
        start_rev_list $n
-    } else {
-       highlightmore
+    }
+    set vhl_done $commitidx($hlview)
+    if {$vhl_done > 0} {
+       drawvisible
     }
 }
 
-proc delhighlight {} {
-    global hlview highlightedrows canv linehtag mainfont
-    global selectedhlview selectedline
+proc delvhighlight {} {
+    global hlview vhighlights
+    global selectedline
 
     if {![info exists hlview]} return
     unset hlview
-    set selectedhlview {}
-    .bar.view.hl entryconf 1 -state disabled
-    foreach l $highlightedrows {
-       $canv itemconf $linehtag($l) -font $mainfont
-       if {$l == $selectedline} {
-           $canv delete secsel
-           set t [eval $canv create rect [$canv bbox $linehtag($l)] \
-                      -outline {{}} -tags secsel \
-                      -fill [$canv cget -selectbackground]]
-           $canv lower $t
-       }
+    set rows [array names vhighlights]
+    if {$rows ne {}} {
+       unset vhighlights
+       unbolden $rows
     }
 }
 
-proc highlightmore {} {
-    global hlview highlighted commitidx highlightedrows linehtag mainfont
-    global displayorder vdisporder curview canv commitrow selectedline
+proc vhighlightmore {} {
+    global hlview vhl_done commitidx vhighlights
+    global displayorder vdisporder curview mainfont
 
     set font [concat $mainfont bold]
     set max $commitidx($hlview)
@@ -1470,25 +1726,176 @@ proc highlightmore {} {
     } else {
        set disp $vdisporder($hlview)
     }
-    for {set i $highlighted($hlview)} {$i < $max} {incr i} {
+    set vr [visiblerows]
+    set r0 [lindex $vr 0]
+    set r1 [lindex $vr 1]
+    for {set i $vhl_done} {$i < $max} {incr i} {
        set id [lindex $disp $i]
        if {[info exists commitrow($curview,$id)]} {
            set row $commitrow($curview,$id)
-           if {[info exists linehtag($row)]} {
-               $canv itemconf $linehtag($row) -font $font
-               lappend highlightedrows $row
-               if {$row == $selectedline} {
-                   $canv delete secsel
-                   set t [eval $canv create rect \
-                              [$canv bbox $linehtag($row)] \
-                              -outline {{}} -tags secsel \
-                              -fill [$canv cget -selectbackground]]
-                   $canv lower $t
+           if {$r0 <= $row && $row <= $r1} {
+               if {![highlighted $row]} {
+                   bolden $row $font
                }
+               set vhighlights($row) 1
+           }
+       }
+    }
+    set vhl_done $max
+}
+
+proc askvhighlight {row id} {
+    global hlview vhighlights commitrow iddrawn mainfont
+
+    if {[info exists commitrow($hlview,$id)]} {
+       if {[info exists iddrawn($id)] && ![ishighlighted $row]} {
+           bolden $row [concat $mainfont bold]
+       }
+       set vhighlights($row) 1
+    } else {
+       set vhighlights($row) 0
+    }
+}
+
+proc hfiles_change {name ix op} {
+    global highlight_files filehighlight fhighlights fh_serial
+    global mainfont highlight_paths
+
+    if {[info exists filehighlight]} {
+       # delete previous highlights
+       catch {close $filehighlight}
+       unset filehighlight
+       set rows [array names fhighlights]
+       if {$rows ne {}} {
+           unset fhighlights
+           unbolden $rows
+       }
+       unhighlight_filelist
+    }
+    set highlight_paths {}
+    after cancel do_file_hl $fh_serial
+    incr fh_serial
+    if {$highlight_files ne {}} {
+       after 300 do_file_hl $fh_serial
+    }
+}
+
+proc makepatterns {l} {
+    set ret {}
+    foreach e $l {
+       set ee [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} $e]
+       if {[string index $ee end] eq "/"} {
+           lappend ret "$ee*"
+       } else {
+           lappend ret $ee
+           lappend ret "$ee/*"
+       }
+    }
+    return $ret
+}
+
+proc do_file_hl {serial} {
+    global highlight_files filehighlight highlight_paths
+
+    if {[catch {set paths [shellsplit $highlight_files]}]} return
+    set highlight_paths [makepatterns $paths]
+    highlight_filelist
+    set cmd [concat | git-diff-tree -r -s --stdin -- $paths]
+    set filehighlight [open $cmd r+]
+    fconfigure $filehighlight -blocking 0
+    fileevent $filehighlight readable readfhighlight
+    drawvisible
+    flushhighlights
+}
+
+proc flushhighlights {} {
+    global filehighlight
+
+    if {[info exists filehighlight]} {
+       puts $filehighlight ""
+       flush $filehighlight
+    }
+}
+
+proc askfilehighlight {row id} {
+    global filehighlight fhighlights
+
+    set fhighlights($row) 0
+    puts $filehighlight $id
+}
+
+proc readfhighlight {} {
+    global filehighlight fhighlights commitrow curview mainfont iddrawn
+
+    set n [gets $filehighlight line]
+    if {$n < 0} {
+       if {[eof $filehighlight]} {
+           # strange...
+           puts "oops, git-diff-tree died"
+           catch {close $filehighlight}
+           unset filehighlight
+       }
+       return
+    }
+    set line [string trim $line]
+    if {$line eq {}} return
+    if {![info exists commitrow($curview,$line)]} return
+    set row $commitrow($curview,$line)
+    if {[info exists iddrawn($line)] && ![ishighlighted $row]} {
+       bolden $row [concat $mainfont bold]
+    }
+    set fhighlights($row) 1
+}
+
+proc hnames_change {name ix op} {
+    global highlight_names nhighlights nhl_names mainfont
+
+    # delete previous highlights, if any
+    set rows [array names nhighlights]
+    if {$rows ne {}} {
+       foreach row $rows {
+           if {$nhighlights($row) >= 2} {
+               bolden_name $row $mainfont
            }
        }
+       unset nhighlights
+       unbolden $rows
     }
-    set highlighted($hlview) $max
+    if {[catch {set nhl_names [shellsplit $highlight_names]}]} {
+       set nhl_names {}
+       return
+    }
+    drawvisible
+}
+
+proc asknamehighlight {row id} {
+    global nhl_names nhighlights commitinfo iddrawn mainfont
+
+    if {![info exists commitinfo($id)]} {
+       getcommit $id
+    }
+    set isbold 0
+    set author [lindex $commitinfo($id) 1]
+    set committer [lindex $commitinfo($id) 3]
+    foreach name $nhl_names {
+       set pattern "*$name*"
+       if {[string match -nocase $pattern $author]} {
+           set isbold 2
+           break
+       }
+       if {!$isbold && [string match -nocase $pattern $committer]} {
+           set isbold 1
+       }
+    }
+    if {[info exists iddrawn($id)]} {
+       if {$isbold && ![ishighlighted $row]} {
+           bolden $row [concat $mainfont bold]
+       }
+       if {$isbold >= 2} {
+           bolden_name $row [concat $mainfont bold]
+       }
+    }
+    set nhighlights($row) $isbold
 }
 
 # Graph layout functions
@@ -2211,7 +2618,6 @@ proc drawcmittext {id row col rmx} {
     global rowtextx idpos idtags idheads idotherrefs
     global linehtag linentag linedtag
     global mainfont canvxmax
-    global hlview commitrow highlightedrows
 
     set ofill [expr {[lindex $commitlisted $row]? "blue": "white"}]
     set x [xc $row $col]
@@ -2237,15 +2643,19 @@ proc drawcmittext {id row col rmx} {
     set date [lindex $commitinfo($id) 2]
     set date [formatdate $date]
     set font $mainfont
-    if {[info exists hlview] && [info exists commitrow($hlview,$id)]} {
+    set nfont $mainfont
+    set isbold [ishighlighted $row]
+    if {$isbold > 0} {
        lappend font bold
-       lappend highlightedrows $row
+       if {$isbold > 1} {
+           lappend nfont bold
+       }
     }
     set linehtag($row) [$canv create text $xt $y -anchor w \
                            -text $headline -font $font]
     $canv bind $linehtag($row) <Button-3> "rowmenu %X %Y $id"
     set linentag($row) [$canv2 create text 3 $y -anchor w \
-                           -text $name -font $mainfont]
+                           -text $name -font $nfont]
     set linedtag($row) [$canv3 create text 3 $y -anchor w \
                            -text $date -font $mainfont]
     set xr [expr {$xt + [font measure $mainfont $headline]}]
@@ -2259,6 +2669,8 @@ proc drawcmitrow {row} {
     global displayorder rowidlist
     global idrangedrawn iddrawn
     global commitinfo parentlist numcommits
+    global filehighlight fhighlights nhl_names nhighlights
+    global hlview vhighlights
 
     if {$row >= $numcommits} return
     foreach id [lindex $rowidlist $row] {
@@ -2279,6 +2691,15 @@ proc drawcmitrow {row} {
     }
 
     set id [lindex $displayorder $row]
+    if {[info exists hlview] && ![info exists vhighlights($row)]} {
+       askvhighlight $row $id
+    }
+    if {[info exists filehighlight] && ![info exists fhighlights($row)]} {
+       askfilehighlight $row $id
+    }
+    if {$nhl_names ne {} && ![info exists nhighlights($row)]} {
+       asknamehighlight $row $id
+    }
     if {[info exists iddrawn($id)]} return
     set col [lsearch -exact [lindex $rowidlist $row] $id]
     if {$col < 0} {
@@ -2327,10 +2748,14 @@ proc drawvisible {} {
 
 proc clear_display {} {
     global iddrawn idrangedrawn
+    global vhighlights fhighlights nhighlights
 
     allcanvs delete all
     catch {unset iddrawn}
     catch {unset idrangedrawn}
+    catch {unset vhighlights}
+    catch {unset fhighlights}
+    catch {unset nhighlights}
 }
 
 proc findcrossings {id} {
@@ -2533,6 +2958,13 @@ proc xcoord {i level ln} {
     return $x
 }
 
+proc show_status {msg} {
+    global canv mainfont
+
+    clear_display
+    $canv create text 3 3 -anchor nw -text $msg -font $mainfont -tags textitems
+}
+
 proc finishcommits {} {
     global commitidx phase curview
     global canv mainfont ctext maincursor textcursor
@@ -2541,9 +2973,7 @@ proc finishcommits {} {
     if {$commitidx($curview) > 0} {
        drawrest
     } else {
-       $canv delete all
-       $canv create text 3 3 -anchor nw -text "No commits selected" \
-           -font $mainfont -tags textitems
+       show_status "No commits selected"
     }
     set phase {}
     catch {unset pending_select}
@@ -3084,8 +3514,11 @@ proc selcanvline {w x y} {
 
 proc commit_descriptor {p} {
     global commitinfo
+    if {![info exists commitinfo($p)]} {
+       getcommit $p
+    }
     set l "..."
-    if {[info exists commitinfo($p)]} {
+    if {[llength $commitinfo($p)] > 1} {
        set l [lindex $commitinfo($p) 0]
     }
     return "$p ($l)"
@@ -3297,6 +3730,7 @@ proc selnextpage {dir} {
        set lpp 1
     }
     allcanvs yview scroll [expr {$dir * $lpp}] units
+    drawvisible
     if {![info exists selectedline]} return
     set l [expr {$selectedline + $dir * $lpp}]
     if {$l < 0} {
@@ -4759,7 +5193,6 @@ set fastdate 0
 set uparrowlen 7
 set downarrowlen 7
 set mingaplen 30
-set flistmode "flat"
 set cmitmode "patch"
 
 set colors {green red blue magenta darkgrey brown orange}
@@ -4782,22 +5215,51 @@ foreach arg $argv {
 # check that we can find a .git directory somewhere...
 set gitdir [gitdir]
 if {![file isdirectory $gitdir]} {
-    error_popup "Cannot find the git directory \"$gitdir\"."
+    show_error . "Cannot find the git directory \"$gitdir\"."
     exit 1
 }
 
+set cmdline_files {}
+set i [lsearch -exact $revtreeargs "--"]
+if {$i >= 0} {
+    set cmdline_files [lrange $revtreeargs [expr {$i + 1}] end]
+    set revtreeargs [lrange $revtreeargs 0 [expr {$i - 1}]]
+} elseif {$revtreeargs ne {}} {
+    if {[catch {
+       set f [eval exec git-rev-parse --no-revs --no-flags $revtreeargs]
+       set cmdline_files [split $f "\n"]
+       set n [llength $cmdline_files]
+       set revtreeargs [lrange $revtreeargs 0 end-$n]
+    } err]} {
+       # unfortunately we get both stdout and stderr in $err,
+       # so look for "fatal:".
+       set i [string first "fatal:" $err]
+       if {$i > 0} {
+           set err [string range [expr {$i + 6}] end]
+       }
+       show_error . "Bad arguments to gitk:\n$err"
+       exit 1
+    }
+}
+
 set history {}
 set historyindex 0
+set fh_serial 0
+set highlight_names {}
+set nhl_names {}
+set highlight_paths {}
 
 set optim_delay 16
 
 set nextviewnum 1
 set curview 0
 set selectedview 0
-set selectedhlview {}
+set selectedhlview None
 set viewfiles(0) {}
 set viewperm(0) 0
+set viewargs(0) {}
 
+set cmdlineok 0
 set stopped 0
 set stuffsaved 0
 set patchnum 0
@@ -4805,28 +5267,18 @@ setcoords
 makewindow
 readrefs
 
-set cmdline_files {}
-catch {
-    set fileargs [eval exec git-rev-parse --no-revs --no-flags $revtreeargs]
-    set cmdline_files [split $fileargs "\n"]
-    set n [llength $cmdline_files]
-    set revtreeargs [lrange $revtreeargs 0 end-$n]
-}
-if {[lindex $revtreeargs end] eq "--"} {
-    set revtreeargs [lrange $revtreeargs 0 end-1]
-}
-
-if {$cmdline_files ne {}} {
+if {$cmdline_files ne {} || $revtreeargs 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
+    set viewargs(1) $revtreeargs
     set viewperm(1) 0
     addviewmenu 1
-    .bar.view entryconf 1 -state normal
     .bar.view entryconf 2 -state normal
+    .bar.view entryconf 3 -state normal
 }
 
 if {[info exists permviews]} {
@@ -4835,6 +5287,7 @@ if {[info exists permviews]} {
        incr nextviewnum
        set viewname($n) [lindex $v 0]
        set viewfiles($n) [lindex $v 1]
+       set viewargs($n) [lindex $v 2]
        set viewperm($n) 1
        addviewmenu $n
     }