When using text widget for some serious text editing – such as HTML patterns – then it's helpful to have a simple package dealing the job of Search, Replace with all bells and whistles.
Usage: see documentation.
License: OLL as always. Have fun! wdb
Update: updated version 0.2
#!/usr/bin/wish
#
# file: textSearchWindow-0.2.tm
#
# usage: textSearchWindow $textwidget
# opens search dialog
#
# Button "Search" selects search text.
# Button "Mark all" hilights all occurrences of search text.
# Button "Replace" replaces currently found search text.
# Button "Replace all" replaces all search text as follows:
# - if some text selected: inside selection bounds,
# - radiobutton "Up": upwards from insert to start of text,
# - radiobutton "Down": downwards from insert to end of text.
# Button "Replace, Search" invokes buttons "Replace", then "Search".
# Serves as shorcut for case-wise manual replacements.
# Radiobuttons "Up", "Down" decide search direction.
# Checkbutton "Case-sensitive" decides on exact search.
# Checkbutton "Regexp" switches to pattern matching.
# Pattern matching uses substitution of \0, \1, \2 ...
#
# Key <Return> invokes button "Search".
# Key <Escape> invokes button "Cancel".
#
package provide textSearchWindow 0.2
namespace eval textSearchWindowDialog {
# try to install translation tools
if {[catch {
package require msgcat
namespace import ::msgcat::mc
}]} then {
proc mc x {set x}
}
namespace export textSearchWindow
array set search {
text ""
replace ""
direction -forwards
case -nocase
count ""
all ""
regexp ""
found 0
}
}
proc textSearchWindowDialog::textSearchWindow win {
# create search dialog for text widget
set search $win.search
if false then {
destroy $search
} elseif {[winfo exists $search]} then {
focus -force $search.oldtext
return
}
set search [toplevel $search]
bind $search <Return> "$search.find invoke"
bind $search <Escape> "$search.cancel invoke"
wm transient $search [winfo toplevel $win]
wm resizable $search yes no
grid columnconfigure $search 0 -weight 1
grid\
[entry $search.oldtext\
-textvariable ::textSearchWindowDialog::search(text)]\
[button $search.find -text [mc Search] -command [subst {
textSearchWindowDialog::findSelect $win
}]]\
[button $search.mark -text [mc "Mark all"] -command [subst {
textSearchWindowDialog::markAll $win
}]]\
-sticky news
grid\
[entry $search.newtext\
-textvariable ::textSearchWindowDialog::search(replace)]\
[button $search.replace -text [mc Replace] -command [subst {
textSearchWindowDialog::replace $win
}]]\
[button $search.replaceall -text [mc "Replace all"] -command [subst {
textSearchWindowDialog::replaceAll $win
}]]\
-sticky news
set options $search.options
grid\
[frame $options -bg ""]\
[button $search.replacefind -text [mc "Replace, Search"]\
-command [subst {
$search.replace invoke
$search.find invoke
}]]\
[button $search.cancel -text [mc Cancel] -command [subst {
destroy $search
}] -fg red]\
-sticky news
grid ^ -in $search
grid\
[radiobutton $options.up -text [mc Up]\
-variable textSearchWindowDialog::search(direction)\
-value -backwards]\
[checkbutton $options.case -text [mc Case-sensitive]\
-variable ::textSearchWindowDialog::search(case)\
-onvalue ""\
-offvalue -nocase]\
-sticky w
grid\
[radiobutton $options.down -text [mc Down]\
-variable textSearchWindowDialog::search(direction)\
-value -forwards]\
[checkbutton $options.regexp -text [mc Regexp]\
-variable ::textSearchWindowDialog::search(regexp)\
-onvalue -regexp\
-offvalue ""]\
-sticky w
apply {{search args} {
foreach button $args {raise $search.$button}
}} $search oldtext newtext find mark replace replacefind replaceall
apply {{options args} {
foreach button $args {raise $options.$button}
}} $options case regexp up down
}
namespace import textSearchWindowDialog::textSearchWindow
proc textSearchWindowDialog::unBackslash text {
string map [list \\\\ \\ \\n \n \\t \t] $text
}
proc textSearchWindowDialog::basicSearchString win {
# create command out of array search(...)
variable search
lappend result $win search\
-count ::textSearchWindowDialog::search(count)\
$search(direction) {*}$search(case) {*}$search(regexp)
}
proc textSearchWindowDialog::find win {
# return index of $search(text)
variable search
if {$search(direction) eq "-backwards"} then {
if {[$win tag ranges sel] eq ""} then {
set from insert
} else {
set from sel.first
}
set to 1.0
} else {
if {[$win tag ranges sel] eq ""} then {
set from insert
} else {
set from sel.last
}
set to end
}
set text $search(text)
if {$search(regexp) eq ""} then {
set text [unBackslash $text]
}
set search(count) ""
set idx [{*}[basicSearchString $win] -- $text $from $to]
set search(found) [llength $search(count)]
if {$idx ne ""} then {
list $idx [$win index $idx+$search(count)chars]
}
}
proc textSearchWindowDialog::findSelect win {
# find and select
set indices [find $win]
$win tag remove sel 1.0 end
if {$indices ne ""} then {
$win tag add sel {*}$indices
$win mark set insert sel.first
$win see insert
focus -force $win
}
}
proc textSearchWindowDialog::replace win {
# replace found text
if {[$win tag ranges sel] ne ""} then {
variable search
set replaceText $search(replace)
lassign [$win tag ranges sel] from to
set selection [$win get $from $to]
set selText [$win get $from $to]
if {$search(regexp) eq ""} then {
if {$selText eq [unBackslash $search(text)]} then {
$win configure -autoseparators no
$win edit separator
$win delete $from $to
$win insert $from [unBackslash $replaceText]
$win edit separator
$win configure -autoseparators yes
focus -force $win
}
} else {
if {[regexp $search(text) $selText match] &&
$match eq $selText} then {
regsub {*}$search(case)\
$search(text) $selText $search(replace) replace
$win configure -autoseparators no
$win edit separator
$win delete $from $to
$win insert $from [unBackslash $replace]
$win edit separator
$win configure -autoseparators yes
focus -force $win
}
}
}
}
proc textSearchWindowDialog::findAll {win {from 1.0} {to end}} {
# return bounds of all occurrences
variable search
set dir $search(direction)
set search(direction) -forwards
lappend result
set text [unBackslash $search(text)]
set search(count) ""
set indices [{*}[basicSearchString $win] -all -- $text $from $to]
set search(found) [llength $search(count)]
set search(direction) $dir
if {$indices ne ""} then {
foreach idx $indices len $search(count) {
lappend result $idx [$win index $idx+${len}chars]
}
set result
}
}
proc textSearchWindowDialog::markAll win {
# hilight all occurrences
variable search
$win tag remove yellow 1.0 end
if {$search(text) ne ""} then {
$win tag configure yellow -background yellow
$win tag lower yellow sel
foreach {from to} [findAll $win] {
$win tag add yellow $from $to
}
}
}
proc textSearchWindowDialog::replaceAll win {
# replace all occurrences in certain range
variable search
set replaceText $search(replace)
if {$search(regexp) eq ""} then {
set replaceText [unBackslash $replaceText]
}
if {[$win tag ranges sel] ne ""} then {
# range: sel
lassign [$win tag ranges sel] fromRange toRange
} elseif {$search(direction) eq "-backwards"} then {
# range: start to insert
lassign "1.0 insert" fromRange toRange
} else {
# range: insert to end
lassign "insert end" fromRange toRange
}
$win configure -autoseparators no
$win edit separator
foreach {to from} [lreverse [findAll $win $fromRange $toRange]] {
if {$search(regexp) eq ""} then {
$win delete $from $to
$win insert $from $replaceText
} else {
regsub {*}$search(case)\
$search(text) [$win get $from $to] $search(replace)\
replace
$win delete $from $to
$win insert $from [unBackslash $replace]
}
}
$win see insert
$win edit separator
$win configure -autoseparators yes
}
JOB - 2016-07-28 13:13:35
Thank your for this contribution. Here is some code to test the package:
if {1} {
package require Tk
package require textSearchWindow
set t [toplevel .textsearch]
wm withdraw .
set txt [text $t.txt]
pack $txt -fill both -expand true
# generate some arbitrary data...
foreach p [lsort -dictionary [info procs]] {
set body [string map {"\n" " "} [info body $p]]
$txt insert end $body
}
textSearchWindowDialog::textSearchWindow $txt
}Just discovered: when resizing the search dialog, there is a graphical problem with the display of the buttons (grid manager ?). Search & replace works quite nicely.