1 """Class browser. 2 3 XXX TO DO: 4 5 - reparse when source changed (maybe just a button would be OK?) 6 (or recheck on window popup) 7 - add popup menu with more options (e.g. doc strings, base classes, imports) 8 - show function argument list? (have to do pattern matching on source) 9 - should the classes and methods lists also be in the module's menu bar? 10 - add base classes to class browser tree 11 """ 12 13 import os 14 import sys 15 import pyclbr 16 17 from idlelib import PyShell 18 from idlelib.WindowList import ListedToplevel 19 from idlelib.TreeWidget import TreeNode, TreeItem, ScrolledCanvas 20 from idlelib.configHandler import idleConf 21 22 class ClassBrowser: 23 24 def __init__(self, flist, name, path): 25 # XXX This API should change, if the file doesn't end in ".py" 26 # XXX the code here is bogus! 27 self.name = name 28 self.file = os.path.join(path[0], self.name + ".py") 29 self.init(flist) 30 31 def close(self, event=None): 32 self.top.destroy() 33 self.node.destroy() 34 35 def init(self, flist): 36 self.flist = flist 37 # reset pyclbr 38 pyclbr._modules.clear() 39 # create top 40 self.top = top = ListedToplevel(flist.root) 41 top.protocol("WM_DELETE_WINDOW", self.close) 42 top.bind("<Escape>", self.close) 43 self.settitle() 44 top.focus_set() 45 # create scrolled canvas 46 theme = idleConf.GetOption('main','Theme','name') 47 background = idleConf.GetHighlight(theme, 'normal')['background'] 48 sc = ScrolledCanvas(top, bg=background, highlightthickness=0, takefocus=1) 49 sc.frame.pack(expand=1, fill="both") 50 item = self.rootnode() 51 self.node = node = TreeNode(sc.canvas, None, item) 52 node.update() 53 node.expand() 54 55 def settitle(self): 56 self.top.wm_title("Class Browser - " + self.name) 57 self.top.wm_iconname("Class Browser") 58 59 def rootnode(self): 60 return ModuleBrowserTreeItem(self.file) 61 62 class ModuleBrowserTreeItem(TreeItem): 63 64 def __init__(self, file): 65 self.file = file 66 67 def GetText(self): 68 return os.path.basename(self.file) 69 70 def GetIconName(self): 71 return "python" 72 73 def GetSubList(self): 74 sublist = [] 75 for name in self.listclasses(): 76 item = ClassBrowserTreeItem(name, self.classes, self.file) 77 sublist.append(item) 78 return sublist 79 80 def OnDoubleClick(self): 81 if os.path.normcase(self.file[-3:]) != ".py": 82 return 83 if not os.path.exists(self.file): 84 return 85 PyShell.flist.open(self.file) 86 87 def IsExpandable(self): 88 return os.path.normcase(self.file[-3:]) == ".py" 89 90 def listclasses(self): 91 dir, file = os.path.split(self.file) 92 name, ext = os.path.splitext(file) 93 if os.path.normcase(ext) != ".py": 94 return [] 95 try: 96 dict = pyclbr.readmodule_ex(name, [dir] + sys.path) 97 except ImportError, msg: 98 return [] 99 items = [] 100 self.classes = {} 101 for key, cl in dict.items(): 102 if cl.module == name: 103 s = key 104 if hasattr(cl, 'super') and cl.super: 105 supers = [] 106 for sup in cl.super: 107 if type(sup) is type(''): 108 sname = sup 109 else: 110 sname = sup.name 111 if sup.module != cl.module: 112 sname = "%s.%s" % (sup.module, sname) 113 supers.append(sname) 114 s = s + "(%s)" % ", ".join(supers) 115 items.append((cl.lineno, s)) 116 self.classes[s] = cl 117 items.sort() 118 list = [] 119 for item, s in items: 120 list.append(s) 121 return list 122 123 class ClassBrowserTreeItem(TreeItem): 124 125 def __init__(self, name, classes, file): 126 self.name = name 127 self.classes = classes 128 self.file = file 129 try: 130 self.cl = self.classes[self.name] 131 except (IndexError, KeyError): 132 self.cl = None 133 self.isfunction = isinstance(self.cl, pyclbr.Function) 134 135 def GetText(self): 136 if self.isfunction: 137 return "def " + self.name + "(...)" 138 else: 139 return "class " + self.name 140 141 def GetIconName(self): 142 if self.isfunction: 143 return "python" 144 else: 145 return "folder" 146 147 def IsExpandable(self): 148 if self.cl: 149 try: 150 return not not self.cl.methods 151 except AttributeError: 152 return False 153 154 def GetSubList(self): 155 if not self.cl: 156 return [] 157 sublist = [] 158 for name in self.listmethods(): 159 item = MethodBrowserTreeItem(name, self.cl, self.file) 160 sublist.append(item) 161 return sublist 162 163 def OnDoubleClick(self): 164 if not os.path.exists(self.file): 165 return 166 edit = PyShell.flist.open(self.file) 167 if hasattr(self.cl, 'lineno'): 168 lineno = self.cl.lineno 169 edit.gotoline(lineno) 170 171 def listmethods(self): 172 if not self.cl: 173 return [] 174 items = [] 175 for name, lineno in self.cl.methods.items(): 176 items.append((lineno, name)) 177 items.sort() 178 list = [] 179 for item, name in items: 180 list.append(name) 181 return list 182 183 class MethodBrowserTreeItem(TreeItem): 184 185 def __init__(self, name, cl, file): 186 self.name = name 187 self.cl = cl 188 self.file = file 189 190 def GetText(self): 191 return "def " + self.name + "(...)" 192 193 def GetIconName(self): 194 return "python" # XXX 195 196 def IsExpandable(self): 197 return 0 198 199 def OnDoubleClick(self): 200 if not os.path.exists(self.file): 201 return 202 edit = PyShell.flist.open(self.file) 203 edit.gotoline(self.cl.methods[self.name]) 204 205 def main(): 206 try: 207 file = __file__ 208 except NameError: 209 file = sys.argv[0] 210 if sys.argv[1:]: 211 file = sys.argv[1] 212 else: 213 file = sys.argv[0] 214 dir, file = os.path.split(file) 215 name = os.path.splitext(file)[0] 216 ClassBrowser(PyShell.flist, name, [dir]) 217 if sys.stdin is sys.__stdin__: 218 mainloop() 219 220 if __name__ == "__main__": 221 main() 222