17db96d56Sopenharmony_ci'''Complete the current word before the cursor with words in the editor.
27db96d56Sopenharmony_ci
37db96d56Sopenharmony_ciEach menu selection or shortcut key selection replaces the word with a
47db96d56Sopenharmony_cidifferent word with the same prefix. The search for matches begins
57db96d56Sopenharmony_cibefore the target and moves toward the top of the editor. It then starts
67db96d56Sopenharmony_ciafter the cursor and moves down. It then returns to the original word and
77db96d56Sopenharmony_cithe cycle starts again.
87db96d56Sopenharmony_ci
97db96d56Sopenharmony_ciChanging the current text line or leaving the cursor in a different
107db96d56Sopenharmony_ciplace before requesting the next selection causes AutoExpand to reset
117db96d56Sopenharmony_ciits state.
127db96d56Sopenharmony_ci
137db96d56Sopenharmony_ciThere is only one instance of Autoexpand.
147db96d56Sopenharmony_ci'''
157db96d56Sopenharmony_ciimport re
167db96d56Sopenharmony_ciimport string
177db96d56Sopenharmony_ci
187db96d56Sopenharmony_ci
197db96d56Sopenharmony_ciclass AutoExpand:
207db96d56Sopenharmony_ci    wordchars = string.ascii_letters + string.digits + "_"
217db96d56Sopenharmony_ci
227db96d56Sopenharmony_ci    def __init__(self, editwin):
237db96d56Sopenharmony_ci        self.text = editwin.text
247db96d56Sopenharmony_ci        self.bell = self.text.bell
257db96d56Sopenharmony_ci        self.state = None
267db96d56Sopenharmony_ci
277db96d56Sopenharmony_ci    def expand_word_event(self, event):
287db96d56Sopenharmony_ci        "Replace the current word with the next expansion."
297db96d56Sopenharmony_ci        curinsert = self.text.index("insert")
307db96d56Sopenharmony_ci        curline = self.text.get("insert linestart", "insert lineend")
317db96d56Sopenharmony_ci        if not self.state:
327db96d56Sopenharmony_ci            words = self.getwords()
337db96d56Sopenharmony_ci            index = 0
347db96d56Sopenharmony_ci        else:
357db96d56Sopenharmony_ci            words, index, insert, line = self.state
367db96d56Sopenharmony_ci            if insert != curinsert or line != curline:
377db96d56Sopenharmony_ci                words = self.getwords()
387db96d56Sopenharmony_ci                index = 0
397db96d56Sopenharmony_ci        if not words:
407db96d56Sopenharmony_ci            self.bell()
417db96d56Sopenharmony_ci            return "break"
427db96d56Sopenharmony_ci        word = self.getprevword()
437db96d56Sopenharmony_ci        self.text.delete("insert - %d chars" % len(word), "insert")
447db96d56Sopenharmony_ci        newword = words[index]
457db96d56Sopenharmony_ci        index = (index + 1) % len(words)
467db96d56Sopenharmony_ci        if index == 0:
477db96d56Sopenharmony_ci            self.bell()            # Warn we cycled around
487db96d56Sopenharmony_ci        self.text.insert("insert", newword)
497db96d56Sopenharmony_ci        curinsert = self.text.index("insert")
507db96d56Sopenharmony_ci        curline = self.text.get("insert linestart", "insert lineend")
517db96d56Sopenharmony_ci        self.state = words, index, curinsert, curline
527db96d56Sopenharmony_ci        return "break"
537db96d56Sopenharmony_ci
547db96d56Sopenharmony_ci    def getwords(self):
557db96d56Sopenharmony_ci        "Return a list of words that match the prefix before the cursor."
567db96d56Sopenharmony_ci        word = self.getprevword()
577db96d56Sopenharmony_ci        if not word:
587db96d56Sopenharmony_ci            return []
597db96d56Sopenharmony_ci        before = self.text.get("1.0", "insert wordstart")
607db96d56Sopenharmony_ci        wbefore = re.findall(r"\b" + word + r"\w+\b", before)
617db96d56Sopenharmony_ci        del before
627db96d56Sopenharmony_ci        after = self.text.get("insert wordend", "end")
637db96d56Sopenharmony_ci        wafter = re.findall(r"\b" + word + r"\w+\b", after)
647db96d56Sopenharmony_ci        del after
657db96d56Sopenharmony_ci        if not wbefore and not wafter:
667db96d56Sopenharmony_ci            return []
677db96d56Sopenharmony_ci        words = []
687db96d56Sopenharmony_ci        dict = {}
697db96d56Sopenharmony_ci        # search backwards through words before
707db96d56Sopenharmony_ci        wbefore.reverse()
717db96d56Sopenharmony_ci        for w in wbefore:
727db96d56Sopenharmony_ci            if dict.get(w):
737db96d56Sopenharmony_ci                continue
747db96d56Sopenharmony_ci            words.append(w)
757db96d56Sopenharmony_ci            dict[w] = w
767db96d56Sopenharmony_ci        # search onwards through words after
777db96d56Sopenharmony_ci        for w in wafter:
787db96d56Sopenharmony_ci            if dict.get(w):
797db96d56Sopenharmony_ci                continue
807db96d56Sopenharmony_ci            words.append(w)
817db96d56Sopenharmony_ci            dict[w] = w
827db96d56Sopenharmony_ci        words.append(word)
837db96d56Sopenharmony_ci        return words
847db96d56Sopenharmony_ci
857db96d56Sopenharmony_ci    def getprevword(self):
867db96d56Sopenharmony_ci        "Return the word prefix before the cursor."
877db96d56Sopenharmony_ci        line = self.text.get("insert linestart", "insert")
887db96d56Sopenharmony_ci        i = len(line)
897db96d56Sopenharmony_ci        while i > 0 and line[i-1] in self.wordchars:
907db96d56Sopenharmony_ci            i = i-1
917db96d56Sopenharmony_ci        return line[i:]
927db96d56Sopenharmony_ci
937db96d56Sopenharmony_ci
947db96d56Sopenharmony_ciif __name__ == '__main__':
957db96d56Sopenharmony_ci    from unittest import main
967db96d56Sopenharmony_ci    main('idlelib.idle_test.test_autoexpand', verbosity=2)
97