blob: 5be65efb9bc0ead58fe684a9c5d3c5dbfac1a6bb [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
16
Florent Xiclunad630c042010-04-02 07:24:52 +000017from idlelib import PyShell
18from idlelib.WindowList import ListedToplevel
19from idlelib.TreeWidget import TreeNode, TreeItem, ScrolledCanvas
20from idlelib.configHandler import idleConf
David Scherer7aced172000-08-15 01:13:23 +000021
Terry Jan Reedycf834762014-10-17 01:31:29 -040022file_open = None # Method...Item and Class...Item use this.
23# Normally PyShell.flist.open, but there is no PyShell.flist for htest.
24
David Scherer7aced172000-08-15 01:13:23 +000025class ClassBrowser:
26
Terry Jan Reedy62012fc2014-05-24 18:48:03 -040027 def __init__(self, flist, name, path, _htest=False):
David Scherer7aced172000-08-15 01:13:23 +000028 # XXX This API should change, if the file doesn't end in ".py"
29 # XXX the code here is bogus!
Terry Jan Reedy62012fc2014-05-24 18:48:03 -040030 """
31 _htest - bool, change box when location running htest.
32 """
Terry Jan Reedycf834762014-10-17 01:31:29 -040033 global file_open
34 if not _htest:
35 file_open = PyShell.flist.open
David Scherer7aced172000-08-15 01:13:23 +000036 self.name = name
37 self.file = os.path.join(path[0], self.name + ".py")
Terry Jan Reedy62012fc2014-05-24 18:48:03 -040038 self._htest = _htest
David Scherer7aced172000-08-15 01:13:23 +000039 self.init(flist)
40
41 def close(self, event=None):
42 self.top.destroy()
43 self.node.destroy()
44
45 def init(self, flist):
46 self.flist = flist
47 # reset pyclbr
48 pyclbr._modules.clear()
49 # create top
50 self.top = top = ListedToplevel(flist.root)
51 top.protocol("WM_DELETE_WINDOW", self.close)
52 top.bind("<Escape>", self.close)
Terry Jan Reedy62012fc2014-05-24 18:48:03 -040053 if self._htest: # place dialog below parent if running htest
54 top.geometry("+%d+%d" %
55 (flist.root.winfo_rootx(), flist.root.winfo_rooty() + 200))
David Scherer7aced172000-08-15 01:13:23 +000056 self.settitle()
57 top.focus_set()
58 # create scrolled canvas
Kurt B. Kaiser73360a32004-03-08 18:15:31 +000059 theme = idleConf.GetOption('main','Theme','name')
60 background = idleConf.GetHighlight(theme, 'normal')['background']
61 sc = ScrolledCanvas(top, bg=background, highlightthickness=0, takefocus=1)
David Scherer7aced172000-08-15 01:13:23 +000062 sc.frame.pack(expand=1, fill="both")
63 item = self.rootnode()
64 self.node = node = TreeNode(sc.canvas, None, item)
65 node.update()
66 node.expand()
67
68 def settitle(self):
69 self.top.wm_title("Class Browser - " + self.name)
70 self.top.wm_iconname("Class Browser")
71
72 def rootnode(self):
73 return ModuleBrowserTreeItem(self.file)
74
75class ModuleBrowserTreeItem(TreeItem):
76
77 def __init__(self, file):
78 self.file = file
79
80 def GetText(self):
81 return os.path.basename(self.file)
82
83 def GetIconName(self):
84 return "python"
85
86 def GetSubList(self):
87 sublist = []
88 for name in self.listclasses():
89 item = ClassBrowserTreeItem(name, self.classes, self.file)
90 sublist.append(item)
91 return sublist
92
93 def OnDoubleClick(self):
94 if os.path.normcase(self.file[-3:]) != ".py":
95 return
96 if not os.path.exists(self.file):
97 return
98 PyShell.flist.open(self.file)
99
100 def IsExpandable(self):
101 return os.path.normcase(self.file[-3:]) == ".py"
Kurt B. Kaiserd6c4c9e2001-07-12 23:54:20 +0000102
David Scherer7aced172000-08-15 01:13:23 +0000103 def listclasses(self):
104 dir, file = os.path.split(self.file)
105 name, ext = os.path.splitext(file)
106 if os.path.normcase(ext) != ".py":
107 return []
108 try:
109 dict = pyclbr.readmodule_ex(name, [dir] + sys.path)
Terry Jan Reedy9bc50562014-07-01 18:52:31 -0400110 except ImportError:
David Scherer7aced172000-08-15 01:13:23 +0000111 return []
112 items = []
113 self.classes = {}
114 for key, cl in dict.items():
115 if cl.module == name:
116 s = key
Raymond Hettinger65500512003-01-19 02:37:41 +0000117 if hasattr(cl, 'super') and cl.super:
David Scherer7aced172000-08-15 01:13:23 +0000118 supers = []
119 for sup in cl.super:
120 if type(sup) is type(''):
121 sname = sup
122 else:
123 sname = sup.name
124 if sup.module != cl.module:
125 sname = "%s.%s" % (sup.module, sname)
126 supers.append(sname)
Kurt B. Kaisera2876442002-09-15 22:09:16 +0000127 s = s + "(%s)" % ", ".join(supers)
David Scherer7aced172000-08-15 01:13:23 +0000128 items.append((cl.lineno, s))
129 self.classes[s] = cl
130 items.sort()
131 list = []
132 for item, s in items:
133 list.append(s)
134 return list
135
136class ClassBrowserTreeItem(TreeItem):
137
138 def __init__(self, name, classes, file):
139 self.name = name
140 self.classes = classes
141 self.file = file
142 try:
143 self.cl = self.classes[self.name]
144 except (IndexError, KeyError):
145 self.cl = None
146 self.isfunction = isinstance(self.cl, pyclbr.Function)
147
148 def GetText(self):
149 if self.isfunction:
150 return "def " + self.name + "(...)"
151 else:
152 return "class " + self.name
153
154 def GetIconName(self):
155 if self.isfunction:
156 return "python"
157 else:
158 return "folder"
159
160 def IsExpandable(self):
161 if self.cl:
Kurt B. Kaiser0b743442003-01-20 04:49:37 +0000162 try:
163 return not not self.cl.methods
164 except AttributeError:
165 return False
David Scherer7aced172000-08-15 01:13:23 +0000166
167 def GetSubList(self):
168 if not self.cl:
169 return []
170 sublist = []
171 for name in self.listmethods():
172 item = MethodBrowserTreeItem(name, self.cl, self.file)
173 sublist.append(item)
174 return sublist
175
176 def OnDoubleClick(self):
177 if not os.path.exists(self.file):
178 return
Terry Jan Reedycf834762014-10-17 01:31:29 -0400179 edit = file_open(self.file)
David Scherer7aced172000-08-15 01:13:23 +0000180 if hasattr(self.cl, 'lineno'):
181 lineno = self.cl.lineno
182 edit.gotoline(lineno)
183
184 def listmethods(self):
185 if not self.cl:
186 return []
187 items = []
188 for name, lineno in self.cl.methods.items():
189 items.append((lineno, name))
190 items.sort()
191 list = []
192 for item, name in items:
193 list.append(name)
194 return list
195
196class MethodBrowserTreeItem(TreeItem):
197
198 def __init__(self, name, cl, file):
199 self.name = name
200 self.cl = cl
201 self.file = file
202
203 def GetText(self):
204 return "def " + self.name + "(...)"
205
206 def GetIconName(self):
207 return "python" # XXX
208
209 def IsExpandable(self):
210 return 0
211
212 def OnDoubleClick(self):
213 if not os.path.exists(self.file):
214 return
Terry Jan Reedycf834762014-10-17 01:31:29 -0400215 edit = file_open(self.file)
David Scherer7aced172000-08-15 01:13:23 +0000216 edit.gotoline(self.cl.methods[self.name])
217
Terry Jan Reedy62012fc2014-05-24 18:48:03 -0400218def _class_browser(parent): #Wrapper for htest
David Scherer7aced172000-08-15 01:13:23 +0000219 try:
220 file = __file__
221 except NameError:
222 file = sys.argv[0]
223 if sys.argv[1:]:
224 file = sys.argv[1]
225 else:
226 file = sys.argv[0]
227 dir, file = os.path.split(file)
228 name = os.path.splitext(file)[0]
Terry Jan Reedy62012fc2014-05-24 18:48:03 -0400229 flist = PyShell.PyShellFileList(parent)
Terry Jan Reedycf834762014-10-17 01:31:29 -0400230 global file_open
231 file_open = flist.open
Terry Jan Reedy62012fc2014-05-24 18:48:03 -0400232 ClassBrowser(flist, name, [dir], _htest=True)
David Scherer7aced172000-08-15 01:13:23 +0000233
234if __name__ == "__main__":
Terry Jan Reedy62012fc2014-05-24 18:48:03 -0400235 from idlelib.idle_test.htest import run
236 run(_class_browser)