ClassBrowser.py revision 73360a3e61274ffcc4c9fc3d09746bd6603e92a5
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 pyclbr 167aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 177aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Schererimport PyShell 187aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Schererfrom WindowList import ListedToplevel 197aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Schererfrom TreeWidget import TreeNode, TreeItem, ScrolledCanvas 2073360a3e61274ffcc4c9fc3d09746bd6603e92a5Kurt B. Kaiserfrom configHandler import idleConf 217aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 227aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Schererclass ClassBrowser: 237aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 247aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def __init__(self, flist, name, path): 257aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer # XXX This API should change, if the file doesn't end in ".py" 267aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer # XXX the code here is bogus! 277aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.name = name 287aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.file = os.path.join(path[0], self.name + ".py") 297aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.init(flist) 307aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 317aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def close(self, event=None): 327aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.top.destroy() 337aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.node.destroy() 347aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 357aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def init(self, flist): 367aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.flist = flist 377aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer # reset pyclbr 387aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer pyclbr._modules.clear() 397aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer # create top 407aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.top = top = ListedToplevel(flist.root) 417aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer top.protocol("WM_DELETE_WINDOW", self.close) 427aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer top.bind("<Escape>", self.close) 437aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.settitle() 447aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer top.focus_set() 457aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer # create scrolled canvas 4673360a3e61274ffcc4c9fc3d09746bd6603e92a5Kurt B. Kaiser theme = idleConf.GetOption('main','Theme','name') 4773360a3e61274ffcc4c9fc3d09746bd6603e92a5Kurt B. Kaiser background = idleConf.GetHighlight(theme, 'normal')['background'] 4873360a3e61274ffcc4c9fc3d09746bd6603e92a5Kurt B. Kaiser sc = ScrolledCanvas(top, bg=background, highlightthickness=0, takefocus=1) 497aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer sc.frame.pack(expand=1, fill="both") 507aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer item = self.rootnode() 517aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.node = node = TreeNode(sc.canvas, None, item) 527aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer node.update() 537aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer node.expand() 547aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 557aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def settitle(self): 567aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.top.wm_title("Class Browser - " + self.name) 577aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.top.wm_iconname("Class Browser") 587aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 597aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def rootnode(self): 607aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return ModuleBrowserTreeItem(self.file) 617aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 627aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Schererclass ModuleBrowserTreeItem(TreeItem): 637aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 647aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def __init__(self, file): 657aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.file = file 667aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 677aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def GetText(self): 687aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return os.path.basename(self.file) 697aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 707aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def GetIconName(self): 717aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return "python" 727aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 737aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def GetSubList(self): 747aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer sublist = [] 757aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer for name in self.listclasses(): 767aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer item = ClassBrowserTreeItem(name, self.classes, self.file) 777aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer sublist.append(item) 787aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return sublist 797aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 807aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def OnDoubleClick(self): 817aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer if os.path.normcase(self.file[-3:]) != ".py": 827aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return 837aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer if not os.path.exists(self.file): 847aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return 857aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer PyShell.flist.open(self.file) 867aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 877aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def IsExpandable(self): 887aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return os.path.normcase(self.file[-3:]) == ".py" 89d6c4c9e846660cb501491ec0b98f5086ef3fcc7fKurt B. Kaiser 907aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def listclasses(self): 917aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer dir, file = os.path.split(self.file) 927aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer name, ext = os.path.splitext(file) 937aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer if os.path.normcase(ext) != ".py": 947aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return [] 957aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer try: 967aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer dict = pyclbr.readmodule_ex(name, [dir] + sys.path) 977aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer except ImportError, msg: 987aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return [] 997aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer items = [] 1007aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.classes = {} 1017aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer for key, cl in dict.items(): 1027aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer if cl.module == name: 1037aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer s = key 1046550051691d604c728ed56e4acf90dc6535981f9Raymond Hettinger if hasattr(cl, 'super') and cl.super: 1057aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer supers = [] 1067aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer for sup in cl.super: 1077aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer if type(sup) is type(''): 1087aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer sname = sup 1097aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer else: 1107aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer sname = sup.name 1117aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer if sup.module != cl.module: 1127aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer sname = "%s.%s" % (sup.module, sname) 1137aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer supers.append(sname) 114a2876442047ac41abce63b5d0e987e9d807c694fKurt B. Kaiser s = s + "(%s)" % ", ".join(supers) 1157aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer items.append((cl.lineno, s)) 1167aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.classes[s] = cl 1177aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer items.sort() 1187aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer list = [] 1197aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer for item, s in items: 1207aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer list.append(s) 1217aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return list 1227aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 1237aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Schererclass ClassBrowserTreeItem(TreeItem): 1247aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 1257aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def __init__(self, name, classes, file): 1267aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.name = name 1277aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.classes = classes 1287aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.file = file 1297aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer try: 1307aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.cl = self.classes[self.name] 1317aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer except (IndexError, KeyError): 1327aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.cl = None 1337aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.isfunction = isinstance(self.cl, pyclbr.Function) 1347aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 1357aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def GetText(self): 1367aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer if self.isfunction: 1377aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return "def " + self.name + "(...)" 1387aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer else: 1397aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return "class " + self.name 1407aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 1417aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def GetIconName(self): 1427aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer if self.isfunction: 1437aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return "python" 1447aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer else: 1457aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return "folder" 1467aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 1477aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def IsExpandable(self): 1487aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer if self.cl: 1490b743441a60640824360bdff15780fc5d40489d6Kurt B. Kaiser try: 1500b743441a60640824360bdff15780fc5d40489d6Kurt B. Kaiser return not not self.cl.methods 1510b743441a60640824360bdff15780fc5d40489d6Kurt B. Kaiser except AttributeError: 1520b743441a60640824360bdff15780fc5d40489d6Kurt B. Kaiser return False 1537aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 1547aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def GetSubList(self): 1557aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer if not self.cl: 1567aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return [] 1577aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer sublist = [] 1587aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer for name in self.listmethods(): 1597aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer item = MethodBrowserTreeItem(name, self.cl, self.file) 1607aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer sublist.append(item) 1617aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return sublist 1627aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 1637aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def OnDoubleClick(self): 1647aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer if not os.path.exists(self.file): 1657aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return 1667aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer edit = PyShell.flist.open(self.file) 1677aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer if hasattr(self.cl, 'lineno'): 1687aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer lineno = self.cl.lineno 1697aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer edit.gotoline(lineno) 1707aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 1717aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def listmethods(self): 1727aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer if not self.cl: 1737aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return [] 1747aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer items = [] 1757aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer for name, lineno in self.cl.methods.items(): 1767aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer items.append((lineno, name)) 1777aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer items.sort() 1787aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer list = [] 1797aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer for item, name in items: 1807aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer list.append(name) 1817aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return list 1827aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 1837aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Schererclass MethodBrowserTreeItem(TreeItem): 1847aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 1857aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def __init__(self, name, cl, file): 1867aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.name = name 1877aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.cl = cl 1887aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer self.file = file 1897aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 1907aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def GetText(self): 1917aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return "def " + self.name + "(...)" 1927aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 1937aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def GetIconName(self): 1947aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return "python" # XXX 1957aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 1967aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def IsExpandable(self): 1977aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return 0 1987aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 1997aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer def OnDoubleClick(self): 2007aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer if not os.path.exists(self.file): 2017aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer return 2027aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer edit = PyShell.flist.open(self.file) 2037aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer edit.gotoline(self.cl.methods[self.name]) 2047aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 2057aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Schererdef main(): 2067aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer try: 2077aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer file = __file__ 2087aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer except NameError: 2097aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer file = sys.argv[0] 2107aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer if sys.argv[1:]: 2117aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer file = sys.argv[1] 2127aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer else: 2137aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer file = sys.argv[0] 2147aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer dir, file = os.path.split(file) 2157aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer name = os.path.splitext(file)[0] 2167aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer ClassBrowser(PyShell.flist, name, [dir]) 2177aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer if sys.stdin is sys.__stdin__: 2187aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer mainloop() 2197aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer 2207aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Schererif __name__ == "__main__": 2217aced17437a6b05bc4b0b5ff93aa6a5d3a374d68David Scherer main() 222