save window geometry on exit, and restore it on startup
[git/trast.git] / gitk
blob37a97acc12df008eed58a26fad8fce41f765ebdf
1 #!/bin/sh
2 # Tcl ignores the next line -*- tcl -*- \
3 exec wish "$0" -- "${1+$@}"
5 # Copyright (C) 2005 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.
10 # CVS $Revision: 1.13 $
12 proc getcommits {rargs} {
13 global commits commfd phase canv mainfont
14 if {$rargs == {}} {
15 set rargs HEAD
17 set commits {}
18 set phase getcommits
19 if [catch {set commfd [open "|git-rev-tree $rargs" r]} err] {
20 puts stderr "Error executing git-rev-tree: $err"
21 exit 1
23 fconfigure $commfd -blocking 0
24 fileevent $commfd readable "getcommitline $commfd"
25 $canv delete all
26 $canv create text 3 3 -anchor nw -text "Reading commits..." \
27 -font $mainfont -tags textitems
30 proc getcommitline {commfd} {
31 global commits parents cdate nparents children nchildren
32 set n [gets $commfd line]
33 if {$n < 0} {
34 if {![eof $commfd]} return
35 if {![catch {close $commfd} err]} {
36 after idle drawgraph
37 return
39 if {[string range $err 0 4] == "usage"} {
40 puts stderr "Error reading commits: bad arguments to git-rev-tree"
41 puts stderr "Note: arguments to gitk are passed to git-rev-tree"
42 puts stderr " to allow selection of commits to be displayed"
43 } else {
44 puts stderr "Error reading commits: $err"
46 exit 1
49 set i 0
50 set cid {}
51 foreach f $line {
52 if {$i == 0} {
53 set d $f
54 } else {
55 set id [lindex [split $f :] 0]
56 if {![info exists nchildren($id)]} {
57 set children($id) {}
58 set nchildren($id) 0
60 if {$i == 1} {
61 set cid $id
62 lappend commits $id
63 set parents($id) {}
64 set cdate($id) $d
65 set nparents($id) 0
66 } else {
67 lappend parents($cid) $id
68 incr nparents($cid)
69 incr nchildren($id)
70 lappend children($id) $cid
73 incr i
77 proc readcommit {id} {
78 global commitinfo
79 set inhdr 1
80 set comment {}
81 set headline {}
82 set auname {}
83 set audate {}
84 set comname {}
85 set comdate {}
86 foreach line [split [exec git-cat-file commit $id] "\n"] {
87 if {$inhdr} {
88 if {$line == {}} {
89 set inhdr 0
90 } else {
91 set tag [lindex $line 0]
92 if {$tag == "author"} {
93 set x [expr {[llength $line] - 2}]
94 set audate [lindex $line $x]
95 set auname [lrange $line 1 [expr {$x - 1}]]
96 } elseif {$tag == "committer"} {
97 set x [expr {[llength $line] - 2}]
98 set comdate [lindex $line $x]
99 set comname [lrange $line 1 [expr {$x - 1}]]
102 } else {
103 if {$comment == {}} {
104 set headline $line
105 } else {
106 append comment "\n"
108 append comment $line
111 if {$audate != {}} {
112 set audate [clock format $audate -format "%Y-%m-%d %H:%M:%S"]
114 if {$comdate != {}} {
115 set comdate [clock format $comdate -format "%Y-%m-%d %H:%M:%S"]
117 set commitinfo($id) [list $headline $auname $audate \
118 $comname $comdate $comment]
121 proc makewindow {} {
122 global canv canv2 canv3 linespc charspc ctext cflist textfont
123 global sha1entry findtype findloc findstring geometry
125 menu .bar
126 .bar add cascade -label "File" -menu .bar.file
127 menu .bar.file
128 .bar.file add command -label "Quit" -command doquit
129 menu .bar.help
130 .bar add cascade -label "Help" -menu .bar.help
131 .bar.help add command -label "About gitk" -command about
132 . configure -menu .bar
134 if {![info exists geometry(canv1)]} {
135 set geometry(canv1) [expr 45 * $charspc]
136 set geometry(canv2) [expr 30 * $charspc]
137 set geometry(canv3) [expr 15 * $charspc]
138 set geometry(canvh) [expr 25 * $linespc + 4]
139 set geometry(ctextw) 80
140 set geometry(ctexth) 30
141 set geometry(cflistw) 30
143 panedwindow .ctop -orient vertical
144 if {[info exists geometry(width)]} {
145 .ctop conf -width $geometry(width) -height $geometry(height)
147 frame .ctop.top
148 frame .ctop.top.bar
149 pack .ctop.top.bar -side bottom -fill x
150 set cscroll .ctop.top.csb
151 scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
152 pack $cscroll -side right -fill y
153 panedwindow .ctop.top.clist -orient horizontal -sashpad 0 -handlesize 4
154 pack .ctop.top.clist -side top -fill both -expand 1
155 .ctop add .ctop.top
156 set canv .ctop.top.clist.canv
157 canvas $canv -height $geometry(canvh) -width $geometry(canv1) \
158 -bg white -bd 0 \
159 -yscrollincr $linespc -yscrollcommand "$cscroll set"
160 .ctop.top.clist add $canv
161 set canv2 .ctop.top.clist.canv2
162 canvas $canv2 -height $geometry(canvh) -width $geometry(canv2) \
163 -bg white -bd 0 -yscrollincr $linespc
164 .ctop.top.clist add $canv2
165 set canv3 .ctop.top.clist.canv3
166 canvas $canv3 -height $geometry(canvh) -width $geometry(canv3) \
167 -bg white -bd 0 -yscrollincr $linespc
168 .ctop.top.clist add $canv3
169 bind .ctop.top.clist <Configure> {resizeclistpanes %W %w}
171 set sha1entry .ctop.top.bar.sha1
172 label .ctop.top.bar.sha1label -text "SHA1 ID: "
173 pack .ctop.top.bar.sha1label -side left
174 entry $sha1entry -width 40 -font $textfont -state readonly
175 pack $sha1entry -side left -pady 2
176 button .ctop.top.bar.findbut -text "Find" -command dofind
177 pack .ctop.top.bar.findbut -side left
178 set findstring {}
179 entry .ctop.top.bar.findstring -width 30 -font $textfont \
180 -textvariable findstring
181 pack .ctop.top.bar.findstring -side left -expand 1 -fill x
182 set findtype Exact
183 tk_optionMenu .ctop.top.bar.findtype findtype Exact IgnCase Regexp
184 set findloc "All fields"
185 tk_optionMenu .ctop.top.bar.findloc findloc "All fields" Headline \
186 Comments Author Committer
187 pack .ctop.top.bar.findloc -side right
188 pack .ctop.top.bar.findtype -side right
190 panedwindow .ctop.cdet -orient horizontal
191 if {[info exists geometry(cdeth)]} {
192 .ctop.cdet conf -height $geometry(cdeth)
194 .ctop add .ctop.cdet
195 frame .ctop.cdet.left
196 set ctext .ctop.cdet.left.ctext
197 text $ctext -bg white -state disabled -font $textfont \
198 -width $geometry(ctextw) -height $geometry(ctexth) \
199 -yscrollcommand ".ctop.cdet.left.sb set"
200 scrollbar .ctop.cdet.left.sb -command "$ctext yview"
201 pack .ctop.cdet.left.sb -side right -fill y
202 pack $ctext -side left -fill both -expand 1
203 .ctop.cdet add .ctop.cdet.left
204 if {[info exists geometry(detlw)]} {
205 .ctop.cdet.left conf -width $geometry(detlw)
208 $ctext tag conf filesep -font [concat $textfont bold]
209 $ctext tag conf hunksep -back blue -fore white
210 $ctext tag conf d0 -back "#ff8080"
211 $ctext tag conf d1 -back green
213 frame .ctop.cdet.right
214 set cflist .ctop.cdet.right.cfiles
215 listbox $cflist -width $geometry(cflistw) -bg white -selectmode extended \
216 -yscrollcommand ".ctop.cdet.right.sb set"
217 scrollbar .ctop.cdet.right.sb -command "$cflist yview"
218 pack .ctop.cdet.right.sb -side right -fill y
219 pack $cflist -side left -fill both -expand 1
220 .ctop.cdet add .ctop.cdet.right
221 if {[info exists geometry(detsash)]} {
222 eval .ctop.cdet sash place 0 $geometry(detsash)
224 bind .ctop.cdet <Configure> {resizecdetpanes %W %w}
226 pack .ctop -side top -fill both -expand 1
228 bindall <1> {selcanvline %x %y}
229 bindall <B1-Motion> {selcanvline %x %y}
230 bindall <ButtonRelease-4> "allcanvs yview scroll -5 u"
231 bindall <ButtonRelease-5> "allcanvs yview scroll 5 u"
232 bindall <2> "allcanvs scan mark 0 %y"
233 bindall <B2-Motion> "allcanvs scan dragto 0 %y"
234 bind . <Key-Up> "selnextline -1"
235 bind . <Key-Down> "selnextline 1"
236 bind . p "selnextline -1"
237 bind . n "selnextline 1"
238 bind . <Key-Prior> "allcanvs yview scroll -1 p"
239 bind . <Key-Next> "allcanvs yview scroll 1 p"
240 bind . <Key-Delete> "$ctext yview scroll -1 p"
241 bind . <Key-BackSpace> "$ctext yview scroll -1 p"
242 bind . <Key-space> "$ctext yview scroll 1 p"
243 bind . b "$ctext yview scroll -1 p"
244 bind . d "$ctext yview scroll 18 u"
245 bind . u "$ctext yview scroll -18 u"
246 bind . Q doquit
247 bind . <Control-q> doquit
248 bind . <Control-f> dofind
249 bind . <Control-g> findnext
250 bind . <Control-r> findprev
251 bind . <Control-equal> {incrfont 1}
252 bind . <Control-KP_Add> {incrfont 1}
253 bind . <Control-minus> {incrfont -1}
254 bind . <Control-KP_Subtract> {incrfont -1}
255 bind $cflist <<ListboxSelect>> listboxsel
256 bind . <Destroy> {savestuff %W}
259 proc savestuff {w} {
260 global canv canv2 canv3 ctext cflist mainfont textfont
261 global stuffsaved
262 if {$stuffsaved} return
263 catch {
264 set f [open "~/.gitk-new" w]
265 puts $f "set mainfont {$mainfont}"
266 puts $f "set textfont {$textfont}"
267 puts $f "set geometry(width) [winfo width .ctop]"
268 puts $f "set geometry(height) [winfo height .ctop]"
269 puts $f "set geometry(canv1) [winfo width $canv]"
270 puts $f "set geometry(canv2) [winfo width $canv2]"
271 puts $f "set geometry(canv3) [winfo width $canv3]"
272 puts $f "set geometry(canvh) [winfo height $canv]"
273 puts $f "set geometry(cdeth) [winfo height .ctop.cdet]"
274 set wid [expr {([winfo width $ctext] - 8) \
275 / [font measure $textfont "0"]}]
276 set ht [expr {([winfo height $ctext] - 8) \
277 / [font metrics $textfont -linespace]}]
278 puts $f "set geometry(ctextw) $wid"
279 puts $f "set geometry(ctexth) $ht"
280 set wid [expr {([winfo width $cflist] - 11) \
281 / [font measure [$cflist cget -font] "0"]}]
282 puts $f "set geometry(cflistw) $wid"
283 close $f
284 file rename -force "~/.gitk-new" "~/.gitk"
286 set stuffsaved 1
289 proc resizeclistpanes {win w} {
290 global oldwidth
291 if [info exists oldwidth($win)] {
292 set s0 [$win sash coord 0]
293 set s1 [$win sash coord 1]
294 if {$w < 60} {
295 set sash0 [expr {int($w/2 - 2)}]
296 set sash1 [expr {int($w*5/6 - 2)}]
297 } else {
298 set factor [expr {1.0 * $w / $oldwidth($win)}]
299 set sash0 [expr {int($factor * [lindex $s0 0])}]
300 set sash1 [expr {int($factor * [lindex $s1 0])}]
301 if {$sash0 < 30} {
302 set sash0 30
304 if {$sash1 < $sash0 + 20} {
305 set sash1 [expr $sash0 + 20]
307 if {$sash1 > $w - 10} {
308 set sash1 [expr $w - 10]
309 if {$sash0 > $sash1 - 20} {
310 set sash0 [expr $sash1 - 20]
314 $win sash place 0 $sash0 [lindex $s0 1]
315 $win sash place 1 $sash1 [lindex $s1 1]
317 set oldwidth($win) $w
320 proc resizecdetpanes {win w} {
321 global oldwidth
322 if [info exists oldwidth($win)] {
323 set s0 [$win sash coord 0]
324 if {$w < 60} {
325 set sash0 [expr {int($w*3/4 - 2)}]
326 } else {
327 set factor [expr {1.0 * $w / $oldwidth($win)}]
328 set sash0 [expr {int($factor * [lindex $s0 0])}]
329 if {$sash0 < 45} {
330 set sash0 45
332 if {$sash0 > $w - 15} {
333 set sash0 [expr $w - 15]
336 $win sash place 0 $sash0 [lindex $s0 1]
338 set oldwidth($win) $w
341 proc allcanvs args {
342 global canv canv2 canv3
343 eval $canv $args
344 eval $canv2 $args
345 eval $canv3 $args
348 proc bindall {event action} {
349 global canv canv2 canv3
350 bind $canv $event $action
351 bind $canv2 $event $action
352 bind $canv3 $event $action
355 proc about {} {
356 set w .about
357 if {[winfo exists $w]} {
358 raise $w
359 return
361 toplevel $w
362 wm title $w "About gitk"
363 message $w.m -text {
364 Gitk version 0.91
366 Copyright © 2005 Paul Mackerras
368 Use and redistribute under the terms of the GNU General Public License
370 (CVS $Revision: 1.13 $)} \
371 -justify center -aspect 400
372 pack $w.m -side top -fill x -padx 20 -pady 20
373 button $w.ok -text Close -command "destroy $w"
374 pack $w.ok -side bottom
377 proc truncatetofit {str width font} {
378 if {[font measure $font $str] <= $width} {
379 return $str
381 set best 0
382 set bad [string length $str]
383 set tmp $str
384 while {$best < $bad - 1} {
385 set try [expr {int(($best + $bad) / 2)}]
386 set tmp "[string range $str 0 [expr $try-1]]..."
387 if {[font measure $font $tmp] <= $width} {
388 set best $try
389 } else {
390 set bad $try
393 return $tmp
396 proc assigncolor {id} {
397 global commitinfo colormap commcolors colors nextcolor
398 global colorbycommitter
399 global parents nparents children nchildren
400 if [info exists colormap($id)] return
401 set ncolors [llength $colors]
402 if {$colorbycommitter} {
403 if {![info exists commitinfo($id)]} {
404 readcommit $id
406 set comm [lindex $commitinfo($id) 3]
407 if {![info exists commcolors($comm)]} {
408 set commcolors($comm) [lindex $colors $nextcolor]
409 if {[incr nextcolor] >= $ncolors} {
410 set nextcolor 0
413 set colormap($id) $commcolors($comm)
414 } else {
415 if {$nparents($id) == 1 && $nchildren($id) == 1} {
416 set child [lindex $children($id) 0]
417 if {[info exists colormap($child)]
418 && $nparents($child) == 1} {
419 set colormap($id) $colormap($child)
420 return
423 set badcolors {}
424 foreach child $children($id) {
425 if {[info exists colormap($child)]
426 && [lsearch -exact $badcolors $colormap($child)] < 0} {
427 lappend badcolors $colormap($child)
429 if {[info exists parents($child)]} {
430 foreach p $parents($child) {
431 if {[info exists colormap($p)]
432 && [lsearch -exact $badcolors $colormap($p)] < 0} {
433 lappend badcolors $colormap($p)
438 if {[llength $badcolors] >= $ncolors} {
439 set badcolors {}
441 for {set i 0} {$i <= $ncolors} {incr i} {
442 set c [lindex $colors $nextcolor]
443 if {[incr nextcolor] >= $ncolors} {
444 set nextcolor 0
446 if {[lsearch -exact $badcolors $c]} break
448 set colormap($id) $c
452 proc drawgraph {} {
453 global parents children nparents nchildren commits
454 global canv canv2 canv3 mainfont namefont canvx0 canvy0 canvy linespc
455 global datemode cdate
456 global lineid linehtag linentag linedtag commitinfo
457 global nextcolor colormap numcommits
458 global stopped phase redisplaying selectedline
460 allcanvs delete all
461 set start {}
462 foreach id $commits {
463 if {$nchildren($id) == 0} {
464 lappend start $id
466 set ncleft($id) $nchildren($id)
468 if {$start == {}} {
469 $canv create text 3 3 -anchor nw -font $mainfont \
470 -text "ERROR: No starting commits found"
471 set phase {}
472 return
475 set nextcolor 0
476 foreach id $start {
477 assigncolor $id
479 set todo $start
480 set level [expr [llength $todo] - 1]
481 set y2 $canvy0
482 set nullentry -1
483 set lineno -1
484 set numcommits 0
485 set phase drawgraph
486 while 1 {
487 set canvy $y2
488 allcanvs conf -scrollregion [list 0 0 0 $canvy]
489 update
490 if {$stopped} break
491 incr numcommits
492 incr lineno
493 set nlines [llength $todo]
494 set id [lindex $todo $level]
495 set lineid($lineno) $id
496 set actualparents {}
497 foreach p $parents($id) {
498 if {[info exists ncleft($p)]} {
499 incr ncleft($p) -1
500 lappend actualparents $p
503 if {![info exists commitinfo($id)]} {
504 readcommit $id
506 set x [expr $canvx0 + $level * $linespc]
507 set y2 [expr $canvy + $linespc]
508 if {[info exists linestarty($level)] && $linestarty($level) < $canvy} {
509 set t [$canv create line $x $linestarty($level) $x $canvy \
510 -width 2 -fill $colormap($id)]
511 $canv lower $t
513 set linestarty($level) $canvy
514 set t [$canv create oval [expr $x - 4] [expr $canvy - 4] \
515 [expr $x + 3] [expr $canvy + 3] \
516 -fill blue -outline black -width 1]
517 $canv raise $t
518 set xt [expr $canvx0 + $nlines * $linespc]
519 set headline [lindex $commitinfo($id) 0]
520 set name [lindex $commitinfo($id) 1]
521 set date [lindex $commitinfo($id) 2]
522 set linehtag($lineno) [$canv create text $xt $canvy -anchor w \
523 -text $headline -font $mainfont ]
524 set linentag($lineno) [$canv2 create text 3 $canvy -anchor w \
525 -text $name -font $namefont]
526 set linedtag($lineno) [$canv3 create text 3 $canvy -anchor w \
527 -text $date -font $mainfont]
528 if {!$datemode && [llength $actualparents] == 1} {
529 set p [lindex $actualparents 0]
530 if {$ncleft($p) == 0 && [lsearch -exact $todo $p] < 0} {
531 assigncolor $p
532 set todo [lreplace $todo $level $level $p]
533 continue
537 set oldtodo $todo
538 set oldlevel $level
539 set lines {}
540 for {set i 0} {$i < $nlines} {incr i} {
541 if {[lindex $todo $i] == {}} continue
542 if {[info exists linestarty($i)]} {
543 set oldstarty($i) $linestarty($i)
544 unset linestarty($i)
546 if {$i != $level} {
547 lappend lines [list $i [lindex $todo $i]]
550 if {$nullentry >= 0} {
551 set todo [lreplace $todo $nullentry $nullentry]
552 if {$nullentry < $level} {
553 incr level -1
557 set todo [lreplace $todo $level $level]
558 if {$nullentry > $level} {
559 incr nullentry -1
561 set i $level
562 foreach p $actualparents {
563 set k [lsearch -exact $todo $p]
564 if {$k < 0} {
565 assigncolor $p
566 set todo [linsert $todo $i $p]
567 if {$nullentry >= $i} {
568 incr nullentry
571 lappend lines [list $oldlevel $p]
574 # choose which one to do next time around
575 set todol [llength $todo]
576 set level -1
577 set latest {}
578 for {set k $todol} {[incr k -1] >= 0} {} {
579 set p [lindex $todo $k]
580 if {$p == {}} continue
581 if {$ncleft($p) == 0} {
582 if {$datemode} {
583 if {$latest == {} || $cdate($p) > $latest} {
584 set level $k
585 set latest $cdate($p)
587 } else {
588 set level $k
589 break
593 if {$level < 0} {
594 if {$todo != {}} {
595 puts "ERROR: none of the pending commits can be done yet:"
596 foreach p $todo {
597 puts " $p"
600 break
603 # If we are reducing, put in a null entry
604 if {$todol < $nlines} {
605 if {$nullentry >= 0} {
606 set i $nullentry
607 while {$i < $todol
608 && [lindex $oldtodo $i] == [lindex $todo $i]} {
609 incr i
611 } else {
612 set i $oldlevel
613 if {$level >= $i} {
614 incr i
617 if {$i >= $todol} {
618 set nullentry -1
619 } else {
620 set nullentry $i
621 set todo [linsert $todo $nullentry {}]
622 if {$level >= $i} {
623 incr level
626 } else {
627 set nullentry -1
630 foreach l $lines {
631 set i [lindex $l 0]
632 set dst [lindex $l 1]
633 set j [lsearch -exact $todo $dst]
634 if {$i == $j} {
635 if {[info exists oldstarty($i)]} {
636 set linestarty($i) $oldstarty($i)
638 continue
640 set xi [expr {$canvx0 + $i * $linespc}]
641 set xj [expr {$canvx0 + $j * $linespc}]
642 set coords {}
643 if {[info exists oldstarty($i)] && $oldstarty($i) < $canvy} {
644 lappend coords $xi $oldstarty($i)
646 lappend coords $xi $canvy
647 if {$j < $i - 1} {
648 lappend coords [expr $xj + $linespc] $canvy
649 } elseif {$j > $i + 1} {
650 lappend coords [expr $xj - $linespc] $canvy
652 lappend coords $xj $y2
653 set t [$canv create line $coords -width 2 -fill $colormap($dst)]
654 $canv lower $t
655 if {![info exists linestarty($j)]} {
656 set linestarty($j) $y2
660 set phase {}
661 if {$redisplaying} {
662 if {$stopped == 0 && [info exists selectedline]} {
663 selectline $selectedline
665 if {$stopped == 1} {
666 set stopped 0
667 after idle drawgraph
668 } else {
669 set redisplaying 0
674 proc dofind {} {
675 global findtype findloc findstring markedmatches commitinfo
676 global numcommits lineid linehtag linentag linedtag
677 global mainfont namefont canv canv2 canv3 selectedline
678 global matchinglines
679 unmarkmatches
680 set matchinglines {}
681 set fldtypes {Headline Author Date Committer CDate Comment}
682 if {$findtype == "IgnCase"} {
683 set fstr [string tolower $findstring]
684 } else {
685 set fstr $findstring
687 set mlen [string length $findstring]
688 if {$mlen == 0} return
689 if {![info exists selectedline]} {
690 set oldsel -1
691 } else {
692 set oldsel $selectedline
694 set didsel 0
695 for {set l 0} {$l < $numcommits} {incr l} {
696 set id $lineid($l)
697 set info $commitinfo($id)
698 set doesmatch 0
699 foreach f $info ty $fldtypes {
700 if {$findloc != "All fields" && $findloc != $ty} {
701 continue
703 if {$findtype == "Regexp"} {
704 set matches [regexp -indices -all -inline $fstr $f]
705 } else {
706 if {$findtype == "IgnCase"} {
707 set str [string tolower $f]
708 } else {
709 set str $f
711 set matches {}
712 set i 0
713 while {[set j [string first $fstr $str $i]] >= 0} {
714 lappend matches [list $j [expr $j+$mlen-1]]
715 set i [expr $j + $mlen]
718 if {$matches == {}} continue
719 set doesmatch 1
720 if {$ty == "Headline"} {
721 markmatches $canv $l $f $linehtag($l) $matches $mainfont
722 } elseif {$ty == "Author"} {
723 markmatches $canv2 $l $f $linentag($l) $matches $namefont
724 } elseif {$ty == "Date"} {
725 markmatches $canv3 $l $f $linedtag($l) $matches $mainfont
728 if {$doesmatch} {
729 lappend matchinglines $l
730 if {!$didsel && $l > $oldsel} {
731 selectline $l
732 set didsel 1
736 if {$matchinglines == {}} {
737 bell
738 } elseif {!$didsel} {
739 selectline [lindex $matchinglines 0]
743 proc findnext {} {
744 global matchinglines selectedline
745 if {![info exists matchinglines]} {
746 dofind
747 return
749 if {![info exists selectedline]} return
750 foreach l $matchinglines {
751 if {$l > $selectedline} {
752 selectline $l
753 return
756 bell
759 proc findprev {} {
760 global matchinglines selectedline
761 if {![info exists matchinglines]} {
762 dofind
763 return
765 if {![info exists selectedline]} return
766 set prev {}
767 foreach l $matchinglines {
768 if {$l >= $selectedline} break
769 set prev $l
771 if {$prev != {}} {
772 selectline $prev
773 } else {
774 bell
778 proc markmatches {canv l str tag matches font} {
779 set bbox [$canv bbox $tag]
780 set x0 [lindex $bbox 0]
781 set y0 [lindex $bbox 1]
782 set y1 [lindex $bbox 3]
783 foreach match $matches {
784 set start [lindex $match 0]
785 set end [lindex $match 1]
786 if {$start > $end} continue
787 set xoff [font measure $font [string range $str 0 [expr $start-1]]]
788 set xlen [font measure $font [string range $str 0 [expr $end]]]
789 set t [$canv create rect [expr $x0+$xoff] $y0 [expr $x0+$xlen+2] $y1 \
790 -outline {} -tags matches -fill yellow]
791 $canv lower $t
795 proc unmarkmatches {} {
796 global matchinglines
797 allcanvs delete matches
798 catch {unset matchinglines}
801 proc selcanvline {x y} {
802 global canv canvy0 ctext linespc selectedline
803 global lineid linehtag linentag linedtag
804 set ymax [lindex [$canv cget -scrollregion] 3]
805 set yfrac [lindex [$canv yview] 0]
806 set y [expr {$y + $yfrac * $ymax}]
807 set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
808 if {$l < 0} {
809 set l 0
811 if {[info exists selectedline] && $selectedline == $l} return
812 unmarkmatches
813 selectline $l
816 proc selectline {l} {
817 global canv canv2 canv3 ctext commitinfo selectedline
818 global lineid linehtag linentag linedtag
819 global canvy canvy0 linespc nparents treepending
820 global cflist treediffs currentid sha1entry
821 if {![info exists lineid($l)] || ![info exists linehtag($l)]} return
822 $canv delete secsel
823 set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
824 -tags secsel -fill [$canv cget -selectbackground]]
825 $canv lower $t
826 $canv2 delete secsel
827 set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
828 -tags secsel -fill [$canv2 cget -selectbackground]]
829 $canv2 lower $t
830 $canv3 delete secsel
831 set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
832 -tags secsel -fill [$canv3 cget -selectbackground]]
833 $canv3 lower $t
834 set y [expr {$canvy0 + $l * $linespc}]
835 set ytop [expr {($y - $linespc / 2.0) / $canvy}]
836 set ybot [expr {($y + $linespc / 2.0) / $canvy}]
837 set wnow [$canv yview]
838 if {$ytop < [lindex $wnow 0]} {
839 allcanvs yview moveto $ytop
840 } elseif {$ybot > [lindex $wnow 1]} {
841 set wh [expr {[lindex $wnow 1] - [lindex $wnow 0]}]
842 allcanvs yview moveto [expr {$ybot - $wh}]
844 set selectedline $l
846 set id $lineid($l)
847 $sha1entry conf -state normal
848 $sha1entry delete 0 end
849 $sha1entry insert 0 $id
850 $sha1entry selection from 0
851 $sha1entry selection to end
852 $sha1entry conf -state readonly
854 $ctext conf -state normal
855 $ctext delete 0.0 end
856 set info $commitinfo($id)
857 $ctext insert end "Author: [lindex $info 1] [lindex $info 2]\n"
858 $ctext insert end "Committer: [lindex $info 3] [lindex $info 4]\n"
859 $ctext insert end "\n"
860 $ctext insert end [lindex $info 5]
861 $ctext insert end "\n"
862 $ctext tag delete Comments
863 $ctext conf -state disabled
865 $cflist delete 0 end
866 set currentid $id
867 if {$nparents($id) == 1} {
868 if {![info exists treediffs($id)]} {
869 if {![info exists treepending]} {
870 gettreediffs $id
872 } else {
873 addtocflist $id
878 proc selnextline {dir} {
879 global selectedline
880 if {![info exists selectedline]} return
881 set l [expr $selectedline + $dir]
882 unmarkmatches
883 selectline $l
886 proc addtocflist {id} {
887 global currentid treediffs cflist treepending
888 if {$id != $currentid} {
889 gettreediffs $currentid
890 return
892 $cflist insert end "All files"
893 foreach f $treediffs($currentid) {
894 $cflist insert end $f
896 getblobdiffs $id
899 proc gettreediffs {id} {
900 global treediffs parents treepending
901 set treepending $id
902 set treediffs($id) {}
903 set p [lindex $parents($id) 0]
904 if [catch {set gdtf [open "|git-diff-tree -r $p $id" r]}] return
905 fconfigure $gdtf -blocking 0
906 fileevent $gdtf readable "gettreediffline $gdtf $id"
909 proc gettreediffline {gdtf id} {
910 global treediffs treepending
911 set n [gets $gdtf line]
912 if {$n < 0} {
913 if {![eof $gdtf]} return
914 close $gdtf
915 unset treepending
916 addtocflist $id
917 return
919 set type [lindex $line 1]
920 set file [lindex $line 3]
921 if {$type == "blob"} {
922 lappend treediffs($id) $file
926 proc getblobdiffs {id} {
927 global parents diffopts blobdifffd env curdifftag curtagstart
928 set p [lindex $parents($id) 0]
929 set env(GIT_DIFF_OPTS) $diffopts
930 if [catch {set bdf [open "|git-diff-tree -r -p $p $id" r]} err] {
931 puts "error getting diffs: $err"
932 return
934 fconfigure $bdf -blocking 0
935 set blobdifffd($id) $bdf
936 set curdifftag Comments
937 set curtagstart 0.0
938 fileevent $bdf readable "getblobdiffline $bdf $id"
941 proc getblobdiffline {bdf id} {
942 global currentid blobdifffd ctext curdifftag curtagstart
943 set n [gets $bdf line]
944 if {$n < 0} {
945 if {[eof $bdf]} {
946 close $bdf
947 if {$id == $currentid && $bdf == $blobdifffd($id)} {
948 $ctext tag add $curdifftag $curtagstart end
951 return
953 if {$id != $currentid || $bdf != $blobdifffd($id)} {
954 return
956 $ctext conf -state normal
957 if {[regexp {^---[ \t]+([^/])+/(.*)} $line match s1 fname]} {
958 # start of a new file
959 $ctext insert end "\n"
960 $ctext tag add $curdifftag $curtagstart end
961 set curtagstart [$ctext index "end - 1c"]
962 set curdifftag "f:$fname"
963 $ctext tag delete $curdifftag
964 set l [expr {(78 - [string length $fname]) / 2}]
965 set pad [string range "----------------------------------------" 1 $l]
966 $ctext insert end "$pad $fname $pad\n" filesep
967 } elseif {[string range $line 0 2] == "+++"} {
968 # no need to do anything with this
969 } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
970 $line match f1l f1c f2l f2c rest]} {
971 $ctext insert end "\t" hunksep
972 $ctext insert end " $f1l " d0 " $f2l " d1
973 $ctext insert end " $rest \n" hunksep
974 } else {
975 set x [string range $line 0 0]
976 if {$x == "-" || $x == "+"} {
977 set tag [expr {$x == "+"}]
978 set line [string range $line 1 end]
979 $ctext insert end "$line\n" d$tag
980 } elseif {$x == " "} {
981 set line [string range $line 1 end]
982 $ctext insert end "$line\n"
983 } else {
984 # Something else we don't recognize
985 if {$curdifftag != "Comments"} {
986 $ctext insert end "\n"
987 $ctext tag add $curdifftag $curtagstart end
988 set curtagstart [$ctext index "end - 1c"]
989 set curdifftag Comments
991 $ctext insert end "$line\n" filesep
994 $ctext conf -state disabled
997 proc listboxsel {} {
998 global ctext cflist currentid treediffs
999 if {![info exists currentid]} return
1000 set sel [$cflist curselection]
1001 if {$sel == {} || [lsearch -exact $sel 0] >= 0} {
1002 # show everything
1003 $ctext tag conf Comments -elide 0
1004 foreach f $treediffs($currentid) {
1005 $ctext tag conf "f:$f" -elide 0
1007 } else {
1008 # just show selected files
1009 $ctext tag conf Comments -elide 1
1010 set i 1
1011 foreach f $treediffs($currentid) {
1012 set elide [expr {[lsearch -exact $sel $i] < 0}]
1013 $ctext tag conf "f:$f" -elide $elide
1014 incr i
1019 proc setcoords {} {
1020 global linespc charspc canvx0 canvy0 mainfont
1021 set linespc [font metrics $mainfont -linespace]
1022 set charspc [font measure $mainfont "m"]
1023 set canvy0 [expr 3 + 0.5 * $linespc]
1024 set canvx0 [expr 3 + 0.5 * $linespc]
1027 proc redisplay {} {
1028 global selectedline stopped redisplaying phase
1029 if {$stopped > 1} return
1030 if {$phase == "getcommits"} return
1031 set redisplaying 1
1032 if {$phase == "drawgraph"} {
1033 set stopped 1
1034 } else {
1035 drawgraph
1039 proc incrfont {inc} {
1040 global mainfont namefont textfont selectedline ctext canv phase
1041 global stopped
1042 unmarkmatches
1043 set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
1044 set namefont [lreplace $namefont 1 1 [expr {[lindex $namefont 1] + $inc}]]
1045 set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
1046 setcoords
1047 $ctext conf -font $textfont
1048 $ctext tag conf filesep -font [concat $textfont bold]
1049 if {$phase == "getcommits"} {
1050 $canv itemconf textitems -font $mainfont
1052 redisplay
1055 proc doquit {} {
1056 global stopped
1057 set stopped 100
1058 destroy .
1061 # defaults...
1062 set datemode 0
1063 set boldnames 0
1064 set diffopts "-U 5 -p"
1066 set mainfont {Helvetica 9}
1067 set namefont $mainfont
1068 set textfont {Courier 9}
1069 if {$boldnames} {
1070 lappend namefont bold
1073 set colors {green red blue magenta darkgrey brown orange}
1074 set colorbycommitter false
1076 catch {source ~/.gitk}
1078 set revtreeargs {}
1079 foreach arg $argv {
1080 switch -regexp -- $arg {
1081 "^$" { }
1082 "^-b" { set boldnames 1 }
1083 "^-c" { set colorbycommitter 1 }
1084 "^-d" { set datemode 1 }
1085 "^-.*" {
1086 puts stderr "unrecognized option $arg"
1087 exit 1
1089 default {
1090 lappend revtreeargs $arg
1095 set stopped 0
1096 set redisplaying 0
1097 set stuffsaved 0
1098 setcoords
1099 makewindow
1100 getcommits $revtreeargs