[2] | 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 |
|
---|
[391] | 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
|
---|
[2] | 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()
|
---|