1919from idlelib .tree import TreeNode , TreeItem , ScrolledCanvas
2020from idlelib .windows import ListedToplevel
2121
22+
2223file_open = None # Method...Item and Class...Item use this.
2324# Normally pyshell.flist.open, but there is no pyshell.flist for htest.
2425
26+
27+ def transform_children (child_dict , modname = None ):
28+ """Transform a child dictionary to an ordered sequence of objects.
29+
30+ The dictionary maps names to pyclbr information objects.
31+ Filter out imported objects.
32+ Augment class names with bases.
33+ Sort objects by line number.
34+
35+ The current tree only calls this once per child_dic as it saves
36+ TreeItems once created. A future tree and tests might violate this,
37+ so a check prevents multiple in-place augmentations.
38+ """
39+ obs = [] # Use list since values should already be sorted.
40+ for key , obj in child_dict .items ():
41+ if modname is None or obj .module == modname :
42+ if hasattr (obj , 'super' ) and obj .super and obj .name == key :
43+ # If obj.name != key, it has already been suffixed.
44+ supers = []
45+ for sup in obj .super :
46+ if type (sup ) is type ('' ):
47+ sname = sup
48+ else :
49+ sname = sup .name
50+ if sup .module != obj .module :
51+ sname = f'{ sup .module } .{ sname } '
52+ supers .append (sname )
53+ obj .name += '({})' .format (', ' .join (supers ))
54+ obs .append (obj )
55+ return sorted (obs , key = lambda o : o .lineno )
56+
57+
2558class ClassBrowser :
2659 """Browse module classes and functions in IDLE.
2760 """
61+ # This class is the base class for pathbrowser.PathBrowser.
62+ # Init and close are inherited, other methods are overriden.
2863
29- def __init__ (self , flist , name , path , _htest = False ):
64+ def __init__ (self , flist , name , path , _htest = False , _utest = False ):
3065 # XXX This API should change, if the file doesn't end in ".py"
3166 # XXX the code here is bogus!
3267 """Create a window for browsing a module's structure.
@@ -47,11 +82,12 @@ def __init__(self, flist, name, path, _htest=False):
4782 the tree and subsequently in the children.
4883 """
4984 global file_open
50- if not _htest :
85+ if not ( _htest or _utest ) :
5186 file_open = pyshell .flist .open
5287 self .name = name
5388 self .file = os .path .join (path [0 ], self .name + ".py" )
5489 self ._htest = _htest
90+ self ._utest = _utest
5591 self .init (flist )
5692
5793 def close (self , event = None ):
@@ -80,8 +116,9 @@ def init(self, flist):
80116 sc .frame .pack (expand = 1 , fill = "both" )
81117 item = self .rootnode ()
82118 self .node = node = TreeNode (sc .canvas , None , item )
83- node .update ()
84- node .expand ()
119+ if not self ._utest :
120+ node .update ()
121+ node .expand ()
85122
86123 def settitle (self ):
87124 "Set the window title."
@@ -92,6 +129,7 @@ def rootnode(self):
92129 "Return a ModuleBrowserTreeItem as the root of the tree."
93130 return ModuleBrowserTreeItem (self .file )
94131
132+
95133class ModuleBrowserTreeItem (TreeItem ):
96134 """Browser tree for Python module.
97135
@@ -115,106 +153,53 @@ def GetIconName(self):
115153 return "python"
116154
117155 def GetSubList (self ):
118- """Return the list of ClassBrowserTreeItem items.
119-
120- Each item returned from listclasses is the first level of
121- classes/functions within the module.
122- """
123- sublist = []
124- for name in self .listclasses ():
125- item = ClassBrowserTreeItem (name , self .classes , self .file )
126- sublist .append (item )
127- return sublist
156+ "Return ChildBrowserTreeItems for children."
157+ return [ChildBrowserTreeItem (obj ) for obj in self .listchildren ()]
128158
129159 def OnDoubleClick (self ):
130160 "Open a module in an editor window when double clicked."
131161 if os .path .normcase (self .file [- 3 :]) != ".py" :
132162 return
133163 if not os .path .exists (self .file ):
134164 return
135- pyshell . flist . open (self .file )
165+ file_open (self .file )
136166
137167 def IsExpandable (self ):
138168 "Return True if Python (.py) file."
139169 return os .path .normcase (self .file [- 3 :]) == ".py"
140170
141- def listclasses (self ):
142- """Return list of classes and functions in the module.
143-
144- The dictionary output from pyclbr is re-written as a
145- list of tuples in the form (lineno, name) and
146- then sorted so that the classes and functions are
147- processed in line number order. The returned list only
148- contains the name and not the line number. An instance
149- variable self.classes contains the pyclbr dictionary values,
150- which are instances of Class and Function.
151- """
171+ def listchildren (self ):
172+ "Return sequenced classes and functions in the module."
152173 dir , file = os .path .split (self .file )
153174 name , ext = os .path .splitext (file )
154175 if os .path .normcase (ext ) != ".py" :
155176 return []
156177 try :
157- dict = pyclbr .readmodule_ex (name , [dir ] + sys .path )
178+ tree = pyclbr .readmodule_ex (name , [dir ] + sys .path )
158179 except ImportError :
159180 return []
160- items = []
161- self .classes = {}
162- for key , cl in dict .items ():
163- if cl .module == name :
164- s = key
165- if hasattr (cl , 'super' ) and cl .super :
166- supers = []
167- for sup in cl .super :
168- if type (sup ) is type ('' ):
169- sname = sup
170- else :
171- sname = sup .name
172- if sup .module != cl .module :
173- sname = "%s.%s" % (sup .module , sname )
174- supers .append (sname )
175- s = s + "(%s)" % ", " .join (supers )
176- items .append ((cl .lineno , s ))
177- self .classes [s ] = cl
178- items .sort ()
179- list = []
180- for item , s in items :
181- list .append (s )
182- return list
183-
184- class ClassBrowserTreeItem (TreeItem ):
185- """Browser tree for classes within a module.
181+ return transform_children (tree , name )
186182
187- Uses TreeItem as the basis for the structure of the tree.
188- """
189183
190- def __init__ ( self , name , classes , file ):
191- """Create a TreeItem for the class/function .
184+ class ChildBrowserTreeItem ( TreeItem ):
185+ """Browser tree for child nodes within the module .
192186
193- Args:
194- name: Name of the class/function.
195- classes: Dictonary of Class/Function instances from pyclbr.
196- file: Full path and module name.
187+ Uses TreeItem as the basis for the structure of the tree.
188+ """
197189
198- Instance variables:
199- self.cl: Class/Function instance for the class/function name.
200- self.isfunction: True if self.cl is a Function.
201- """
202- self .name = name
203- # XXX - Does classes need to be an instance variable?
204- self .classes = classes
205- self .file = file
206- try :
207- self .cl = self .classes [self .name ]
208- except (IndexError , KeyError ):
209- self .cl = None
210- self .isfunction = isinstance (self .cl , pyclbr .Function )
190+ def __init__ (self , obj ):
191+ "Create a TreeItem for a pyclbr class/function object."
192+ self .obj = obj
193+ self .name = obj .name
194+ self .isfunction = isinstance (obj , pyclbr .Function )
211195
212196 def GetText (self ):
213197 "Return the name of the function/class to display."
198+ name = self .name
214199 if self .isfunction :
215- return "def " + self . name + "(...)"
200+ return "def " + name + "(...)"
216201 else :
217- return "class " + self . name
202+ return "class " + name
218203
219204 def GetIconName (self ):
220205 "Return the name of the icon to display."
@@ -224,95 +209,34 @@ def GetIconName(self):
224209 return "folder"
225210
226211 def IsExpandable (self ):
227- "Return True if this class has methods."
228- if self .cl :
229- try :
230- return not not self .cl .methods
231- except AttributeError :
232- return False
233- return None
212+ "Return True if self.obj has nested objects."
213+ return self .obj .children != {}
234214
235215 def GetSubList (self ):
236- """Return Class methods as a list of MethodBrowserTreeItem items.
237-
238- Each item is a method within the class.
239- """
240- if not self .cl :
241- return []
242- sublist = []
243- for name in self .listmethods ():
244- item = MethodBrowserTreeItem (name , self .cl , self .file )
245- sublist .append (item )
246- return sublist
216+ "Return ChildBrowserTreeItems for children."
217+ return [ChildBrowserTreeItem (obj )
218+ for obj in transform_children (self .obj .children )]
247219
248220 def OnDoubleClick (self ):
249- "Open module with file_open and position to lineno, if it exists."
250- if not os .path .exists (self .file ):
251- return
252- edit = file_open (self .file )
253- if hasattr (self .cl , 'lineno' ):
254- lineno = self .cl .lineno
255- edit .gotoline (lineno )
256-
257- def listmethods (self ):
258- "Return list of methods within a class sorted by lineno."
259- if not self .cl :
260- return []
261- items = []
262- for name , lineno in self .cl .methods .items ():
263- items .append ((lineno , name ))
264- items .sort ()
265- list = []
266- for item , name in items :
267- list .append (name )
268- return list
269-
270- class MethodBrowserTreeItem (TreeItem ):
271- """Browser tree for methods within a class.
272-
273- Uses TreeItem as the basis for the structure of the tree.
274- """
275-
276- def __init__ (self , name , cl , file ):
277- """Create a TreeItem for the methods.
278-
279- Args:
280- name: Name of the class/function.
281- cl: pyclbr.Class instance for name.
282- file: Full path and module name.
283- """
284- self .name = name
285- self .cl = cl
286- self .file = file
287-
288- def GetText (self ):
289- "Return the method name to display."
290- return "def " + self .name + "(...)"
291-
292- def GetIconName (self ):
293- "Return the name of the icon to display."
294- return "python"
295-
296- def IsExpandable (self ):
297- "Return False as there are no tree items after methods."
298- return False
221+ "Open module with file_open and position to lineno."
222+ try :
223+ edit = file_open (self .obj .file )
224+ edit .gotoline (self .obj .lineno )
225+ except (OSError , AttributeError ):
226+ pass
299227
300- def OnDoubleClick (self ):
301- "Open module with file_open and position at the method start."
302- if not os .path .exists (self .file ):
303- return
304- edit = file_open (self .file )
305- edit .gotoline (self .cl .methods [self .name ])
306228
307229def _class_browser (parent ): # htest #
308230 try :
231+ file = sys .argv [1 ] # If pass file on command line
232+ # If this succeeds, unittest will fail.
233+ except IndexError :
309234 file = __file__
310- except NameError :
311- file = sys .argv [0 ]
312- if sys .argv [1 :]:
313- file = sys .argv [1 ]
314- else :
315- file = sys .argv [0 ]
235+ # Add objects for htest
236+ class Nested_in_func (TreeNode ):
237+ def nested_in_class (): pass
238+ def closure ():
239+ class Nested_in_closure : pass
316240 dir , file = os .path .split (file )
317241 name = os .path .splitext (file )[0 ]
318242 flist = pyshell .PyShellFileList (parent )
@@ -321,5 +245,7 @@ def _class_browser(parent): # htest #
321245 ClassBrowser (flist , name , [dir ], _htest = True )
322246
323247if __name__ == "__main__" :
248+ from unittest import main
249+ main ('idlelib.idle_test.test_browser' , verbosity = 2 , exit = False )
324250 from idlelib .idle_test .htest import run
325251 run (_class_browser )
0 commit comments