ยปCore Development>Code coverage>Lib/idlelib/searchengine.py

Python code coverage for Lib/idlelib/searchengine.py

#countcontent
1n/a'''Define SearchEngine for search dialogs.'''
2n/aimport re
3n/a
4n/afrom tkinter import StringVar, BooleanVar, TclError
5n/aimport tkinter.messagebox as tkMessageBox
6n/a
7n/adef get(root):
8n/a '''Return the singleton SearchEngine instance for the process.
9n/a
10n/a The single SearchEngine saves settings between dialog instances.
11n/a If there is not a SearchEngine already, make one.
12n/a '''
13n/a if not hasattr(root, "_searchengine"):
14n/a root._searchengine = SearchEngine(root)
15n/a # This creates a cycle that persists until root is deleted.
16n/a return root._searchengine
17n/a
18n/a
19n/aclass SearchEngine:
20n/a """Handles searching a text widget for Find, Replace, and Grep."""
21n/a
22n/a def __init__(self, root):
23n/a '''Initialize Variables that save search state.
24n/a
25n/a The dialogs bind these to the UI elements present in the dialogs.
26n/a '''
27n/a self.root = root # need for report_error()
28n/a self.patvar = StringVar(root, '') # search pattern
29n/a self.revar = BooleanVar(root, False) # regular expression?
30n/a self.casevar = BooleanVar(root, False) # match case?
31n/a self.wordvar = BooleanVar(root, False) # match whole word?
32n/a self.wrapvar = BooleanVar(root, True) # wrap around buffer?
33n/a self.backvar = BooleanVar(root, False) # search backwards?
34n/a
35n/a # Access methods
36n/a
37n/a def getpat(self):
38n/a return self.patvar.get()
39n/a
40n/a def setpat(self, pat):
41n/a self.patvar.set(pat)
42n/a
43n/a def isre(self):
44n/a return self.revar.get()
45n/a
46n/a def iscase(self):
47n/a return self.casevar.get()
48n/a
49n/a def isword(self):
50n/a return self.wordvar.get()
51n/a
52n/a def iswrap(self):
53n/a return self.wrapvar.get()
54n/a
55n/a def isback(self):
56n/a return self.backvar.get()
57n/a
58n/a # Higher level access methods
59n/a
60n/a def setcookedpat(self, pat):
61n/a "Set pattern after escaping if re."
62n/a # called only in search.py: 66
63n/a if self.isre():
64n/a pat = re.escape(pat)
65n/a self.setpat(pat)
66n/a
67n/a def getcookedpat(self):
68n/a pat = self.getpat()
69n/a if not self.isre(): # if True, see setcookedpat
70n/a pat = re.escape(pat)
71n/a if self.isword():
72n/a pat = r"\b%s\b" % pat
73n/a return pat
74n/a
75n/a def getprog(self):
76n/a "Return compiled cooked search pattern."
77n/a pat = self.getpat()
78n/a if not pat:
79n/a self.report_error(pat, "Empty regular expression")
80n/a return None
81n/a pat = self.getcookedpat()
82n/a flags = 0
83n/a if not self.iscase():
84n/a flags = flags | re.IGNORECASE
85n/a try:
86n/a prog = re.compile(pat, flags)
87n/a except re.error as what:
88n/a args = what.args
89n/a msg = args[0]
90n/a col = args[1] if len(args) >= 2 else -1
91n/a self.report_error(pat, msg, col)
92n/a return None
93n/a return prog
94n/a
95n/a def report_error(self, pat, msg, col=-1):
96n/a # Derived class could override this with something fancier
97n/a msg = "Error: " + str(msg)
98n/a if pat:
99n/a msg = msg + "\nPattern: " + str(pat)
100n/a if col >= 0:
101n/a msg = msg + "\nOffset: " + str(col)
102n/a tkMessageBox.showerror("Regular expression error",
103n/a msg, master=self.root)
104n/a
105n/a def search_text(self, text, prog=None, ok=0):
106n/a '''Return (lineno, matchobj) or None for forward/backward search.
107n/a
108n/a This function calls the right function with the right arguments.
109n/a It directly return the result of that call.
110n/a
111n/a Text is a text widget. Prog is a precompiled pattern.
112n/a The ok parameter is a bit complicated as it has two effects.
113n/a
114n/a If there is a selection, the search begin at either end,
115n/a depending on the direction setting and ok, with ok meaning that
116n/a the search starts with the selection. Otherwise, search begins
117n/a at the insert mark.
118n/a
119n/a To aid progress, the search functions do not return an empty
120n/a match at the starting position unless ok is True.
121n/a '''
122n/a
123n/a if not prog:
124n/a prog = self.getprog()
125n/a if not prog:
126n/a return None # Compilation failed -- stop
127n/a wrap = self.wrapvar.get()
128n/a first, last = get_selection(text)
129n/a if self.isback():
130n/a if ok:
131n/a start = last
132n/a else:
133n/a start = first
134n/a line, col = get_line_col(start)
135n/a res = self.search_backward(text, prog, line, col, wrap, ok)
136n/a else:
137n/a if ok:
138n/a start = first
139n/a else:
140n/a start = last
141n/a line, col = get_line_col(start)
142n/a res = self.search_forward(text, prog, line, col, wrap, ok)
143n/a return res
144n/a
145n/a def search_forward(self, text, prog, line, col, wrap, ok=0):
146n/a wrapped = 0
147n/a startline = line
148n/a chars = text.get("%d.0" % line, "%d.0" % (line+1))
149n/a while chars:
150n/a m = prog.search(chars[:-1], col)
151n/a if m:
152n/a if ok or m.end() > col:
153n/a return line, m
154n/a line = line + 1
155n/a if wrapped and line > startline:
156n/a break
157n/a col = 0
158n/a ok = 1
159n/a chars = text.get("%d.0" % line, "%d.0" % (line+1))
160n/a if not chars and wrap:
161n/a wrapped = 1
162n/a wrap = 0
163n/a line = 1
164n/a chars = text.get("1.0", "2.0")
165n/a return None
166n/a
167n/a def search_backward(self, text, prog, line, col, wrap, ok=0):
168n/a wrapped = 0
169n/a startline = line
170n/a chars = text.get("%d.0" % line, "%d.0" % (line+1))
171n/a while 1:
172n/a m = search_reverse(prog, chars[:-1], col)
173n/a if m:
174n/a if ok or m.start() < col:
175n/a return line, m
176n/a line = line - 1
177n/a if wrapped and line < startline:
178n/a break
179n/a ok = 1
180n/a if line <= 0:
181n/a if not wrap:
182n/a break
183n/a wrapped = 1
184n/a wrap = 0
185n/a pos = text.index("end-1c")
186n/a line, col = map(int, pos.split("."))
187n/a chars = text.get("%d.0" % line, "%d.0" % (line+1))
188n/a col = len(chars) - 1
189n/a return None
190n/a
191n/a
192n/adef search_reverse(prog, chars, col):
193n/a '''Search backwards and return an re match object or None.
194n/a
195n/a This is done by searching forwards until there is no match.
196n/a Prog: compiled re object with a search method returning a match.
197n/a Chars: line of text, without \\n.
198n/a Col: stop index for the search; the limit for match.end().
199n/a '''
200n/a m = prog.search(chars)
201n/a if not m:
202n/a return None
203n/a found = None
204n/a i, j = m.span() # m.start(), m.end() == match slice indexes
205n/a while i < col and j <= col:
206n/a found = m
207n/a if i == j:
208n/a j = j+1
209n/a m = prog.search(chars, j)
210n/a if not m:
211n/a break
212n/a i, j = m.span()
213n/a return found
214n/a
215n/adef get_selection(text):
216n/a '''Return tuple of 'line.col' indexes from selection or insert mark.
217n/a '''
218n/a try:
219n/a first = text.index("sel.first")
220n/a last = text.index("sel.last")
221n/a except TclError:
222n/a first = last = None
223n/a if not first:
224n/a first = text.index("insert")
225n/a if not last:
226n/a last = first
227n/a return first, last
228n/a
229n/adef get_line_col(index):
230n/a '''Return (line, col) tuple of ints from 'line.col' string.'''
231n/a line, col = map(int, index.split(".")) # Fails on invalid index
232n/a return line, col
233n/a
234n/aif __name__ == "__main__":
235n/a import unittest
236n/a unittest.main('idlelib.idle_test.test_searchengine', verbosity=2, exit=False)