jpayne@69: '''Complete the current word before the cursor with words in the editor. jpayne@69: jpayne@69: Each menu selection or shortcut key selection replaces the word with a jpayne@69: different word with the same prefix. The search for matches begins jpayne@69: before the target and moves toward the top of the editor. It then starts jpayne@69: after the cursor and moves down. It then returns to the original word and jpayne@69: the cycle starts again. jpayne@69: jpayne@69: Changing the current text line or leaving the cursor in a different jpayne@69: place before requesting the next selection causes AutoExpand to reset jpayne@69: its state. jpayne@69: jpayne@69: There is only one instance of Autoexpand. jpayne@69: ''' jpayne@69: import re jpayne@69: import string jpayne@69: jpayne@69: jpayne@69: class AutoExpand: jpayne@69: wordchars = string.ascii_letters + string.digits + "_" jpayne@69: jpayne@69: def __init__(self, editwin): jpayne@69: self.text = editwin.text jpayne@69: self.bell = self.text.bell jpayne@69: self.state = None jpayne@69: jpayne@69: def expand_word_event(self, event): jpayne@69: "Replace the current word with the next expansion." jpayne@69: curinsert = self.text.index("insert") jpayne@69: curline = self.text.get("insert linestart", "insert lineend") jpayne@69: if not self.state: jpayne@69: words = self.getwords() jpayne@69: index = 0 jpayne@69: else: jpayne@69: words, index, insert, line = self.state jpayne@69: if insert != curinsert or line != curline: jpayne@69: words = self.getwords() jpayne@69: index = 0 jpayne@69: if not words: jpayne@69: self.bell() jpayne@69: return "break" jpayne@69: word = self.getprevword() jpayne@69: self.text.delete("insert - %d chars" % len(word), "insert") jpayne@69: newword = words[index] jpayne@69: index = (index + 1) % len(words) jpayne@69: if index == 0: jpayne@69: self.bell() # Warn we cycled around jpayne@69: self.text.insert("insert", newword) jpayne@69: curinsert = self.text.index("insert") jpayne@69: curline = self.text.get("insert linestart", "insert lineend") jpayne@69: self.state = words, index, curinsert, curline jpayne@69: return "break" jpayne@69: jpayne@69: def getwords(self): jpayne@69: "Return a list of words that match the prefix before the cursor." jpayne@69: word = self.getprevword() jpayne@69: if not word: jpayne@69: return [] jpayne@69: before = self.text.get("1.0", "insert wordstart") jpayne@69: wbefore = re.findall(r"\b" + word + r"\w+\b", before) jpayne@69: del before jpayne@69: after = self.text.get("insert wordend", "end") jpayne@69: wafter = re.findall(r"\b" + word + r"\w+\b", after) jpayne@69: del after jpayne@69: if not wbefore and not wafter: jpayne@69: return [] jpayne@69: words = [] jpayne@69: dict = {} jpayne@69: # search backwards through words before jpayne@69: wbefore.reverse() jpayne@69: for w in wbefore: jpayne@69: if dict.get(w): jpayne@69: continue jpayne@69: words.append(w) jpayne@69: dict[w] = w jpayne@69: # search onwards through words after jpayne@69: for w in wafter: jpayne@69: if dict.get(w): jpayne@69: continue jpayne@69: words.append(w) jpayne@69: dict[w] = w jpayne@69: words.append(word) jpayne@69: return words jpayne@69: jpayne@69: def getprevword(self): jpayne@69: "Return the word prefix before the cursor." jpayne@69: line = self.text.get("insert linestart", "insert") jpayne@69: i = len(line) jpayne@69: while i > 0 and line[i-1] in self.wordchars: jpayne@69: i = i-1 jpayne@69: return line[i:] jpayne@69: jpayne@69: jpayne@69: if __name__ == '__main__': jpayne@69: from unittest import main jpayne@69: main('idlelib.idle_test.test_autoexpand', verbosity=2)