+proc drawgraph {} {
+ global nextupdate startmsecs startcommits todo
+
+ if {$startcommits == {}} return
+ set startmsecs [clock clicks -milliseconds]
+ set nextupdate [expr $startmsecs + 100]
+ initgraph
+ set todo [lindex $startcommits 0]
+ drawrest 0 1
+}
+
+proc drawrest {level startix} {
+ global phase stopped redisplaying selectedline
+ global datemode currentparents todo
+ global numcommits
+ global nextupdate startmsecs startcommits idline
+
+ if {$level >= 0} {
+ set phase drawgraph
+ set startid [lindex $startcommits $startix]
+ set startline -1
+ if {$startid != {}} {
+ set startline $idline($startid)
+ }
+ while 1 {
+ if {$stopped} break
+ drawcommitline $level
+ set hard [updatetodo $level $datemode]
+ if {$numcommits == $startline} {
+ lappend todo $startid
+ set hard 1
+ incr startix
+ set startid [lindex $startcommits $startix]
+ set startline -1
+ if {$startid != {}} {
+ set startline $idline($startid)
+ }
+ }
+ if {$hard} {
+ set level [decidenext]
+ if {$level < 0} break
+ drawslants
+ }
+ if {[clock clicks -milliseconds] >= $nextupdate} {
+ update
+ incr nextupdate 100
+ }
+ }
+ }
+ set phase {}
+ set drawmsecs [expr [clock clicks -milliseconds] - $startmsecs]
+ #puts "overall $drawmsecs ms for $numcommits commits"
+ if {$redisplaying} {
+ if {$stopped == 0 && [info exists selectedline]} {
+ selectline $selectedline
+ }
+ if {$stopped == 1} {
+ set stopped 0
+ after idle drawgraph
+ } else {
+ set redisplaying 0
+ }
+ }
+}
+
+proc findmatches {f} {
+ global findtype foundstring foundstrlen
+ if {$findtype == "Regexp"} {
+ set matches [regexp -indices -all -inline $foundstring $f]
+ } else {
+ if {$findtype == "IgnCase"} {
+ set str [string tolower $f]
+ } else {
+ set str $f
+ }
+ set matches {}
+ set i 0
+ while {[set j [string first $foundstring $str $i]] >= 0} {
+ lappend matches [list $j [expr $j+$foundstrlen-1]]
+ set i [expr $j + $foundstrlen]
+ }
+ }
+ return $matches
+}
+
+proc dofind {} {
+ global findtype findloc findstring markedmatches commitinfo
+ global numcommits lineid linehtag linentag linedtag
+ global mainfont namefont canv canv2 canv3 selectedline
+ global matchinglines foundstring foundstrlen
+
+ stopfindproc
+ unmarkmatches
+ focus .
+ set matchinglines {}
+ if {$findloc == "Pickaxe"} {
+ findpatches
+ return
+ }
+ if {$findtype == "IgnCase"} {
+ set foundstring [string tolower $findstring]
+ } else {
+ set foundstring $findstring
+ }
+ set foundstrlen [string length $findstring]
+ if {$foundstrlen == 0} return
+ if {$findloc == "Files"} {
+ findfiles
+ return
+ }
+ if {![info exists selectedline]} {
+ set oldsel -1
+ } else {
+ set oldsel $selectedline
+ }
+ set didsel 0
+ set fldtypes {Headline Author Date Committer CDate Comment}
+ for {set l 0} {$l < $numcommits} {incr l} {
+ set id $lineid($l)
+ set info $commitinfo($id)
+ set doesmatch 0
+ foreach f $info ty $fldtypes {
+ if {$findloc != "All fields" && $findloc != $ty} {
+ continue
+ }
+ set matches [findmatches $f]
+ if {$matches == {}} continue
+ set doesmatch 1
+ if {$ty == "Headline"} {
+ markmatches $canv $l $f $linehtag($l) $matches $mainfont
+ } elseif {$ty == "Author"} {
+ markmatches $canv2 $l $f $linentag($l) $matches $namefont
+ } elseif {$ty == "Date"} {
+ markmatches $canv3 $l $f $linedtag($l) $matches $mainfont
+ }
+ }
+ if {$doesmatch} {
+ lappend matchinglines $l
+ if {!$didsel && $l > $oldsel} {
+ findselectline $l
+ set didsel 1
+ }
+ }
+ }
+ if {$matchinglines == {}} {
+ bell
+ } elseif {!$didsel} {
+ findselectline [lindex $matchinglines 0]
+ }
+}
+
+proc findselectline {l} {
+ global findloc commentend ctext
+ selectline $l
+ if {$findloc == "All fields" || $findloc == "Comments"} {
+ # highlight the matches in the comments
+ set f [$ctext get 1.0 $commentend]
+ set matches [findmatches $f]
+ foreach match $matches {
+ set start [lindex $match 0]
+ set end [expr [lindex $match 1] + 1]
+ $ctext tag add found "1.0 + $start c" "1.0 + $end c"
+ }
+ }
+}
+
+proc findnext {restart} {
+ global matchinglines selectedline
+ if {![info exists matchinglines]} {
+ if {$restart} {
+ dofind
+ }
+ return
+ }
+ if {![info exists selectedline]} return
+ foreach l $matchinglines {
+ if {$l > $selectedline} {
+ findselectline $l
+ return
+ }
+ }
+ bell
+}
+
+proc findprev {} {
+ global matchinglines selectedline
+ if {![info exists matchinglines]} {
+ dofind
+ return
+ }
+ if {![info exists selectedline]} return
+ set prev {}
+ foreach l $matchinglines {
+ if {$l >= $selectedline} break
+ set prev $l
+ }
+ if {$prev != {}} {
+ findselectline $prev
+ } else {
+ bell
+ }
+}
+
+proc findlocchange {name ix op} {
+ global findloc findtype findtypemenu
+ if {$findloc == "Pickaxe"} {
+ set findtype Exact
+ set state disabled
+ } else {
+ set state normal
+ }
+ $findtypemenu entryconf 1 -state $state
+ $findtypemenu entryconf 2 -state $state
+}
+
+proc stopfindproc {{done 0}} {
+ global findprocpid findprocfile findids
+ global ctext findoldcursor phase maincursor textcursor
+ global findinprogress
+
+ catch {unset findids}
+ if {[info exists findprocpid]} {
+ if {!$done} {
+ catch {exec kill $findprocpid}
+ }
+ catch {close $findprocfile}
+ unset findprocpid
+ }
+ if {[info exists findinprogress]} {
+ unset findinprogress
+ if {$phase != "incrdraw"} {
+ . config -cursor $maincursor
+ $ctext config -cursor $textcursor
+ }
+ }
+}
+
+proc findpatches {} {
+ global findstring selectedline numcommits
+ global findprocpid findprocfile
+ global finddidsel ctext lineid findinprogress
+ global findinsertpos
+
+ if {$numcommits == 0} return
+
+ # make a list of all the ids to search, starting at the one
+ # after the selected line (if any)
+ if {[info exists selectedline]} {
+ set l $selectedline
+ } else {
+ set l -1
+ }
+ set inputids {}
+ for {set i 0} {$i < $numcommits} {incr i} {
+ if {[incr l] >= $numcommits} {
+ set l 0
+ }
+ append inputids $lineid($l) "\n"
+ }
+
+ if {[catch {
+ set f [open [list | git-diff-tree --stdin -s -r -S$findstring \
+ << $inputids] r]
+ } err]} {
+ error_popup "Error starting search process: $err"
+ return
+ }
+
+ set findinsertpos end
+ set findprocfile $f
+ set findprocpid [pid $f]
+ fconfigure $f -blocking 0
+ fileevent $f readable readfindproc
+ set finddidsel 0
+ . config -cursor watch
+ $ctext config -cursor watch
+ set findinprogress 1
+}
+
+proc readfindproc {} {
+ global findprocfile finddidsel
+ global idline matchinglines findinsertpos
+
+ set n [gets $findprocfile line]
+ if {$n < 0} {
+ if {[eof $findprocfile]} {
+ stopfindproc 1
+ if {!$finddidsel} {
+ bell
+ }
+ }
+ return
+ }
+ if {![regexp {^[0-9a-f]{40}} $line id]} {
+ error_popup "Can't parse git-diff-tree output: $line"
+ stopfindproc
+ return
+ }
+ if {![info exists idline($id)]} {
+ puts stderr "spurious id: $id"
+ return
+ }
+ set l $idline($id)
+ insertmatch $l $id
+}
+
+proc insertmatch {l id} {
+ global matchinglines findinsertpos finddidsel
+
+ if {$findinsertpos == "end"} {
+ if {$matchinglines != {} && $l < [lindex $matchinglines 0]} {
+ set matchinglines [linsert $matchinglines 0 $l]
+ set findinsertpos 1
+ } else {
+ lappend matchinglines $l
+ }
+ } else {
+ set matchinglines [linsert $matchinglines $findinsertpos $l]
+ incr findinsertpos
+ }
+ markheadline $l $id
+ if {!$finddidsel} {
+ findselectline $l
+ set finddidsel 1
+ }
+}
+
+proc findfiles {} {
+ global selectedline numcommits lineid ctext
+ global ffileline finddidsel parents nparents
+ global findinprogress findstartline findinsertpos
+ global treediffs fdiffids fdiffsneeded fdiffpos
+ global findmergefiles
+
+ if {$numcommits == 0} return
+
+ if {[info exists selectedline]} {
+ set l [expr {$selectedline + 1}]
+ } else {
+ set l 0
+ }
+ set ffileline $l
+ set findstartline $l
+ set diffsneeded {}
+ set fdiffsneeded {}
+ while 1 {
+ set id $lineid($l)
+ if {$findmergefiles || $nparents($id) == 1} {
+ foreach p $parents($id) {
+ if {![info exists treediffs([list $id $p])]} {
+ append diffsneeded "$id $p\n"
+ lappend fdiffsneeded [list $id $p]
+ }
+ }
+ }
+ if {[incr l] >= $numcommits} {
+ set l 0
+ }
+ if {$l == $findstartline} break
+ }
+
+ # start off a git-diff-tree process if needed
+ if {$diffsneeded ne {}} {
+ if {[catch {
+ set df [open [list | git-diff-tree -r --stdin << $diffsneeded] r]
+ } err ]} {
+ error_popup "Error starting search process: $err"
+ return
+ }
+ catch {unset fdiffids}
+ set fdiffpos 0
+ fconfigure $df -blocking 0
+ fileevent $df readable [list readfilediffs $df]
+ }
+
+ set finddidsel 0
+ set findinsertpos end
+ set id $lineid($l)
+ set p [lindex $parents($id) 0]
+ . config -cursor watch
+ $ctext config -cursor watch
+ set findinprogress 1
+ findcont [list $id $p]
+ update
+}
+
+proc readfilediffs {df} {
+ global findids fdiffids fdiffs
+
+ set n [gets $df line]
+ if {$n < 0} {
+ if {[eof $df]} {
+ donefilediff
+ if {[catch {close $df} err]} {
+ stopfindproc
+ bell
+ error_popup "Error in git-diff-tree: $err"
+ } elseif {[info exists findids]} {
+ set ids $findids
+ stopfindproc
+ bell
+ error_popup "Couldn't find diffs for {$ids}"
+ }
+ }
+ return
+ }
+ if {[regexp {^([0-9a-f]{40}) \(from ([0-9a-f]{40})\)} $line match id p]} {
+ # start of a new string of diffs
+ donefilediff
+ set fdiffids [list $id $p]
+ set fdiffs {}
+ } elseif {[string match ":*" $line]} {
+ lappend fdiffs [lindex $line 5]
+ }
+}
+
+proc donefilediff {} {
+ global fdiffids fdiffs treediffs findids
+ global fdiffsneeded fdiffpos
+
+ if {[info exists fdiffids]} {
+ while {[lindex $fdiffsneeded $fdiffpos] ne $fdiffids
+ && $fdiffpos < [llength $fdiffsneeded]} {
+ # git-diff-tree doesn't output anything for a commit
+ # which doesn't change anything
+ set nullids [lindex $fdiffsneeded $fdiffpos]
+ set treediffs($nullids) {}
+ if {[info exists findids] && $nullids eq $findids} {
+ unset findids
+ findcont $nullids
+ }
+ incr fdiffpos
+ }
+ incr fdiffpos
+
+ if {![info exists treediffs($fdiffids)]} {
+ set treediffs($fdiffids) $fdiffs
+ }
+ if {[info exists findids] && $fdiffids eq $findids} {
+ unset findids
+ findcont $fdiffids
+ }
+ }
+}
+
+proc findcont {ids} {
+ global findids treediffs parents nparents
+ global ffileline findstartline finddidsel
+ global lineid numcommits matchinglines findinprogress
+ global findmergefiles
+
+ set id [lindex $ids 0]
+ set p [lindex $ids 1]
+ set pi [lsearch -exact $parents($id) $p]
+ set l $ffileline
+ while 1 {
+ if {$findmergefiles || $nparents($id) == 1} {
+ if {![info exists treediffs($ids)]} {
+ set findids $ids
+ set ffileline $l
+ return
+ }
+ set doesmatch 0
+ foreach f $treediffs($ids) {
+ set x [findmatches $f]
+ if {$x != {}} {
+ set doesmatch 1
+ break
+ }
+ }
+ if {$doesmatch} {
+ insertmatch $l $id
+ set pi $nparents($id)
+ }
+ } else {
+ set pi $nparents($id)
+ }
+ if {[incr pi] >= $nparents($id)} {
+ set pi 0
+ if {[incr l] >= $numcommits} {
+ set l 0
+ }
+ if {$l == $findstartline} break
+ set id $lineid($l)
+ }
+ set p [lindex $parents($id) $pi]
+ set ids [list $id $p]
+ }
+ stopfindproc
+ if {!$finddidsel} {
+ bell
+ }
+}
+
+# mark a commit as matching by putting a yellow background
+# behind the headline
+proc markheadline {l id} {
+ global canv mainfont linehtag commitinfo
+
+ set bbox [$canv bbox $linehtag($l)]
+ set t [$canv create rect $bbox -outline {} -tags matches -fill yellow]
+ $canv lower $t
+}
+
+# mark the bits of a headline, author or date that match a find string
+proc markmatches {canv l str tag matches font} {
+ set bbox [$canv bbox $tag]
+ set x0 [lindex $bbox 0]
+ set y0 [lindex $bbox 1]
+ set y1 [lindex $bbox 3]
+ foreach match $matches {
+ set start [lindex $match 0]
+ set end [lindex $match 1]
+ if {$start > $end} continue
+ set xoff [font measure $font [string range $str 0 [expr $start-1]]]
+ set xlen [font measure $font [string range $str 0 [expr $end]]]
+ set t [$canv create rect [expr $x0+$xoff] $y0 [expr $x0+$xlen+2] $y1 \
+ -outline {} -tags matches -fill yellow]
+ $canv lower $t
+ }
+}
+
+proc unmarkmatches {} {
+ global matchinglines findids
+ allcanvs delete matches
+ catch {unset matchinglines}
+ catch {unset findids}
+}
+
+proc selcanvline {w x y} {
+ global canv canvy0 ctext linespc selectedline
+ global lineid linehtag linentag linedtag rowtextx
+ set ymax [lindex [$canv cget -scrollregion] 3]
+ if {$ymax == {}} return
+ set yfrac [lindex [$canv yview] 0]
+ set y [expr {$y + $yfrac * $ymax}]
+ set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
+ if {$l < 0} {
+ set l 0
+ }
+ if {$w eq $canv} {
+ if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
+ }
+ unmarkmatches
+ selectline $l
+}
+
+proc selectline {l} {
+ global canv canv2 canv3 ctext commitinfo selectedline
+ global lineid linehtag linentag linedtag
+ global canvy0 linespc parents nparents
+ global cflist currentid sha1entry
+ global commentend idtags
+ $canv delete hover
+ if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
+ $canv delete secsel
+ set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
+ -tags secsel -fill [$canv cget -selectbackground]]
+ $canv lower $t
+ $canv2 delete secsel
+ set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
+ -tags secsel -fill [$canv2 cget -selectbackground]]
+ $canv2 lower $t