blob: c85340f3cb71cd4ab3d9d352450c4db5ecdf1983 [file] [log] [blame]
David Scherer7aced172000-08-15 01:13:23 +00001"""Class browser.
2
3XXX 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
13import os
14import sys
David Scherer7aced172000-08-15 01:13:23 +000015import pyclbr
Terry Jan Reedy62012fc2014-05-24 18:48:03 -040016import re
David Scherer7aced172000-08-15 01:13:23 +000017
Florent Xiclunad630c042010-04-02 07:24:52 +000018from idlelib import PyShell
19from idlelib.WindowList import ListedToplevel
20from idlelib.TreeWidget import TreeNode, TreeItem, ScrolledCanvas
21from idlelib.configHandler import idleConf
David Scherer7aced172000-08-15 01:13:23 +000022
23class ClassBrowser:
24
Terry Jan Reedy62012fc2014-05-24 18:48:03 -040025 def __init__(self, flist, name, path, _htest=False):
David Scherer7aced172000-08-15 01:13:23 +000026 # XXX This API should change, if the file doesn't end in ".py"
27 # XXX the code here is bogus!
Terry Jan Reedy62012fc2014-05-24 18:48:03 -040028 """
29 _htest - bool, change box when location running htest.
30 """
David Scherer7aced172000-08-15 01:13:23 +000031 self.name = name
32 self.file = os.path.join(path[0], self.name + ".py")
Terry Jan Reedy62012fc2014-05-24 18:48:03 -040033 self._htest = _htest
David Scherer7aced172000-08-15 01:13:23 +000034 self.init(flist)
35
36 def close(self, event=None):
37 self.top.destroy()
38 self.node.destroy()
39
40 def init(self, flist):
41 self.flist = flist
42 # reset pyclbr
43 pyclbr._modules.clear()
44 # create top
45 self.top = top = ListedToplevel(flist.root)
46 top.protocol("WM_DELETE_WINDOW", self.close)
47 top.bind("<Escape>", self.close)
Terry Jan Reedy62012fc2014-05-24 18:48:03 -040048 if self._htest: # place dialog below parent if running htest
49 top.geometry("+%d+%d" %
50 (flist.root.winfo_rootx(), flist.root.winfo_rooty() + 200))
David Scherer7aced172000-08-15 01:13:23 +000051 self.settitle()
52 top.focus_set()
53 # create scrolled canvas
Kurt B. Kaiser73360a32004-03-08 18:15:31 +000054 theme = idleConf.GetOption('main','Theme','name')
55 background = idleConf.GetHighlight(theme, 'normal')['background']
56 sc = ScrolledCanvas(top, bg=background, highlightthickness=0, takefocus=1)
David Scherer7aced172000-08-15 01:13:23 +000057 sc.frame.pack(expand=1, fill="both")
58 item = self.rootnode()
59 self.node = node = TreeNode(sc.canvas, None, item)
60 node.update()
61 node.expand()
62
63 def settitle(self):
64 self.top.wm_title("Class Browser - " + self.name)
65 self.top.wm_iconname("Class Browser")
66
67 def rootnode(self):
68 return ModuleBrowserTreeItem(self.file)
69
70class ModuleBrowserTreeItem(TreeItem):
71
72 def __init__(self, file):
73 self.file = file
74
75 def GetText(self):
76 return os.path.basename(self.file)
77
78 def GetIconName(self):
79 return "python"
80
81 def GetSubList(self):
82 sublist = []
83 for name in self.listclasses():
84 item = ClassBrowserTreeItem(name, self.classes, self.file)
85 sublist.append(item)
86 return sublist
87
88 def OnDoubleClick(self):
89 if os.path.normcase(self.file[-3:]) != ".py":
90 return
91 if not os.path.exists(self.file):
92 return
93 PyShell.flist.open(self.file)
94
95 def IsExpandable(self):
96 return os.path.normcase(self.file[-3:]) == ".py"
Kurt B. Kaiserd6c4c9e2001-07-12 23:54:20 +000097
David Scherer7aced172000-08-15 01:13:23 +000098 def listclasses(self):
99 dir, file = os.path.split(self.file)
100 name, ext = os.path.splitext(file)
101 if os.path.normcase(ext) != ".py":
102 return []
103 try:
104 dict = pyclbr.readmodule_ex(name, [dir] + sys.path)
105 except ImportError, msg:
106 return []
107 items = []
108 self.classes = {}
109 for key, cl in dict.items():
110 if cl.module == name:
111 s = key
Raymond Hettinger65500512003-01-19 02:37:41 +0000112 if hasattr(cl, 'super') and cl.super:
David Scherer7aced172000-08-15 01:13:23 +0000113 supers = []
114 for sup in cl.super:
115 if type(sup) is type(''):
116 sname = sup
117 else:
118 sname = sup.name
119 if sup.module != cl.module:
120 sname = "%s.%s" % (sup.module, sname)
121 supers.append(sname)
Kurt B. Kaisera2876442002-09-15 22:09:16 +0000122 s = s + "(%s)" % ", ".join(supers)
David Scherer7aced172000-08-15 01:13:23 +0000123 items.append((cl.lineno, s))
124 self.classes[s] = cl
125 items.sort()
126 list = []
127 for item, s in items:
128 list.append(s)
129 return list
130
131class ClassBrowserTreeItem(TreeItem):
132
133 def __init__(self, name, classes, file):
134 self.name = name
135 self.classes = classes
136 self.file = file
137 try:
138 self.cl = self.classes[self.name]
139 except (IndexError, KeyError):
140 self.cl = None
141 self.isfunction = isinstance(self.cl, pyclbr.Function)
142
143 def GetText(self):
144 if self.isfunction:
145 return "def " + self.name + "(...)"
146 else:
147 return "class " + self.name
148
149 def GetIconName(self):
150 if self.isfunction:
151 return "python"
152 else:
153 return "folder"
154
155 def IsExpandable(self):
156 if self.cl:
Kurt B. Kaiser0b743442003-01-20 04:49:37 +0000157 try:
158 return not not self.cl.methods
159 except AttributeError:
160 return False
David Scherer7aced172000-08-15 01:13:23 +0000161
162 def GetSubList(self):
163 if not self.cl:
164 return []
165 sublist = []
166 for name in self.listmethods():
167 item = MethodBrowserTreeItem(name, self.cl, self.file)
168 sublist.append(item)
169 return sublist
170
171 def OnDoubleClick(self):
172 if not os.path.exists(self.file):
173 return
174 edit = PyShell.flist.open(self.file)
175 if hasattr(self.cl, 'lineno'):
176 lineno = self.cl.lineno
177 edit.gotoline(lineno)
178
179 def listmethods(self):
180 if not self.cl:
181 return []
182 items = []
183 for name, lineno in self.cl.methods.items():
184 items.append((lineno, name))
185 items.sort()
186 list = []
187 for item, name in items:
188 list.append(name)
189 return list
190
191class MethodBrowserTreeItem(TreeItem):
192
193 def __init__(self, name, cl, file):
194 self.name = name
195 self.cl = cl
196 self.file = file
197
198 def GetText(self):
199 return "def " + self.name + "(...)"
200
201 def GetIconName(self):
202 return "python" # XXX
203
204 def IsExpandable(self):
205 return 0
206
207 def OnDoubleClick(self):
208 if not os.path.exists(self.file):
209 return
210 edit = PyShell.flist.open(self.file)
211 edit.gotoline(self.cl.methods[self.name])
212
Terry Jan Reedy62012fc2014-05-24 18:48:03 -0400213def _class_browser(parent): #Wrapper for htest
David Scherer7aced172000-08-15 01:13:23 +0000214 try:
215 file = __file__
216 except NameError:
217 file = sys.argv[0]
218 if sys.argv[1:]:
219 file = sys.argv[1]
220 else:
221 file = sys.argv[0]
222 dir, file = os.path.split(file)
223 name = os.path.splitext(file)[0]
Terry Jan Reedy62012fc2014-05-24 18:48:03 -0400224 flist = PyShell.PyShellFileList(parent)
225 ClassBrowser(flist, name, [dir], _htest=True)
David Scherer7aced172000-08-15 01:13:23 +0000226
227if __name__ == "__main__":
Terry Jan Reedy62012fc2014-05-24 18:48:03 -0400228 from idlelib.idle_test.htest import run
229 run(_class_browser)