123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249 |
- """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
- - finish removing limitation to x.py files (ModuleBrowserTreeItem)
- """
- import os
- import pyclbr
- import sys
- from idlelib.config import idleConf
- from idlelib import pyshell
- from idlelib.tree import TreeNode, TreeItem, ScrolledCanvas
- from idlelib.window import ListedToplevel
- file_open = None # Method...Item and Class...Item use this.
- # Normally pyshell.flist.open, but there is no pyshell.flist for htest.
- def transform_children(child_dict, modname=None):
- """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.
- """
- obs = [] # Use list since values should already be sorted.
- 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:
- # If obj.name != key, it has already been suffixed.
- supers = []
- for sup in obj.super:
- if type(sup) is type(''):
- 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:
- """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.
- def __init__(self, master, path, *, _htest=False, _utest=False):
- """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 .py extension. Used in
- creating ModuleBrowserTreeItem as the rootnode for
- the tree and subsequently in the children.
- """
- 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()
- # create top
- self.top = top = ListedToplevel(root)
- top.protocol("WM_DELETE_WINDOW", self.close)
- top.bind("<Escape>", self.close)
- if self._htest: # place dialog below parent if running htest
- top.geometry("+%d+%d" %
- (root.winfo_rootx(), root.winfo_rooty() + 200))
- self.settitle()
- top.focus_set()
- # create scrolled canvas
- 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):
- """Browser tree for Python module.
- Uses TreeItem as the basis for the structure of the tree.
- Used by both browsers.
- """
- def __init__(self, file):
- """Create a TreeItem for the file.
- Args:
- file: Full path and module name.
- """
- 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 os.path.normcase(self.file[-3:]) != ".py":
- return
- if not os.path.exists(self.file):
- return
- file_open(self.file)
- def IsExpandable(self):
- "Return True if Python (.py) file."
- return os.path.normcase(self.file[-3:]) == ".py"
- def listchildren(self):
- "Return sequenced classes and functions in the module."
- dir, base = os.path.split(self.file)
- name, ext = os.path.splitext(base)
- if os.path.normcase(ext) != ".py":
- return []
- try:
- tree = pyclbr.readmodule_ex(name, [dir] + sys.path)
- except ImportError:
- return []
- return transform_children(tree, name)
- class ChildBrowserTreeItem(TreeItem):
- """Browser tree for child nodes within the module.
- Uses TreeItem as the basis for the structure of the tree.
- """
- 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): # htest #
- if len(sys.argv) > 1: # If pass file on command line.
- file = sys.argv[1]
- else:
- file = __file__
- # Add nested objects for htest.
- 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: # If pass file on command line, unittest fails.
- from unittest import main
- main('idlelib.idle_test.test_browser', verbosity=2, exit=False)
- from idlelib.idle_test.htest import run
- run(_module_browser)
|