|
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() |