(root)/
Python-3.11.7/
Lib/
idlelib/
browser.py
       1  """Module 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  - add base classes to class browser tree
       9  """
      10  
      11  import os
      12  import pyclbr
      13  import sys
      14  
      15  from idlelib.config import idleConf
      16  from idlelib import pyshell
      17  from idlelib.tree import TreeNode, TreeItem, ScrolledCanvas
      18  from idlelib.util import py_extensions
      19  from idlelib.window import ListedToplevel
      20  
      21  
      22  file_open = None  # Method...Item and Class...Item use this.
      23  # Normally pyshell.flist.open, but there is no pyshell.flist for htest.
      24  
      25  # The browser depends on pyclbr and importlib which do not support .pyi files.
      26  browseable_extension_blocklist = ('.pyi',)
      27  
      28  
      29  def is_browseable_extension(path):
      30      _, ext = os.path.splitext(path)
      31      ext = os.path.normcase(ext)
      32      return ext in py_extensions and ext not in browseable_extension_blocklist
      33  
      34  
      35  def transform_children(child_dict, modname=None):
      36      """Transform a child dictionary to an ordered sequence of objects.
      37  
      38      The dictionary maps names to pyclbr information objects.
      39      Filter out imported objects.
      40      Augment class names with bases.
      41      The insertion order of the dictionary is assumed to have been in line
      42      number order, so sorting is not necessary.
      43  
      44      The current tree only calls this once per child_dict as it saves
      45      TreeItems once created.  A future tree and tests might violate this,
      46      so a check prevents multiple in-place augmentations.
      47      """
      48      obs = []  # Use list since values should already be sorted.
      49      for key, obj in child_dict.items():
      50          if modname is None or obj.module == modname:
      51              if hasattr(obj, 'super') and obj.super and obj.name == key:
      52                  # If obj.name != key, it has already been suffixed.
      53                  supers = []
      54                  for sup in obj.super:
      55                      if isinstance(sup, str):
      56                          sname = sup
      57                      else:
      58                          sname = sup.name
      59                          if sup.module != obj.module:
      60                              sname = f'{sup.module}.{sname}'
      61                      supers.append(sname)
      62                  obj.name += '({})'.format(', '.join(supers))
      63              obs.append(obj)
      64      return obs
      65  
      66  
      67  class ESC[4;38;5;81mModuleBrowser:
      68      """Browse module classes and functions in IDLE.
      69      """
      70      # This class is also the base class for pathbrowser.PathBrowser.
      71      # Init and close are inherited, other methods are overridden.
      72      # PathBrowser.__init__ does not call __init__ below.
      73  
      74      def __init__(self, master, path, *, _htest=False, _utest=False):
      75          """Create a window for browsing a module's structure.
      76  
      77          Args:
      78              master: parent for widgets.
      79              path: full path of file to browse.
      80              _htest - bool; change box location when running htest.
      81              -utest - bool; suppress contents when running unittest.
      82  
      83          Global variables:
      84              file_open: Function used for opening a file.
      85  
      86          Instance variables:
      87              name: Module name.
      88              file: Full path and module with supported extension.
      89                  Used in creating ModuleBrowserTreeItem as the rootnode for
      90                  the tree and subsequently in the children.
      91          """
      92          self.master = master
      93          self.path = path
      94          self._htest = _htest
      95          self._utest = _utest
      96          self.init()
      97  
      98      def close(self, event=None):
      99          "Dismiss the window and the tree nodes."
     100          self.top.destroy()
     101          self.node.destroy()
     102  
     103      def init(self):
     104          "Create browser tkinter widgets, including the tree."
     105          global file_open
     106          root = self.master
     107          flist = (pyshell.flist if not (self._htest or self._utest)
     108                   else pyshell.PyShellFileList(root))
     109          file_open = flist.open
     110          pyclbr._modules.clear()
     111  
     112          # create top
     113          self.top = top = ListedToplevel(root)
     114          top.protocol("WM_DELETE_WINDOW", self.close)
     115          top.bind("<Escape>", self.close)
     116          if self._htest: # place dialog below parent if running htest
     117              top.geometry("+%d+%d" %
     118                  (root.winfo_rootx(), root.winfo_rooty() + 200))
     119          self.settitle()
     120          top.focus_set()
     121  
     122          # create scrolled canvas
     123          theme = idleConf.CurrentTheme()
     124          background = idleConf.GetHighlight(theme, 'normal')['background']
     125          sc = ScrolledCanvas(top, bg=background, highlightthickness=0,
     126                              takefocus=1)
     127          sc.frame.pack(expand=1, fill="both")
     128          item = self.rootnode()
     129          self.node = node = TreeNode(sc.canvas, None, item)
     130          if not self._utest:
     131              node.update()
     132              node.expand()
     133  
     134      def settitle(self):
     135          "Set the window title."
     136          self.top.wm_title("Module Browser - " + os.path.basename(self.path))
     137          self.top.wm_iconname("Module Browser")
     138  
     139      def rootnode(self):
     140          "Return a ModuleBrowserTreeItem as the root of the tree."
     141          return ModuleBrowserTreeItem(self.path)
     142  
     143  
     144  class ESC[4;38;5;81mModuleBrowserTreeItem(ESC[4;38;5;149mTreeItem):
     145      """Browser tree for Python module.
     146  
     147      Uses TreeItem as the basis for the structure of the tree.
     148      Used by both browsers.
     149      """
     150  
     151      def __init__(self, file):
     152          """Create a TreeItem for the file.
     153  
     154          Args:
     155              file: Full path and module name.
     156          """
     157          self.file = file
     158  
     159      def GetText(self):
     160          "Return the module name as the text string to display."
     161          return os.path.basename(self.file)
     162  
     163      def GetIconName(self):
     164          "Return the name of the icon to display."
     165          return "python"
     166  
     167      def GetSubList(self):
     168          "Return ChildBrowserTreeItems for children."
     169          return [ChildBrowserTreeItem(obj) for obj in self.listchildren()]
     170  
     171      def OnDoubleClick(self):
     172          "Open a module in an editor window when double clicked."
     173          if not is_browseable_extension(self.file):
     174              return
     175          if not os.path.exists(self.file):
     176              return
     177          file_open(self.file)
     178  
     179      def IsExpandable(self):
     180          "Return True if Python file."
     181          return is_browseable_extension(self.file)
     182  
     183      def listchildren(self):
     184          "Return sequenced classes and functions in the module."
     185          if not is_browseable_extension(self.file):
     186              return []
     187          dir, base = os.path.split(self.file)
     188          name, _ = os.path.splitext(base)
     189          try:
     190              tree = pyclbr.readmodule_ex(name, [dir] + sys.path)
     191          except ImportError:
     192              return []
     193          return transform_children(tree, name)
     194  
     195  
     196  class ESC[4;38;5;81mChildBrowserTreeItem(ESC[4;38;5;149mTreeItem):
     197      """Browser tree for child nodes within the module.
     198  
     199      Uses TreeItem as the basis for the structure of the tree.
     200      """
     201  
     202      def __init__(self, obj):
     203          "Create a TreeItem for a pyclbr class/function object."
     204          self.obj = obj
     205          self.name = obj.name
     206          self.isfunction = isinstance(obj, pyclbr.Function)
     207  
     208      def GetText(self):
     209          "Return the name of the function/class to display."
     210          name = self.name
     211          if self.isfunction:
     212              return "def " + name + "(...)"
     213          else:
     214              return "class " + name
     215  
     216      def GetIconName(self):
     217          "Return the name of the icon to display."
     218          if self.isfunction:
     219              return "python"
     220          else:
     221              return "folder"
     222  
     223      def IsExpandable(self):
     224          "Return True if self.obj has nested objects."
     225          return self.obj.children != {}
     226  
     227      def GetSubList(self):
     228          "Return ChildBrowserTreeItems for children."
     229          return [ChildBrowserTreeItem(obj)
     230                  for obj in transform_children(self.obj.children)]
     231  
     232      def OnDoubleClick(self):
     233          "Open module with file_open and position to lineno."
     234          try:
     235              edit = file_open(self.obj.file)
     236              edit.gotoline(self.obj.lineno)
     237          except (OSError, AttributeError):
     238              pass
     239  
     240  
     241  def _module_browser(parent): # htest #
     242      if len(sys.argv) > 1:  # If pass file on command line.
     243          file = sys.argv[1]
     244      else:
     245          file = __file__
     246          # Add nested objects for htest.
     247          class ESC[4;38;5;81mNested_in_func(ESC[4;38;5;149mTreeNode):
     248              def nested_in_class(): pass
     249          def closure():
     250              class ESC[4;38;5;81mNested_in_closure: pass
     251      ModuleBrowser(parent, file, _htest=True)
     252  
     253  
     254  if __name__ == "__main__":
     255      if len(sys.argv) == 1:  # If pass file on command line, unittest fails.
     256          from unittest import main
     257          main('idlelib.idle_test.test_browser', verbosity=2, exit=False)
     258  
     259      from idlelib.idle_test.htest import run
     260      run(_module_browser)