ClassBrowser.py revision 7aced17437a6b05bc4b0b5ff93aa6a5d3a374d68
17aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer"""Class browser. 27aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 37aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David SchererXXX TO DO: 47aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 57aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer- reparse when source changed (maybe just a button would be OK?) 67aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer (or recheck on window popup) 77aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer- add popup menu with more options (e.g. doc strings, base classes, imports) 87aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer- show function argument list? (have to do pattern matching on source) 97aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer- should the classes and methods lists also be in the module's menu bar? 107aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer- add base classes to class browser tree 117aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer""" 127aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 137aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Schererimport os 147aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Schererimport sys 157aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Schererimport string 167aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Schererimport pyclbr 177aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 187aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer# XXX Patch pyclbr with dummies if it's vintage Python 1.5.2: 197aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Schererif not hasattr(pyclbr, "readmodule_ex"): 207aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer pyclbr.readmodule_ex = pyclbr.readmodule 217aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Schererif not hasattr(pyclbr, "Function"): 227aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer class Function(pyclbr.Class): 237aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer pass 247aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer pyclbr.Function = Function 257aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 267aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Schererimport PyShell 277aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Schererfrom WindowList import ListedToplevel 287aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Schererfrom TreeWidget import TreeNode, TreeItem, ScrolledCanvas 297aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 307aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Schererclass ClassBrowser: 317aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 327aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def __init__(self, flist, name, path): 337aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer # XXX This API should change, if the file doesn't end in ".py" 347aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer # XXX the code here is bogus! 357aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.name = name 367aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.file = os.path.join(path[0], self.name + ".py") 377aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.init(flist) 387aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 397aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def close(self, event=None): 407aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.top.destroy() 417aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.node.destroy() 427aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 437aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def init(self, flist): 447aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.flist = flist 457aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer # reset pyclbr 467aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer pyclbr._modules.clear() 477aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer # create top 487aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.top = top = ListedToplevel(flist.root) 497aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer top.protocol("WM_DELETE_WINDOW", self.close) 507aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer top.bind("<Escape>", self.close) 517aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.settitle() 527aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer top.focus_set() 537aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer # create scrolled canvas 547aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer sc = ScrolledCanvas(top, bg="white", highlightthickness=0, takefocus=1) 557aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer sc.frame.pack(expand=1, fill="both") 567aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer item = self.rootnode() 577aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.node = node = TreeNode(sc.canvas, None, item) 587aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer node.update() 597aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer node.expand() 607aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 617aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def settitle(self): 627aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.top.wm_title("Class Browser - " + self.name) 637aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.top.wm_iconname("Class Browser") 647aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 657aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def rootnode(self): 667aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return ModuleBrowserTreeItem(self.file) 677aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 687aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Schererclass ModuleBrowserTreeItem(TreeItem): 697aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 707aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def __init__(self, file): 717aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.file = file 727aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 737aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def GetText(self): 747aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return os.path.basename(self.file) 757aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 767aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def GetIconName(self): 777aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return "python" 787aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 797aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def GetSubList(self): 807aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer sublist = [] 817aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer for name in self.listclasses(): 827aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer item = ClassBrowserTreeItem(name, self.classes, self.file) 837aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer sublist.append(item) 847aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return sublist 857aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 867aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def OnDoubleClick(self): 877aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer if os.path.normcase(self.file[-3:]) != ".py": 887aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return 897aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer if not os.path.exists(self.file): 907aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return 917aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer PyShell.flist.open(self.file) 927aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 937aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def IsExpandable(self): 947aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return os.path.normcase(self.file[-3:]) == ".py" 957aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 967aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def listclasses(self): 977aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer dir, file = os.path.split(self.file) 987aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer name, ext = os.path.splitext(file) 997aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer if os.path.normcase(ext) != ".py": 1007aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return [] 1017aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer try: 1027aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer dict = pyclbr.readmodule_ex(name, [dir] + sys.path) 1037aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer except ImportError, msg: 1047aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return [] 1057aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer items = [] 1067aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.classes = {} 1077aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer for key, cl in dict.items(): 1087aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer if cl.module == name: 1097aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer s = key 1107aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer if cl.super: 1117aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer supers = [] 1127aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer for sup in cl.super: 1137aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer if type(sup) is type(''): 1147aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer sname = sup 1157aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer else: 1167aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer sname = sup.name 1177aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer if sup.module != cl.module: 1187aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer sname = "%s.%s" % (sup.module, sname) 1197aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer supers.append(sname) 1207aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer s = s + "(%s)" % string.join(supers, ", ") 1217aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer items.append((cl.lineno, s)) 1227aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.classes[s] = cl 1237aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer items.sort() 1247aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer list = [] 1257aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer for item, s in items: 1267aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer list.append(s) 1277aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return list 1287aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 1297aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Schererclass ClassBrowserTreeItem(TreeItem): 1307aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 1317aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def __init__(self, name, classes, file): 1327aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.name = name 1337aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.classes = classes 1347aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.file = file 1357aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer try: 1367aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.cl = self.classes[self.name] 1377aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer except (IndexError, KeyError): 1387aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.cl = None 1397aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.isfunction = isinstance(self.cl, pyclbr.Function) 1407aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 1417aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def GetText(self): 1427aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer if self.isfunction: 1437aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return "def " + self.name + "(...)" 1447aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer else: 1457aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return "class " + self.name 1467aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 1477aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def GetIconName(self): 1487aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer if self.isfunction: 1497aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return "python" 1507aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer else: 1517aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return "folder" 1527aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 1537aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def IsExpandable(self): 1547aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer if self.cl: 1557aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return not not self.cl.methods 1567aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 1577aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def GetSubList(self): 1587aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer if not self.cl: 1597aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return [] 1607aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer sublist = [] 1617aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer for name in self.listmethods(): 1627aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer item = MethodBrowserTreeItem(name, self.cl, self.file) 1637aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer sublist.append(item) 1647aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return sublist 1657aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 1667aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def OnDoubleClick(self): 1677aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer if not os.path.exists(self.file): 1687aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return 1697aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer edit = PyShell.flist.open(self.file) 1707aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer if hasattr(self.cl, 'lineno'): 1717aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer lineno = self.cl.lineno 1727aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer edit.gotoline(lineno) 1737aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 1747aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def listmethods(self): 1757aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer if not self.cl: 1767aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return [] 1777aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer items = [] 1787aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer for name, lineno in self.cl.methods.items(): 1797aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer items.append((lineno, name)) 1807aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer items.sort() 1817aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer list = [] 1827aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer for item, name in items: 1837aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer list.append(name) 1847aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return list 1857aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 1867aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Schererclass MethodBrowserTreeItem(TreeItem): 1877aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 1887aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def __init__(self, name, cl, file): 1897aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.name = name 1907aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.cl = cl 1917aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.file = file 1927aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 1937aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def GetText(self): 1947aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return "def " + self.name + "(...)" 1957aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 1967aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def GetIconName(self): 1977aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return "python" # XXX 1987aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 1997aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def IsExpandable(self): 2007aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return 0 2017aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 2027aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def OnDoubleClick(self): 2037aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer if not os.path.exists(self.file): 2047aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return 2057aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer edit = PyShell.flist.open(self.file) 2067aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer edit.gotoline(self.cl.methods[self.name]) 2077aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 2087aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Schererdef main(): 2097aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer try: 2107aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer file = __file__ 2117aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer except NameError: 2127aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer file = sys.argv[0] 2137aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer if sys.argv[1:]: 2147aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer file = sys.argv[1] 2157aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer else: 2167aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer file = sys.argv[0] 2177aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer dir, file = os.path.split(file) 2187aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer name = os.path.splitext(file)[0] 2197aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer ClassBrowser(PyShell.flist, name, [dir]) 2207aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer if sys.stdin is sys.__stdin__: 2217aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer mainloop() 2227aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 2237aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Schererif __name__ == "__main__": 2247aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer main() 225