6 if not hasattr(root
, "_searchengine"):
7 root
._searchengine
= SearchEngine(root
)
8 # XXX This will never garbage-collect -- who cares
9 return root
._searchengine
13 def __init__(self
, root
):
15 # State shared by search, replace, and grep;
16 # the search dialogs bind these to UI elements.
17 self
.patvar
= StringVar(root
) # search pattern
18 self
.revar
= BooleanVar(root
) # regular expression?
19 self
.casevar
= BooleanVar(root
) # match case?
20 self
.wordvar
= BooleanVar(root
) # match whole word?
21 self
.wrapvar
= BooleanVar(root
) # wrap around buffer?
22 self
.wrapvar
.set(1) # (on by default)
23 self
.backvar
= BooleanVar(root
) # search backwards?
28 return self
.patvar
.get()
30 def setpat(self
, pat
):
34 return self
.revar
.get()
37 return self
.casevar
.get()
40 return self
.wordvar
.get()
43 return self
.wrapvar
.get()
46 return self
.backvar
.get()
48 # Higher level access methods
50 def getcookedpat(self
):
61 self
.report_error(pat
, "Empty regular expression")
63 pat
= self
.getcookedpat()
66 flags
= flags | re
.IGNORECASE
68 prog
= re
.compile(pat
, flags
)
69 except re
.error
, what
:
75 self
.report_error(pat
, msg
, col
)
79 def report_error(self
, pat
, msg
, col
=-1):
80 # Derived class could overrid this with something fancier
81 msg
= "Error: " + str(msg
)
83 msg
= msg
+ "\np\Pattern: " + str(pat
)
85 msg
= msg
+ "\nOffset: " + str(col
)
86 tkMessageBox
.showerror("Regular expression error",
87 msg
, master
=self
.root
)
89 def setcookedpat(self
, pat
):
94 def search_text(self
, text
, prog
=None, ok
=0):
95 """Search a text widget for the pattern.
97 If prog is given, it should be the precompiled pattern.
98 Return a tuple (lineno, matchobj); None if not found.
100 This obeys the wrap and direction (back) settings.
102 The search starts at the selection (if there is one) or
103 at the insert mark (otherwise). If the search is forward,
104 it starts at the right of the selection; for a backward
105 search, it starts at the left end. An empty match exactly
106 at either end of the selection (or at the insert mark if
107 there is no selection) is ignored unless the ok flag is true
108 -- this is done to guarantee progress.
110 If the search is allowed to wrap around, it will return the
111 original selection if (and only if) it is the only match.
115 prog
= self
.getprog()
117 return None # Compilation failed -- stop
118 wrap
= self
.wrapvar
.get()
119 first
, last
= get_selection(text
)
125 line
, col
= get_line_col(start
)
126 res
= self
.search_backward(text
, prog
, line
, col
, wrap
, ok
)
132 line
, col
= get_line_col(start
)
133 res
= self
.search_forward(text
, prog
, line
, col
, wrap
, ok
)
136 def search_forward(self
, text
, prog
, line
, col
, wrap
, ok
=0):
139 chars
= text
.get("%d.0" % line
, "%d.0" % (line
+1))
141 m
= prog
.search(chars
[:-1], col
)
143 if ok
or m
.end() > col
:
146 if wrapped
and line
> startline
:
150 chars
= text
.get("%d.0" % line
, "%d.0" % (line
+1))
151 if not chars
and wrap
:
155 chars
= text
.get("1.0", "2.0")
158 def search_backward(self
, text
, prog
, line
, col
, wrap
, ok
=0):
161 chars
= text
.get("%d.0" % line
, "%d.0" % (line
+1))
163 m
= search_reverse(prog
, chars
[:-1], col
)
165 if ok
or m
.start() < col
:
168 if wrapped
and line
< startline
:
176 pos
= text
.index("end-1c")
177 line
, col
= map(int, pos
.split("."))
178 chars
= text
.get("%d.0" % line
, "%d.0" % (line
+1))
182 # Helper to search backwards in a string.
183 # (Optimized for the case where the pattern isn't found.)
185 def search_reverse(prog
, chars
, col
):
186 m
= prog
.search(chars
)
191 while i
< col
and j
<= col
:
195 m
= prog
.search(chars
, j
)
201 # Helper to get selection end points, defaulting to insert mark.
202 # Return a tuple of indices ("line.col" strings).
204 def get_selection(text
):
206 first
= text
.index("sel.first")
207 last
= text
.index("sel.last")
211 first
= text
.index("insert")
216 # Helper to parse a text index into a (line, col) tuple.
218 def get_line_col(index
):
219 line
, col
= map(int, index
.split(".")) # Fails on invalid index