Code

fe165724ed3ed8c5645dae327208013d0e2c340c
[git.git] / lib / search.tcl
1 # incremental search panel
2 # based on code from gitk, Copyright (C) Paul Mackerras
4 class searchbar {
6 field w
7 field ctext
9 field searchstring   {}
10 field regexpsearch
11 field default_regexpsearch
12 field casesensitive
13 field default_casesensitive
14 field searchdirn     -forwards
16 field history
17 field history_index
19 field smarktop
20 field smarkbot
22 constructor new {i_w i_text args} {
23         global use_ttk NS
24         set w      $i_w
25         set ctext  $i_text
27         set default_regexpsearch [is_config_true gui.search.regexp]
28         if {[is_config_true gui.search.smartcase]} {
29                 set default_casesensitive 0
30         } else {
31                 set default_casesensitive 1
32         }
34         set history [list]
36         ${NS}::frame  $w
37         ${NS}::label  $w.l       -text [mc Find:]
38         tentry  $w.ent -textvariable ${__this}::searchstring -background lightgreen
39         ${NS}::button $w.bn      -text [mc Next] -command [cb find_next]
40         ${NS}::button $w.bp      -text [mc Prev] -command [cb find_prev]
41         ${NS}::checkbutton $w.re -text [mc RegExp] \
42                 -variable ${__this}::regexpsearch -command [cb _incrsearch]
43         ${NS}::checkbutton $w.cs -text [mc Case] \
44                 -variable ${__this}::casesensitive -command [cb _incrsearch]
45         pack   $w.l   -side left
46         pack   $w.cs  -side right
47         pack   $w.re  -side right
48         pack   $w.bp  -side right
49         pack   $w.bn  -side right
50         pack   $w.ent -side left -expand 1 -fill x
52         eval grid conf $w -sticky we $args
53         grid remove $w
55         trace add variable searchstring write [cb _incrsearch_cb]
56         bind $w.ent <Return> [cb find_next]
57         bind $w.ent <Shift-Return> [cb find_prev]
58         bind $w.ent <Key-Up>   [cb _prev_search]
59         bind $w.ent <Key-Down> [cb _next_search]
60         
61         bind $w <Destroy> [list delete_this $this]
62         return $this
63 }
65 method show {} {
66         if {![visible $this]} {
67                 grid $w
68                 $w.ent delete 0 end
69                 set regexpsearch  $default_regexpsearch
70                 set casesensitive $default_casesensitive
71                 set history_index [llength $history]
72         }
73         focus -force $w.ent
74 }
76 method hide {} {
77         if {[visible $this]} {
78                 focus $ctext
79                 grid remove $w
80                 _save_search $this
81         }
82 }
84 method visible {} {
85         return [winfo ismapped $w]
86 }
88 method editor {} {
89         return $w.ent
90 }
92 method _get_new_anchor {} {
93         # use start of selection if it is visible,
94         # or the bounds of the visible area
95         set top    [$ctext index @0,0]
96         set bottom [$ctext index @0,[winfo height $ctext]]
97         set sel    [$ctext tag ranges sel]
98         if {$sel ne {}} {
99                 set spos [lindex $sel 0]
100                 if {[lindex $spos 0] >= [lindex $top 0] &&
101                     [lindex $spos 0] <= [lindex $bottom 0]} {
102                         return $spos
103                 }
104         }
105         if {$searchdirn eq "-forwards"} {
106                 return $top
107         } else {
108                 return $bottom
109         }
112 method _get_wrap_anchor {dir} {
113         if {$dir eq "-forwards"} {
114                 return 1.0
115         } else {
116                 return end
117         }
120 method _do_search {start {mlenvar {}} {dir {}} {endbound {}}} {
121         set cmd [list $ctext search]
122         if {$mlenvar ne {}} {
123                 upvar $mlenvar mlen
124                 lappend cmd -count mlen
125         }
126         if {$regexpsearch} {
127                 lappend cmd -regexp
128         }
129         if {!$casesensitive} {
130                 lappend cmd -nocase
131         }
132         if {$dir eq {}} {
133                 set dir $searchdirn
134         }
135         lappend cmd $dir -- $searchstring
136         if {$endbound ne {}} {
137                 set here [eval $cmd [list $start] [list $endbound]]
138         } else {
139                 set here [eval $cmd [list $start]]
140                 if {$here eq {}} {
141                         set here [eval $cmd [_get_wrap_anchor $this $dir]]
142                 }
143         }
144         return $here
147 method _incrsearch_cb {name ix op} {
148         after idle [cb _incrsearch]
151 method _incrsearch {} {
152         $ctext tag remove found 1.0 end
153         if {[catch {$ctext index anchor}]} {
154                 $ctext mark set anchor [_get_new_anchor $this]
155         }
156         if {[regexp {[[:upper:]]} $searchstring]} {
157                 set casesensitive 1
158         }
159         if {$searchstring ne {}} {
160                 set here [_do_search $this anchor mlen]
161                 if {$here ne {}} {
162                         $ctext see $here
163                         $ctext tag remove sel 1.0 end
164                         $ctext tag add sel $here "$here + $mlen c"
165                         #$w.ent configure -background lightgreen
166                         $w.ent state !pressed
167                         _set_marks $this 1
168                 } else {
169                         #$w.ent configure -background lightpink
170                         $w.ent state pressed
171                 }
172         }
175 method _save_search {} {
176         if {$searchstring eq {}} {
177                 return
178         }
179         if {[llength $history] > 0} {
180                 foreach {s_regexp s_case s_expr} [lindex $history end] break
181         } else {
182                 set s_regexp $regexpsearch
183                 set s_case   $casesensitive
184                 set s_expr   ""
185         }
186         if {$searchstring eq $s_expr} {
187                 # update modes
188                 set history [lreplace $history end end \
189                                 [list $regexpsearch $casesensitive $searchstring]]
190         } else {
191                 lappend history [list $regexpsearch $casesensitive $searchstring]
192         }
193         set history_index [llength $history]
196 method _prev_search {} {
197         if {$history_index > 0} {
198                 incr history_index -1
199                 foreach {s_regexp s_case s_expr} [lindex $history $history_index] break
200                 $w.ent delete 0 end
201                 $w.ent insert 0 $s_expr
202                 set regexpsearch $s_regexp
203                 set casesensitive $s_case
204         }
207 method _next_search {} {
208         if {$history_index < [llength $history]} {
209                 incr history_index
210         }
211         if {$history_index < [llength $history]} {
212                 foreach {s_regexp s_case s_expr} [lindex $history $history_index] break
213         } else {
214                 set s_regexp $default_regexpsearch
215                 set s_case   $default_casesensitive
216                 set s_expr   ""
217         }
218         $w.ent delete 0 end
219         $w.ent insert 0 $s_expr
220         set regexpsearch $s_regexp
221         set casesensitive $s_case
224 method find_prev {} {
225         find_next $this -backwards
228 method find_next {{dir -forwards}} {
229         focus $w.ent
230         $w.ent icursor end
231         set searchdirn $dir
232         $ctext mark unset anchor
233         if {$searchstring ne {}} {
234                 _save_search $this
235                 set start [_get_new_anchor $this]
236                 if {$dir eq "-forwards"} {
237                         set start "$start + 1c"
238                 }
239                 set match [_do_search $this $start mlen]
240                 $ctext tag remove sel 1.0 end
241                 if {$match ne {}} {
242                         $ctext see $match
243                         $ctext tag add sel $match "$match + $mlen c"
244                 }
245         }
248 method _mark_range {first last} {
249         set mend $first.0
250         while {1} {
251                 set match [_do_search $this $mend mlen -forwards $last.end]
252                 if {$match eq {}} break
253                 set mend "$match + $mlen c"
254                 $ctext tag add found $match $mend
255         }
258 method _set_marks {doall} {
259         set topline [lindex [split [$ctext index @0,0] .] 0]
260         set botline [lindex [split [$ctext index @0,[winfo height $ctext]] .] 0]
261         if {$doall || $botline < $smarktop || $topline > $smarkbot} {
262                 # no overlap with previous
263                 _mark_range $this $topline $botline
264                 set smarktop $topline
265                 set smarkbot $botline
266         } else {
267                 if {$topline < $smarktop} {
268                         _mark_range $this $topline [expr {$smarktop-1}]
269                         set smarktop $topline
270                 }
271                 if {$botline > $smarkbot} {
272                         _mark_range $this [expr {$smarkbot+1}] $botline
273                         set smarkbot $botline
274                 }
275         }
278 method scrolled {} {
279         if {$searchstring ne {}} {
280                 after idle [cb _set_marks 0]
281         }