Commit | Line | Data |
---|---|---|
920dae64 AT |
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 | import PyShell | |
18 | from WindowList import ListedToplevel | |
19 | from TreeWidget import TreeNode, TreeItem, ScrolledCanvas | |
20 | from 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() |