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