############################################################################ # filename: AutoComplete.py # # Author: JerryKing # # Ver: 1.10 # # Date: 2005/11/17 3:25 Pm # # E-mail: JerryKingKing_WZ@yahoo.com.cn # # Purpose: Add a basic function to IDLE, hope everybody enjoy it. # # Modification: # # 2005/11/19 4:32 Pm Pick up all attributes, add handler # # to new exception. now it works fine in Shell. # ############################################################################ from string import ascii_letters, digits import re, sys, os, sets, inspect, Tkinter import __main__ class AutoComplete: def __init__(self, win): self.win = win self.WordChars = ascii_letters + digits + '_' self.NameSpace = {} self.active = False self.record = "" self.count = 0 self.win.bind("<1>", self.OnClick) self.win.bind("", self.OnChar) def WordStartPosition(self, pos): row, column = pos.split('.') i = int(column) - 1 while i >= 0: if self.win.get(row+'.'+`i`, row+'.'+`i+1`) in self.WordChars: i -= 1 else: break result = row + '.' + `i+1` return result def WordEndPosition(self, pos): row, column = pos.split('.') end = self.win.index("insert lineend").split('.')[1] i = int(column) + 1 while i <= int(end): if self.win.get(row+'.'+`i-1`, row+'.'+`i`) in self.WordChars: i += 1 else: break result = row + '.' + `i-1` return result def GetWord(self, whole=None): pos = self.win.index("insert") start = self.WordStartPosition(pos) if whole: end = self.WordEndPosition(pos) return self.win.get(start, end) else: return self.win.get(start, "insert") def ImportDocument(self): r = re.compile(r"^\s*from\s+.*$|^\s*import\s+.*$", re.M) result = r.findall(self.win.get("0.0", "end")) result = [s.strip() for s in result] for line in result: if line.startswith("from"): try: exec(line) in self.NameSpace except: pass elif line.startswith('import'): try: exec(line) in self.NameSpace except: pass def Evaluate(self, word): try: obj = eval(word, self.NameSpace) return obj except: self.ImportDocument() try: obj = eval(word, self.NameSpace) return obj except: return None def GetWordObject(self, word=None, whole=None): if not word: word = self.GetWord(whole) try: return self.Evaluate(word) except: return None def FilterInput(self): FilteredAttr = [] for attr in self.attrs: if re.match('^' + self.record + "[A-Z0-9]*", attr.upper()): FilteredAttr.append(attr) if FilteredAttr: self.listbox.delete('0', "end") for attr in FilteredAttr: self.listbox.insert("end", attr) self.listbox.select_set('0') else: self.DestroyGUI() # deal with KeyBoard input event which bind with Text widget. def OnChar(self, event): key = event.char if key == '.': self.NameSpace.update(__main__.__dict__) obj = self.GetWordObject(whole=False) if obj != None: self.MakeGUI(obj) self.active = True return elif re.match("[_a-zA-Z0-9]", key): if self.active == True: self.record += key.upper() self.count = len(self.record) self.win.insert("insert", key) self.FilterInput() elif event.keysym == "BackSpace": if self.active == True and self.count > -1: row, column = self.win.index("insert").split('.') column = int(column) - 1 self.win.delete(row + '.' + `column`, "insert") self.record = self.record[:-1] self.count -= 1 if self.count == -1: self.DestroyGUI() return self.FilterInput() else: if self.active == True: row, column = self.win.index("insert").split('.') column = int(column) - 1 self.win.delete(row + '.' + `column`, "insert") self.DestroyGUI() return "break" else: return elif event.keysym == "Return": if self.active == True: length = len(self.record) row, column = self.win.index("insert").split('.') column = int(column) - length self.win.delete(row + '.' + `column`, "insert") self.win.insert("insert", self.listbox.get("active")) self.DestroyGUI() return elif event.keysym in ("Up", "Down", "Shift_R", "Shift_L", "Control_L", "Control_R", "Alt_L", "Alt_R", "parenleft", "parenright"): return else: if self.active == True: self.DestroyGUI() return def OnClick(self, event): if self.active == True: self.DestroyGUI() def OnDClick(self, event): length = len(self.record) row, column = self.win.index("insert").split('.') column = int(column) - length self.win.delete(row + '.' + `column`, "insert") self.win.insert("insert", self.listbox.get("active")) self.DestroyGUI() def MakeGUI(self, obj): x, y, cx, cy = self.win.bbox("insert") x = x + self.win.winfo_rootx() + 2 y = y + cy + self.win.winfo_rooty() tl = Tkinter.Toplevel(self.win.master) tl.wm_overrideredirect(1) tl.wm_geometry("+%d+%d" % (x, y)) form = Tkinter.Frame(tl, highlightthickness=1, highlightcolor="darkgreen") form.pack(fill="both", expand="yes") sb = Tkinter.Scrollbar(form, highlightthickness=0) sb.pack(side="right", fill="y") self.listbox = Tkinter.Listbox(form, highlightthickness=0, relief="flat", yscrollcommand=sb.set, height=6) self.attrs = dir(obj) for attr in self.attrs: self.listbox.insert("end", attr) self.listbox.pack(side="left", fill="both") self.listbox.select_set('0') sb.config(command=self.listbox.yview) self.listbox.focus() self.listbox.bind("", self.OnChar) self.listbox.bind("", self.OnDClick) def DestroyGUI(self): self.listbox.master.master.destroy() self.record = "" self.count = 0 self.active = False # Test if __name__ == "__main__": t = Tkinter.Text(font=("courier", 10)) t.pack() t.insert("end", "import Tkinter\nt = Tkinter") a = AutoComplete(t) t.mainloop()