Code

git-gui: Display commit/tag/remote info in tooltip of revision picker
[git.git] / lib / choose_rev.tcl
1 # git-gui revision chooser
2 # Copyright (C) 2006, 2007 Shawn Pearce
4 class choose_rev {
6 image create photo ::choose_rev::img_find -data {R0lGODlhEAAQAIYAAPwCBCQmJDw+PBQSFAQCBMza3NTm5MTW1HyChOT29Ozq7MTq7Kze5Kzm7Oz6/NTy9Iza5GzGzKzS1Nzy9Nz29Kzq9HTGzHTK1Lza3AwKDLzu9JTi7HTW5GTCzITO1Mzq7Hza5FTK1ESyvHzKzKzW3DQyNDyqtDw6PIzW5HzGzAT+/Dw+RKyurNTOzMTGxMS+tJSGdATCxHRydLSqpLymnLSijBweHERCRNze3Pz69PTy9Oze1OTSxOTGrMSqlLy+vPTu5OzSvMymjNTGvNS+tMy2pMyunMSefAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAAALAAAAAAQABAAAAe4gACCAAECA4OIiAIEBQYHBAKJgwIICQoLDA0IkZIECQ4PCxARCwSSAxITFA8VEBYXGBmJAQYLGhUbHB0eH7KIGRIMEBAgISIjJKaIJQQLFxERIialkieUGigpKRoIBCqJKyyLBwvJAioEyoICLS4v6QQwMQQyLuqLli8zNDU2BCf1lN3AkUPHDh49fAQAAEnGD1MCCALZEaSHkIUMBQS8wWMIkSJGhBzBmFEGgRsBUqpMiSgdAD+BAAAh/mhDcmVhdGVkIGJ5IEJNUFRvR0lGIFBybyB2ZXJzaW9uIDIuNQ0KqSBEZXZlbENvciAxOTk3LDE5OTguIEFsbCByaWdodHMgcmVzZXJ2ZWQuDQpodHRwOi8vd3d3LmRldmVsY29yLmNvbQA7}
8 field w               ; # our megawidget path
9 field w_list          ; # list of currently filtered specs
10 field w_filter        ; # filter entry for $w_list
12 field c_expr        {}; # current revision expression
13 field filter          ; # current filter string
14 field revtype     head; # type of revision chosen
15 field cur_specs [list]; # list of specs for $revtype
16 field spec_head       ; # list of all head specs
17 field spec_trck       ; # list of all tracking branch specs
18 field spec_tag        ; # list of all tag specs
19 field tip_data        ; # array of tip commit info by refname
21 field tooltip_wm        {} ; # Current tooltip toplevel, if open
22 field tooltip_t         {} ; # Text widget in $tooltip_wm
23 field tooltip_timer     {} ; # Current timer event for our tooltip
25 proc new {path {title {}}} {
26         return [_new $path 0 $title]
27 }
29 proc new_unmerged {path {title {}}} {
30         return [_new $path 1 $title]
31 }
33 constructor _new {path unmerged_only title} {
34         global current_branch is_detached
36         set w $path
38         if {$title ne {}} {
39                 labelframe $w -text $title
40         } else {
41                 frame $w
42         }
43         bind $w <Destroy> [cb _delete %W]
45         if {$is_detached} {
46                 radiobutton $w.detachedhead_r \
47                         -anchor w \
48                         -text {This Detached Checkout} \
49                         -value HEAD \
50                         -variable @revtype
51                 grid $w.detachedhead_r -sticky we -padx {0 5} -columnspan 2
52         }
54         radiobutton $w.expr_r \
55                 -text {Revision Expression:} \
56                 -value expr \
57                 -variable @revtype
58         entry $w.expr_t \
59                 -borderwidth 1 \
60                 -relief sunken \
61                 -width 50 \
62                 -textvariable @c_expr \
63                 -validate key \
64                 -validatecommand [cb _validate %d %S]
65         grid $w.expr_r $w.expr_t -sticky we -padx {0 5}
67         frame $w.types
68         radiobutton $w.types.head_r \
69                 -text {Local Branch} \
70                 -value head \
71                 -variable @revtype
72         pack $w.types.head_r -side left
73         radiobutton $w.types.trck_r \
74                 -text {Tracking Branch} \
75                 -value trck \
76                 -variable @revtype
77         pack $w.types.trck_r -side left
78         radiobutton $w.types.tag_r \
79                 -text {Tag} \
80                 -value tag \
81                 -variable @revtype
82         pack $w.types.tag_r -side left
83         set w_filter $w.types.filter
84         entry $w_filter \
85                 -borderwidth 1 \
86                 -relief sunken \
87                 -width 12 \
88                 -textvariable @filter \
89                 -validate key \
90                 -validatecommand [cb _filter %P]
91         pack $w_filter -side right
92         pack [label $w.types.filter_icon \
93                 -image ::choose_rev::img_find \
94                 ] -side right
95         grid $w.types -sticky we -padx {0 5} -columnspan 2
97         frame $w.list
98         set w_list $w.list.l
99         listbox $w_list \
100                 -font font_diff \
101                 -width 50 \
102                 -height 10 \
103                 -selectmode browse \
104                 -exportselection false \
105                 -xscrollcommand [cb _sb_set $w.list.sbx h] \
106                 -yscrollcommand [cb _sb_set $w.list.sby v]
107         pack $w_list -fill both -expand 1
108         grid $w.list -sticky nswe -padx {20 5} -columnspan 2
109         bind $w_list <Any-Motion>  [cb _show_tooltip @%x,%y]
110         bind $w_list <Any-Enter>   [cb _hide_tooltip]
111         bind $w_list <Any-Leave>   [cb _hide_tooltip]
112         bind $w_list <Destroy>     [cb _hide_tooltip]
114         grid columnconfigure $w 1 -weight 1
115         if {$is_detached} {
116                 grid rowconfigure $w 3 -weight 1
117         } else {
118                 grid rowconfigure $w 2 -weight 1
119         }
121         trace add variable @revtype write [cb _select]
122         bind $w_filter <Key-Return> [list focus $w_list]\;break
123         bind $w_filter <Key-Down>   [list focus $w_list]
125         set fmt list
126         append fmt { %(refname)}
127         append fmt { [list}
128         append fmt { %(objecttype)}
129         append fmt { %(objectname)}
130         append fmt { [concat %(taggername) %(authorname)]}
131         append fmt { [concat %(taggerdate) %(authordate)]}
132         append fmt { %(subject)}
133         append fmt {] [list}
134         append fmt { %(*objecttype)}
135         append fmt { %(*objectname)}
136         append fmt { %(*authorname)}
137         append fmt { %(*authordate)}
138         append fmt { %(*subject)}
139         append fmt {]}
140         set all_refn [list]
141         set fr_fd [git_read for-each-ref \
142                 --tcl \
143                 --sort=-taggerdate \
144                 --format=$fmt \
145                 refs/heads \
146                 refs/remotes \
147                 refs/tags \
148                 ]
149         fconfigure $fr_fd -translation lf -encoding utf-8
150         while {[gets $fr_fd line] > 0} {
151                 set line [eval $line]
152                 if {[lindex $line 1 0] eq {tag}} {
153                         if {[lindex $line 2 0] eq {commit}} {
154                                 set sha1 [lindex $line 2 1]
155                         } else {
156                                 continue
157                         }
158                 } elseif {[lindex $line 1 0] eq {commit}} {
159                         set sha1 [lindex $line 1 1]
160                 } else {
161                         continue
162                 }
163                 set refn [lindex $line 0]
164                 set tip_data($refn) [lrange $line 1 end]
165                 lappend cmt_refn($sha1) $refn
166                 lappend all_refn $refn
167         }
168         close $fr_fd
170         if {$unmerged_only} {
171                 set fr_fd [git_read rev-list --all ^$::HEAD]
172                 while {[gets $fr_fd sha1] > 0} {
173                         if {[catch {set rlst $cmt_refn($sha1)}]} continue
174                         foreach refn $rlst {
175                                 set inc($refn) 1
176                         }
177                 }
178                 close $fr_fd
179         } else {
180                 foreach refn $all_refn {
181                         set inc($refn) 1
182                 }
183         }
185         set spec_head [list]
186         foreach name [load_all_heads] {
187                 set refn refs/heads/$name
188                 if {[info exists inc($refn)]} {
189                         lappend spec_head [list $name $refn]
190                 }
191         }
193         set spec_trck [list]
194         foreach spec [all_tracking_branches] {
195                 set refn [lindex $spec 0]
196                 if {[info exists inc($refn)]} {
197                         regsub ^refs/(heads|remotes)/ $refn {} name
198                         lappend spec_trck [concat $name $spec]
199                 }
200         }
202         set spec_tag [list]
203         foreach name [load_all_tags] {
204                 set refn refs/tags/$name
205                 if {[info exists inc($refn)]} {
206                         lappend spec_tag [list $name $refn]
207                 }
208         }
210                   if {$is_detached}             { set revtype HEAD
211         } elseif {[llength $spec_head] > 0} { set revtype head
212         } elseif {[llength $spec_trck] > 0} { set revtype trck
213         } elseif {[llength $spec_tag ] > 0} { set revtype tag
214         } else {                              set revtype expr
215         }
217         if {$revtype eq {head} && $current_branch ne {}} {
218                 set i 0
219                 foreach spec $spec_head {
220                         if {[lindex $spec 0] eq $current_branch} {
221                                 $w_list selection clear 0 end
222                                 $w_list selection set $i
223                                 break
224                         }
225                         incr i
226                 }
227         }
229         return $this
232 method none {text} {
233         if {![winfo exists $w.none_r]} {
234                 radiobutton $w.none_r \
235                         -anchor w \
236                         -value none \
237                         -variable @revtype
238                 grid $w.none_r -sticky we -padx {0 5} -columnspan 2
239         }
240         $w.none_r configure -text $text
243 method get {} {
244         switch -- $revtype {
245         head -
246         trck -
247         tag  {
248                 set i [$w_list curselection]
249                 if {$i ne {}} {
250                         return [lindex $cur_specs $i 0]
251                 } else {
252                         return {}
253                 }
254         }
256         HEAD { return HEAD                     }
257         expr { return $c_expr                  }
258         none { return {}                       }
259         default { error "unknown type of revision" }
260         }
263 method pick_tracking_branch {} {
264         set revtype trck
267 method focus_filter {} {
268         if {[$w_filter cget -state] eq {normal}} {
269                 focus $w_filter
270         }
273 method bind_listbox {event script}  {
274         bind $w_list $event $script
277 method get_local_branch {} {
278         if {$revtype eq {head}} {
279                 return [_expr $this]
280         } else {
281                 return {}
282         }
285 method get_tracking_branch {} {
286         set i [$w_list curselection]
287         if {$i eq {} || $revtype ne {trck}} {
288                 return {}
289         }
290         return [lrange [lindex $cur_specs $i] 1 end]
293 method get_commit {} {
294         set e [_expr $this]
295         if {$e eq {}} {
296                 return {}
297         }
298         return [git rev-parse --verify "$e^0"]
301 method commit_or_die {} {
302         if {[catch {set new [get_commit $this]} err]} {
304                 # Cleanup the not-so-friendly error from rev-parse.
305                 #
306                 regsub {^fatal:\s*} $err {} err
307                 if {$err eq {Needed a single revision}} {
308                         set err {}
309                 }
311                 set top [winfo toplevel $w]
312                 set msg "Invalid revision: [get $this]\n\n$err"
313                 tk_messageBox \
314                         -icon error \
315                         -type ok \
316                         -title [wm title $top] \
317                         -parent $top \
318                         -message $msg
319                 error $msg
320         }
321         return $new
324 method _expr {} {
325         switch -- $revtype {
326         head -
327         trck -
328         tag  {
329                 set i [$w_list curselection]
330                 if {$i ne {}} {
331                         return [lindex $cur_specs $i 1]
332                 } else {
333                         error "No revision selected."
334                 }
335         }
337         expr {
338                 if {$c_expr ne {}} {
339                         return $c_expr
340                 } else {
341                         error "Revision expression is empty."
342                 }
343         }
344         HEAD { return HEAD                     }
345         none { return {}                       }
346         default { error "unknown type of revision"      }
347         }
350 method _validate {d S} {
351         if {$d == 1} {
352                 if {[regexp {\s} $S]} {
353                         return 0
354                 }
355                 if {[string length $S] > 0} {
356                         set revtype expr
357                 }
358         }
359         return 1
362 method _filter {P} {
363         if {[regexp {\s} $P]} {
364                 return 0
365         }
366         _rebuild $this $P
367         return 1
370 method _select {args} {
371         _rebuild $this $filter
372         focus_filter $this
375 method _rebuild {pat} {
376         set ste normal
377         switch -- $revtype {
378         head { set new $spec_head }
379         trck { set new $spec_trck }
380         tag  { set new $spec_tag  }
381         expr -
382         HEAD -
383         none {
384                 set new [list]
385                 set ste disabled
386         }
387         }
389         if {[$w_list cget -state] eq {disabled}} {
390                 $w_list configure -state normal
391         }
392         $w_list delete 0 end
394         if {$pat ne {}} {
395                 set pat *${pat}*
396         }
397         set cur_specs [list]
398         foreach spec $new {
399                 set txt [lindex $spec 0]
400                 if {$pat eq {} || [string match $pat $txt]} {
401                         lappend cur_specs $spec
402                         $w_list insert end $txt
403                 }
404         }
405         if {$cur_specs ne {}} {
406                 $w_list selection clear 0 end
407                 $w_list selection set 0
408         }
410         if {[$w_filter cget -state] ne $ste} {
411                 $w_list   configure -state $ste
412                 $w_filter configure -state $ste
413         }
416 method _delete {current} {
417         if {$current eq $w} {
418                 delete_this
419         }
422 method _sb_set {sb orient first last} {
423         set old_focus [focus -lastfor $w]
425         if {$first == 0 && $last == 1} {
426                 if {[winfo exists $sb]} {
427                         destroy $sb
428                         if {$old_focus ne {}} {
429                                 update
430                                 focus $old_focus
431                         }
432                 }
433                 return
434         }
436         if {![winfo exists $sb]} {
437                 if {$orient eq {h}} {
438                         scrollbar $sb -orient h -command [list $w_list xview]
439                         pack $sb -fill x -side bottom -before $w_list
440                 } else {
441                         scrollbar $sb -orient v -command [list $w_list yview]
442                         pack $sb -fill y -side right -before $w_list
443                 }
444                 if {$old_focus ne {}} {
445                         update
446                         focus $old_focus
447                 }
448         }
449         $sb set $first $last
452 method _show_tooltip {pos} {
453         if {$tooltip_wm ne {}} {
454                 _open_tooltip $this
455         } elseif {$tooltip_timer eq {}} {
456                 set tooltip_timer [after 1000 [cb _open_tooltip]]
457         }
460 method _open_tooltip {} {
461         global remote_url
463         set tooltip_timer {}
464         set pos_x [winfo pointerx $w_list]
465         set pos_y [winfo pointery $w_list]
466         if {[winfo containing $pos_x $pos_y] ne $w_list} {
467                 _hide_tooltip $this
468                 return
469         }
471         set pos @[join [list \
472                 [expr {$pos_x - [winfo rootx $w_list]}] \
473                 [expr {$pos_y - [winfo rooty $w_list]}]] ,]
474         set lno [$w_list index $pos]
475         if {$lno eq {}} {
476                 _hide_tooltip $this
477                 return
478         }
480         set spec [lindex $cur_specs $lno]
481         set refn [lindex $spec 1]
482         if {$refn eq {}} {
483                 _hide_tooltip $this
484                 return
485         }
487         if {$tooltip_wm eq {}} {
488                 set tooltip_wm [toplevel $w_list.tooltip -borderwidth 1]
489                 wm overrideredirect $tooltip_wm 1
490                 wm transient $tooltip_wm [winfo toplevel $w_list]
491                 set tooltip_t $tooltip_wm.label
492                 text $tooltip_t \
493                         -takefocus 0 \
494                         -highlightthickness 0 \
495                         -relief flat \
496                         -borderwidth 0 \
497                         -wrap none \
498                         -background lightyellow \
499                         -foreground black
500                 $tooltip_t tag conf section_header -font font_uibold
501                 bind $tooltip_wm <Escape> [cb _hide_tooltip]
502                 pack $tooltip_t
503         } else {
504                 $tooltip_t conf -state normal
505                 $tooltip_t delete 0.0 end
506         }
508         set data $tip_data($refn)
509         if {[lindex $data 0 0] eq {tag}} {
510                 set tag  [lindex $data 0]
511                 if {[lindex $data 1 0] eq {commit}} {
512                         set cmit [lindex $data 1]
513                 } else {
514                         set cmit {}
515                 }
516         } elseif {[lindex $data 0 0] eq {commit}} {
517                 set tag  {}
518                 set cmit [lindex $data 0]
519         }
521         $tooltip_t insert end "[lindex $spec 0]\n"
523         if {$tag ne {}} {
524                 $tooltip_t insert end "\n"
525                 $tooltip_t insert end "tag" section_header
526                 $tooltip_t insert end "  [lindex $tag 1]\n"
527                 $tooltip_t insert end [lindex $tag 2]
528                 $tooltip_t insert end " ([lindex $tag 3])\n"
529                 $tooltip_t insert end [lindex $tag 4]
530                 $tooltip_t insert end "\n"
531         }
533         if {$cmit ne {}} {
534                 $tooltip_t insert end "\n"
535                 $tooltip_t insert end "commit" section_header
536                 $tooltip_t insert end "  [lindex $cmit 1]\n"
537                 $tooltip_t insert end [lindex $cmit 2]
538                 $tooltip_t insert end " ([lindex $cmit 3])\n"
539                 $tooltip_t insert end [lindex $cmit 4]
540         }
542         if {[llength $spec] > 2} {
543                 $tooltip_t insert end "\n"
544                 $tooltip_t insert end "remote" section_header
545                 $tooltip_t insert end "  [lindex $spec 2]\n"
546                 $tooltip_t insert end "url"
547                 $tooltip_t insert end " $remote_url([lindex $spec 2])\n"
548                 $tooltip_t insert end "branch"
549                 $tooltip_t insert end " [lindex $spec 3]"
550         }
552         $tooltip_t conf -state disabled
553         _position_tooltip $this
556 method _position_tooltip {} {
557         set max_h [lindex [split [$tooltip_t index end] .] 0]
558         set max_w 0
559         for {set i 1} {$i <= $max_h} {incr i} {
560                 set c [lindex [split [$tooltip_t index "$i.0 lineend"] .] 1]
561                 if {$c > $max_w} {set max_w $c}
562         }
563         $tooltip_t conf -width $max_w -height $max_h
565         set req_w [winfo reqwidth  $tooltip_t]
566         set req_h [winfo reqheight $tooltip_t]
567         set pos_x [expr {[winfo pointerx .] +  5}]
568         set pos_y [expr {[winfo pointery .] + 10}]
570         set g "${req_w}x${req_h}"
571         if {$pos_x >= 0} {append g +}
572         append g $pos_x
573         if {$pos_y >= 0} {append g +}
574         append g $pos_y
576         wm geometry $tooltip_wm $g
577         raise $tooltip_wm
580 method _hide_tooltip {} {
581         if {$tooltip_wm ne {}} {
582                 destroy $tooltip_wm
583                 set tooltip_wm {}
584         }
585         if {$tooltip_timer ne {}} {
586                 after cancel $tooltip_timer
587                 set tooltip_timer {}
588         }