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