diff CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/python3.8/idlelib/searchengine.py @ 69:33d812a61356

planemo upload commit 2e9511a184a1ca667c7be0c6321a36dc4e3d116d
author jpayne
date Tue, 18 Mar 2025 17:55:14 -0400
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/python3.8/idlelib/searchengine.py	Tue Mar 18 17:55:14 2025 -0400
@@ -0,0 +1,237 @@
+'''Define SearchEngine for search dialogs.'''
+import re
+
+from tkinter import StringVar, BooleanVar, TclError
+import tkinter.messagebox as tkMessageBox
+
+def get(root):
+    '''Return the singleton SearchEngine instance for the process.
+
+    The single SearchEngine saves settings between dialog instances.
+    If there is not a SearchEngine already, make one.
+    '''
+    if not hasattr(root, "_searchengine"):
+        root._searchengine = SearchEngine(root)
+        # This creates a cycle that persists until root is deleted.
+    return root._searchengine
+
+
+class SearchEngine:
+    """Handles searching a text widget for Find, Replace, and Grep."""
+
+    def __init__(self, root):
+        '''Initialize Variables that save search state.
+
+        The dialogs bind these to the UI elements present in the dialogs.
+        '''
+        self.root = root  # need for report_error()
+        self.patvar = StringVar(root, '')   # search pattern
+        self.revar = BooleanVar(root, False)   # regular expression?
+        self.casevar = BooleanVar(root, False)   # match case?
+        self.wordvar = BooleanVar(root, False)   # match whole word?
+        self.wrapvar = BooleanVar(root, True)   # wrap around buffer?
+        self.backvar = BooleanVar(root, False)   # search backwards?
+
+    # Access methods
+
+    def getpat(self):
+        return self.patvar.get()
+
+    def setpat(self, pat):
+        self.patvar.set(pat)
+
+    def isre(self):
+        return self.revar.get()
+
+    def iscase(self):
+        return self.casevar.get()
+
+    def isword(self):
+        return self.wordvar.get()
+
+    def iswrap(self):
+        return self.wrapvar.get()
+
+    def isback(self):
+        return self.backvar.get()
+
+    # Higher level access methods
+
+    def setcookedpat(self, pat):
+        "Set pattern after escaping if re."
+        # called only in search.py: 66
+        if self.isre():
+            pat = re.escape(pat)
+        self.setpat(pat)
+
+    def getcookedpat(self):
+        pat = self.getpat()
+        if not self.isre():  # if True, see setcookedpat
+            pat = re.escape(pat)
+        if self.isword():
+            pat = r"\b%s\b" % pat
+        return pat
+
+    def getprog(self):
+        "Return compiled cooked search pattern."
+        pat = self.getpat()
+        if not pat:
+            self.report_error(pat, "Empty regular expression")
+            return None
+        pat = self.getcookedpat()
+        flags = 0
+        if not self.iscase():
+            flags = flags | re.IGNORECASE
+        try:
+            prog = re.compile(pat, flags)
+        except re.error as what:
+            args = what.args
+            msg = args[0]
+            col = args[1] if len(args) >= 2 else -1
+            self.report_error(pat, msg, col)
+            return None
+        return prog
+
+    def report_error(self, pat, msg, col=-1):
+        # Derived class could override this with something fancier
+        msg = "Error: " + str(msg)
+        if pat:
+            msg = msg + "\nPattern: " + str(pat)
+        if col >= 0:
+            msg = msg + "\nOffset: " + str(col)
+        tkMessageBox.showerror("Regular expression error",
+                               msg, master=self.root)
+
+    def search_text(self, text, prog=None, ok=0):
+        '''Return (lineno, matchobj) or None for forward/backward search.
+
+        This function calls the right function with the right arguments.
+        It directly return the result of that call.
+
+        Text is a text widget. Prog is a precompiled pattern.
+        The ok parameter is a bit complicated as it has two effects.
+
+        If there is a selection, the search begin at either end,
+        depending on the direction setting and ok, with ok meaning that
+        the search starts with the selection. Otherwise, search begins
+        at the insert mark.
+
+        To aid progress, the search functions do not return an empty
+        match at the starting position unless ok is True.
+        '''
+
+        if not prog:
+            prog = self.getprog()
+            if not prog:
+                return None # Compilation failed -- stop
+        wrap = self.wrapvar.get()
+        first, last = get_selection(text)
+        if self.isback():
+            if ok:
+                start = last
+            else:
+                start = first
+            line, col = get_line_col(start)
+            res = self.search_backward(text, prog, line, col, wrap, ok)
+        else:
+            if ok:
+                start = first
+            else:
+                start = last
+            line, col = get_line_col(start)
+            res = self.search_forward(text, prog, line, col, wrap, ok)
+        return res
+
+    def search_forward(self, text, prog, line, col, wrap, ok=0):
+        wrapped = 0
+        startline = line
+        chars = text.get("%d.0" % line, "%d.0" % (line+1))
+        while chars:
+            m = prog.search(chars[:-1], col)
+            if m:
+                if ok or m.end() > col:
+                    return line, m
+            line = line + 1
+            if wrapped and line > startline:
+                break
+            col = 0
+            ok = 1
+            chars = text.get("%d.0" % line, "%d.0" % (line+1))
+            if not chars and wrap:
+                wrapped = 1
+                wrap = 0
+                line = 1
+                chars = text.get("1.0", "2.0")
+        return None
+
+    def search_backward(self, text, prog, line, col, wrap, ok=0):
+        wrapped = 0
+        startline = line
+        chars = text.get("%d.0" % line, "%d.0" % (line+1))
+        while 1:
+            m = search_reverse(prog, chars[:-1], col)
+            if m:
+                if ok or m.start() < col:
+                    return line, m
+            line = line - 1
+            if wrapped and line < startline:
+                break
+            ok = 1
+            if line <= 0:
+                if not wrap:
+                    break
+                wrapped = 1
+                wrap = 0
+                pos = text.index("end-1c")
+                line, col = map(int, pos.split("."))
+            chars = text.get("%d.0" % line, "%d.0" % (line+1))
+            col = len(chars) - 1
+        return None
+
+
+def search_reverse(prog, chars, col):
+    '''Search backwards and return an re match object or None.
+
+    This is done by searching forwards until there is no match.
+    Prog: compiled re object with a search method returning a match.
+    Chars: line of text, without \\n.
+    Col: stop index for the search; the limit for match.end().
+    '''
+    m = prog.search(chars)
+    if not m:
+        return None
+    found = None
+    i, j = m.span()  # m.start(), m.end() == match slice indexes
+    while i < col and j <= col:
+        found = m
+        if i == j:
+            j = j+1
+        m = prog.search(chars, j)
+        if not m:
+            break
+        i, j = m.span()
+    return found
+
+def get_selection(text):
+    '''Return tuple of 'line.col' indexes from selection or insert mark.
+    '''
+    try:
+        first = text.index("sel.first")
+        last = text.index("sel.last")
+    except TclError:
+        first = last = None
+    if not first:
+        first = text.index("insert")
+    if not last:
+        last = first
+    return first, last
+
+def get_line_col(index):
+    '''Return (line, col) tuple of ints from 'line.col' string.'''
+    line, col = map(int, index.split(".")) # Fails on invalid index
+    return line, col
+
+
+if __name__ == "__main__":
+    from unittest import main
+    main('idlelib.idle_test.test_searchengine', verbosity=2)