module browser xxx to do reparse when source changed maybe just a button would be ok or recheck on window popup add popup menu with more options e g doc strings base classes imports add base classes to class browser tree normally pyshell flist open but there is no pyshell flist for htest the browser depends on pyclbr and importlib which do not support pyi files transform a child dictionary to an ordered sequence of objects the dictionary maps names to pyclbr information objects filter out imported objects augment class names with bases the insertion order of the dictionary is assumed to have been in line number order so sorting is not necessary the current tree only calls this once per childdict as it saves treeitems once created a future tree and tests might violate this so a check prevents multiple inplace augmentations if obj name key it has already been suffixed browse module classes and functions in idle this class is also the base class for pathbrowser pathbrowser init and close are inherited other methods are overridden pathbrowser init does not call init below create a window for browsing a module s structure args master parent for widgets path full path of file to browse htest bool change box location when running htest utest bool suppress contents when running unittest global variables fileopen function used for opening a file instance variables name module name file full path and module with supported extension used in creating modulebrowsertreeitem as the rootnode for the tree and subsequently in the children create top create scrolled canvas browser tree for python module uses treeitem as the basis for the structure of the tree used by both browsers create a treeitem for the file args file full path and module name browser tree for child nodes within the module uses treeitem as the basis for the structure of the tree add nested objects for htest method item and class item use this normally pyshell flist open but there is no pyshell flist for htest the browser depends on pyclbr and importlib which do not support pyi files transform a child dictionary to an ordered sequence of objects the dictionary maps names to pyclbr information objects filter out imported objects augment class names with bases the insertion order of the dictionary is assumed to have been in line number order so sorting is not necessary the current tree only calls this once per child_dict as it saves treeitems once created a future tree and tests might violate this so a check prevents multiple in place augmentations use list since values should already be sorted if obj name key it has already been suffixed browse module classes and functions in idle this class is also the base class for pathbrowser pathbrowser init and close are inherited other methods are overridden pathbrowser __init__ does not call __init__ below create a window for browsing a module s structure args master parent for widgets path full path of file to browse _htest bool change box location when running htest utest bool suppress contents when running unittest global variables file_open function used for opening a file instance variables name module name file full path and module with supported extension used in creating modulebrowsertreeitem as the rootnode for the tree and subsequently in the children create top place dialog below parent if running htest create scrolled canvas browser tree for python module uses treeitem as the basis for the structure of the tree used by both browsers create a treeitem for the file args file full path and module name browser tree for child nodes within the module uses treeitem as the basis for the structure of the tree htest if pass file on command line add nested objects for htest if pass file on command line unittest fails
import os
import pyclbr
import sys

from idlelib.config import idleConf
from idlelib import pyshell
from idlelib.tree import TreeNode, TreeItem, ScrolledCanvas
from idlelib.util import py_extensions
from idlelib.window import ListedToplevel


file_open = None  



browseable_extension_blocklist = ('.pyi',)


def is_browseable_extension(path):
    _, ext = os.path.splitext(path)
    ext = os.path.normcase(ext)
    return ext in py_extensions and ext not in browseable_extension_blocklist


def transform_children(child_dict, modname=None):
    
    obs = []  
    for key, obj in child_dict.items():
        if modname is None or obj.module == modname:
            if hasattr(obj, 'super') and obj.super and obj.name == key:
                
                supers = []
                for sup in obj.super:
                    if isinstance(sup, str):
                        sname = sup
                    else:
                        sname = sup.name
                        if sup.module != obj.module:
                            sname = f'{sup.module}.{sname}'
                    supers.append(sname)
                obj.name += '({})'.format(', '.join(supers))
            obs.append(obj)
    return obs


class ModuleBrowser:
    
    
    
    

    def __init__(self, master, path, *, _htest=False, _utest=False):
        
        self.master = master
        self.path = path
        self._htest = _htest
        self._utest = _utest
        self.init()

    def close(self, event=None):
        "Dismiss the window and the tree nodes."
        self.top.destroy()
        self.node.destroy()

    def init(self):
        "Create browser tkinter widgets, including the tree."
        global file_open
        root = self.master
        flist = (pyshell.flist if not (self._htest or self._utest)
                 else pyshell.PyShellFileList(root))
        file_open = flist.open
        pyclbr._modules.clear()

        
        self.top = top = ListedToplevel(root)
        top.protocol("WM_DELETE_WINDOW", self.close)
        top.bind("<Escape>", self.close)
        if self._htest: 
            top.geometry("+%d+%d" %
                (root.winfo_rootx(), root.winfo_rooty() + 200))
        self.settitle()
        top.focus_set()

        
        theme = idleConf.CurrentTheme()
        background = idleConf.GetHighlight(theme, 'normal')['background']
        sc = ScrolledCanvas(top, bg=background, highlightthickness=0,
                            takefocus=1)
        sc.frame.pack(expand=1, fill="both")
        item = self.rootnode()
        self.node = node = TreeNode(sc.canvas, None, item)
        if not self._utest:
            node.update()
            node.expand()

    def settitle(self):
        "Set the window title."
        self.top.wm_title("Module Browser - " + os.path.basename(self.path))
        self.top.wm_iconname("Module Browser")

    def rootnode(self):
        "Return a ModuleBrowserTreeItem as the root of the tree."
        return ModuleBrowserTreeItem(self.path)


class ModuleBrowserTreeItem(TreeItem):
    

    def __init__(self, file):
        
        self.file = file

    def GetText(self):
        "Return the module name as the text string to display."
        return os.path.basename(self.file)

    def GetIconName(self):
        "Return the name of the icon to display."
        return "python"

    def GetSubList(self):
        "Return ChildBrowserTreeItems for children."
        return [ChildBrowserTreeItem(obj) for obj in self.listchildren()]

    def OnDoubleClick(self):
        "Open a module in an editor window when double clicked."
        if not is_browseable_extension(self.file):
            return
        if not os.path.exists(self.file):
            return
        file_open(self.file)

    def IsExpandable(self):
        "Return True if Python file."
        return is_browseable_extension(self.file)

    def listchildren(self):
        "Return sequenced classes and functions in the module."
        if not is_browseable_extension(self.file):
            return []
        dir, base = os.path.split(self.file)
        name, _ = os.path.splitext(base)
        try:
            tree = pyclbr.readmodule_ex(name, [dir] + sys.path)
        except ImportError:
            return []
        return transform_children(tree, name)


class ChildBrowserTreeItem(TreeItem):
    

    def __init__(self, obj):
        "Create a TreeItem for a pyclbr class/function object."
        self.obj = obj
        self.name = obj.name
        self.isfunction = isinstance(obj, pyclbr.Function)

    def GetText(self):
        "Return the name of the function/class to display."
        name = self.name
        if self.isfunction:
            return "def " + name + "(...)"
        else:
            return "class " + name

    def GetIconName(self):
        "Return the name of the icon to display."
        if self.isfunction:
            return "python"
        else:
            return "folder"

    def IsExpandable(self):
        "Return True if self.obj has nested objects."
        return self.obj.children != {}

    def GetSubList(self):
        "Return ChildBrowserTreeItems for children."
        return [ChildBrowserTreeItem(obj)
                for obj in transform_children(self.obj.children)]

    def OnDoubleClick(self):
        "Open module with file_open and position to lineno."
        try:
            edit = file_open(self.obj.file)
            edit.gotoline(self.obj.lineno)
        except (OSError, AttributeError):
            pass


def _module_browser(parent): 
    if len(sys.argv) > 1:  
        file = sys.argv[1]
    else:
        file = __file__
        
        class Nested_in_func(TreeNode):
            def nested_in_class(): pass
        def closure():
            class Nested_in_closure: pass
    ModuleBrowser(parent, file, _htest=True)

if __name__ == "__main__":
    if len(sys.argv) == 1:  
        from unittest import main
        main('idlelib.idle_test.test_browser', verbosity=2, exit=False)

    from idlelib.idle_test.htest import run
    run(_module_browser)