| # incremental search panel |
| # based on code from gitk, Copyright (C) Paul Mackerras |
| |
| class searchbar { |
| |
| field w |
| field ctext |
| |
| field searchstring {} |
| field regexpsearch |
| field default_regexpsearch |
| field casesensitive |
| field default_casesensitive |
| field smartcase |
| field searchdirn -forwards |
| |
| field history |
| field history_index |
| |
| field smarktop |
| field smarkbot |
| |
| constructor new {i_w i_text args} { |
| global use_ttk NS |
| set w $i_w |
| set ctext $i_text |
| |
| set default_regexpsearch [is_config_true gui.search.regexp] |
| switch -- [get_config gui.search.case] { |
| no { |
| set default_casesensitive 0 |
| set smartcase 0 |
| } |
| smart { |
| set default_casesensitive 0 |
| set smartcase 1 |
| } |
| yes - |
| default { |
| set default_casesensitive 1 |
| set smartcase 0 |
| } |
| } |
| |
| set history [list] |
| |
| ${NS}::frame $w |
| ${NS}::label $w.l -text [mc Find:] |
| tentry $w.ent -textvariable ${__this}::searchstring -background lightgreen |
| ${NS}::button $w.bn -text [mc Next] -command [cb find_next] |
| ${NS}::button $w.bp -text [mc Prev] -command [cb find_prev] |
| ${NS}::checkbutton $w.re -text [mc RegExp] \ |
| -variable ${__this}::regexpsearch -command [cb _incrsearch] |
| ${NS}::checkbutton $w.cs -text [mc Case] \ |
| -variable ${__this}::casesensitive -command [cb _incrsearch] |
| pack $w.l -side left |
| pack $w.cs -side right |
| pack $w.re -side right |
| pack $w.bp -side right |
| pack $w.bn -side right |
| pack $w.ent -side left -expand 1 -fill x |
| |
| eval grid conf $w -sticky we $args |
| grid remove $w |
| |
| trace add variable searchstring write [cb _incrsearch_cb] |
| bind $w.ent <Return> [cb find_next] |
| bind $w.ent <Shift-Return> [cb find_prev] |
| bind $w.ent <Key-Up> [cb _prev_search] |
| bind $w.ent <Key-Down> [cb _next_search] |
| |
| bind $w <Destroy> [list delete_this $this] |
| return $this |
| } |
| |
| method show {} { |
| if {![visible $this]} { |
| grid $w |
| $w.ent delete 0 end |
| set regexpsearch $default_regexpsearch |
| set casesensitive $default_casesensitive |
| set history_index [llength $history] |
| } |
| focus -force $w.ent |
| } |
| |
| method hide {} { |
| if {[visible $this]} { |
| focus $ctext |
| grid remove $w |
| _save_search $this |
| } |
| } |
| |
| method visible {} { |
| return [winfo ismapped $w] |
| } |
| |
| method editor {} { |
| return $w.ent |
| } |
| |
| method _get_new_anchor {} { |
| # use start of selection if it is visible, |
| # or the bounds of the visible area |
| set top [$ctext index @0,0] |
| set bottom [$ctext index @0,[winfo height $ctext]] |
| set sel [$ctext tag ranges sel] |
| if {$sel ne {}} { |
| set spos [lindex $sel 0] |
| if {[lindex $spos 0] >= [lindex $top 0] && |
| [lindex $spos 0] <= [lindex $bottom 0]} { |
| return $spos |
| } |
| } |
| if {$searchdirn eq "-forwards"} { |
| return $top |
| } else { |
| return $bottom |
| } |
| } |
| |
| method _get_wrap_anchor {dir} { |
| if {$dir eq "-forwards"} { |
| return 1.0 |
| } else { |
| return end |
| } |
| } |
| |
| method _do_search {start {mlenvar {}} {dir {}} {endbound {}}} { |
| set cmd [list $ctext search] |
| if {$mlenvar ne {}} { |
| upvar $mlenvar mlen |
| lappend cmd -count mlen |
| } |
| if {$regexpsearch} { |
| lappend cmd -regexp |
| } |
| if {!$casesensitive} { |
| lappend cmd -nocase |
| } |
| if {$dir eq {}} { |
| set dir $searchdirn |
| } |
| lappend cmd $dir -- $searchstring |
| if {[catch { |
| if {$endbound ne {}} { |
| set here [eval $cmd [list $start] [list $endbound]] |
| } else { |
| set here [eval $cmd [list $start]] |
| if {$here eq {}} { |
| set here [eval $cmd [_get_wrap_anchor $this $dir]] |
| } |
| } |
| } err]} { set here {} } |
| return $here |
| } |
| |
| method _incrsearch_cb {name ix op} { |
| after idle [cb _incrsearch] |
| } |
| |
| method _incrsearch {} { |
| $ctext tag remove found 1.0 end |
| if {[catch {$ctext index anchor}]} { |
| $ctext mark set anchor [_get_new_anchor $this] |
| } |
| if {$searchstring ne {}} { |
| if {$smartcase && [regexp {[[:upper:]]} $searchstring]} { |
| set casesensitive 1 |
| } |
| set here [_do_search $this anchor mlen] |
| if {$here ne {}} { |
| $ctext see $here |
| $ctext tag remove sel 1.0 end |
| $ctext tag add sel $here "$here + $mlen c" |
| #$w.ent configure -background lightgreen |
| $w.ent state !pressed |
| _set_marks $this 1 |
| } else { |
| #$w.ent configure -background lightpink |
| $w.ent state pressed |
| } |
| } elseif {$smartcase} { |
| # clearing the field resets the smart case detection |
| set casesensitive 0 |
| } |
| } |
| |
| method _save_search {} { |
| if {$searchstring eq {}} { |
| return |
| } |
| if {[llength $history] > 0} { |
| foreach {s_regexp s_case s_expr} [lindex $history end] break |
| } else { |
| set s_regexp $regexpsearch |
| set s_case $casesensitive |
| set s_expr "" |
| } |
| if {$searchstring eq $s_expr} { |
| # update modes |
| set history [lreplace $history end end \ |
| [list $regexpsearch $casesensitive $searchstring]] |
| } else { |
| lappend history [list $regexpsearch $casesensitive $searchstring] |
| } |
| set history_index [llength $history] |
| } |
| |
| method _prev_search {} { |
| if {$history_index > 0} { |
| incr history_index -1 |
| foreach {s_regexp s_case s_expr} [lindex $history $history_index] break |
| $w.ent delete 0 end |
| $w.ent insert 0 $s_expr |
| set regexpsearch $s_regexp |
| set casesensitive $s_case |
| } |
| } |
| |
| method _next_search {} { |
| if {$history_index < [llength $history]} { |
| incr history_index |
| } |
| if {$history_index < [llength $history]} { |
| foreach {s_regexp s_case s_expr} [lindex $history $history_index] break |
| } else { |
| set s_regexp $default_regexpsearch |
| set s_case $default_casesensitive |
| set s_expr "" |
| } |
| $w.ent delete 0 end |
| $w.ent insert 0 $s_expr |
| set regexpsearch $s_regexp |
| set casesensitive $s_case |
| } |
| |
| method find_prev {} { |
| find_next $this -backwards |
| } |
| |
| method find_next {{dir -forwards}} { |
| focus $w.ent |
| $w.ent icursor end |
| set searchdirn $dir |
| $ctext mark unset anchor |
| if {$searchstring ne {}} { |
| _save_search $this |
| set start [_get_new_anchor $this] |
| if {$dir eq "-forwards"} { |
| set start "$start + 1c" |
| } |
| set match [_do_search $this $start mlen] |
| $ctext tag remove sel 1.0 end |
| if {$match ne {}} { |
| $ctext see $match |
| $ctext tag add sel $match "$match + $mlen c" |
| } |
| } |
| } |
| |
| method _mark_range {first last} { |
| set mend $first.0 |
| while {1} { |
| set match [_do_search $this $mend mlen -forwards $last.end] |
| if {$match eq {}} break |
| set mend "$match + $mlen c" |
| $ctext tag add found $match $mend |
| } |
| } |
| |
| method _set_marks {doall} { |
| set topline [lindex [split [$ctext index @0,0] .] 0] |
| set botline [lindex [split [$ctext index @0,[winfo height $ctext]] .] 0] |
| if {$doall || $botline < $smarktop || $topline > $smarkbot} { |
| # no overlap with previous |
| _mark_range $this $topline $botline |
| set smarktop $topline |
| set smarkbot $botline |
| } else { |
| if {$topline < $smarktop} { |
| _mark_range $this $topline [expr {$smarktop-1}] |
| set smarktop $topline |
| } |
| if {$botline > $smarkbot} { |
| _mark_range $this [expr {$smarkbot+1}] $botline |
| set smarkbot $botline |
| } |
| } |
| } |
| |
| method scrolled {} { |
| if {$searchstring ne {}} { |
| after idle [cb _set_marks 0] |
| } |
| } |
| |
| } |