Kaydet (Commit) d6bb65f3 authored tarafından Terry Jan Reedy's avatar Terry Jan Reedy Kaydeden (comit) GitHub

bpo-31460: Simplify the API of IDLE's Module Browser. (#3842)

Passing a widget instead of an flist with a root widget opens the option of
creating a browser frame that is only part of a window. Passing a full file
name instead of pieces assumed to come from a .py file opens the possibility
of browsing python files that do not end in .py.
üst bfebfd81
...@@ -5,9 +5,8 @@ XXX TO DO: ...@@ -5,9 +5,8 @@ XXX TO DO:
- reparse when source changed (maybe just a button would be OK?) - reparse when source changed (maybe just a button would be OK?)
(or recheck on window popup) (or recheck on window popup)
- add popup menu with more options (e.g. doc strings, base classes, imports) - add popup menu with more options (e.g. doc strings, base classes, imports)
- show function argument list? (have to do pattern matching on source)
- should the classes and methods lists also be in the module's menu bar?
- add base classes to class browser tree - add base classes to class browser tree
- finish removing limitation to x.py files (ModuleBrowserTreeItem)
""" """
import os import os
...@@ -58,19 +57,18 @@ def transform_children(child_dict, modname=None): ...@@ -58,19 +57,18 @@ def transform_children(child_dict, modname=None):
class ModuleBrowser: class ModuleBrowser:
"""Browse module classes and functions in IDLE. """Browse module classes and functions in IDLE.
""" """
# This class is the base class for pathbrowser.PathBrowser. # This class is also the base class for pathbrowser.PathBrowser.
# Init and close are inherited, other methods are overriden. # Init and close are inherited, other methods are overriden.
# PathBrowser.__init__ does not call __init__ below.
def __init__(self, flist, name, path, *, _htest=False, _utest=False): def __init__(self, master, path, *, _htest=False, _utest=False):
# XXX This API should change, if the file doesn't end in ".py"
# XXX the code here is bogus!
"""Create a window for browsing a module's structure. """Create a window for browsing a module's structure.
Args: Args:
flist: filelist.FileList instance used as the root for the window. master: parent for widgets.
name: Python module to parse. path: full path of file to browse.
path: Module search path. _htest - bool; change box location when running htest.
_htest - bool, change box when location running htest. -utest - bool; suppress contents when running unittest.
Global variables: Global variables:
file_open: Function used for opening a file. file_open: Function used for opening a file.
...@@ -84,35 +82,36 @@ class ModuleBrowser: ...@@ -84,35 +82,36 @@ class ModuleBrowser:
global file_open global file_open
if not (_htest or _utest): if not (_htest or _utest):
file_open = pyshell.flist.open file_open = pyshell.flist.open
self.name = name self.master = master
self.file = os.path.join(path[0], self.name + ".py") self.path = path
self._htest = _htest self._htest = _htest
self._utest = _utest self._utest = _utest
self.init(flist) self.init()
def close(self, event=None): def close(self, event=None):
"Dismiss the window and the tree nodes." "Dismiss the window and the tree nodes."
self.top.destroy() self.top.destroy()
self.node.destroy() self.node.destroy()
def init(self, flist): def init(self):
"Create browser tkinter widgets, including the tree." "Create browser tkinter widgets, including the tree."
self.flist = flist root = self.master
# reset pyclbr # reset pyclbr
pyclbr._modules.clear() pyclbr._modules.clear()
# create top # create top
self.top = top = ListedToplevel(flist.root) self.top = top = ListedToplevel(root)
top.protocol("WM_DELETE_WINDOW", self.close) top.protocol("WM_DELETE_WINDOW", self.close)
top.bind("<Escape>", self.close) top.bind("<Escape>", self.close)
if self._htest: # place dialog below parent if running htest if self._htest: # place dialog below parent if running htest
top.geometry("+%d+%d" % top.geometry("+%d+%d" %
(flist.root.winfo_rootx(), flist.root.winfo_rooty() + 200)) (root.winfo_rootx(), root.winfo_rooty() + 200))
self.settitle() self.settitle()
top.focus_set() top.focus_set()
# create scrolled canvas # create scrolled canvas
theme = idleConf.CurrentTheme() theme = idleConf.CurrentTheme()
background = idleConf.GetHighlight(theme, 'normal')['background'] background = idleConf.GetHighlight(theme, 'normal')['background']
sc = ScrolledCanvas(top, bg=background, highlightthickness=0, takefocus=1) sc = ScrolledCanvas(top, bg=background, highlightthickness=0,
takefocus=1)
sc.frame.pack(expand=1, fill="both") sc.frame.pack(expand=1, fill="both")
item = self.rootnode() item = self.rootnode()
self.node = node = TreeNode(sc.canvas, None, item) self.node = node = TreeNode(sc.canvas, None, item)
...@@ -122,18 +121,19 @@ class ModuleBrowser: ...@@ -122,18 +121,19 @@ class ModuleBrowser:
def settitle(self): def settitle(self):
"Set the window title." "Set the window title."
self.top.wm_title("Module Browser - " + self.name) self.top.wm_title("Module Browser - " + os.path.basename(self.path))
self.top.wm_iconname("Module Browser") self.top.wm_iconname("Module Browser")
def rootnode(self): def rootnode(self):
"Return a ModuleBrowserTreeItem as the root of the tree." "Return a ModuleBrowserTreeItem as the root of the tree."
return ModuleBrowserTreeItem(self.file) return ModuleBrowserTreeItem(self.path)
class ModuleBrowserTreeItem(TreeItem): class ModuleBrowserTreeItem(TreeItem):
"""Browser tree for Python module. """Browser tree for Python module.
Uses TreeItem as the basis for the structure of the tree. Uses TreeItem as the basis for the structure of the tree.
Used by both browsers.
""" """
def __init__(self, file): def __init__(self, file):
...@@ -170,8 +170,8 @@ class ModuleBrowserTreeItem(TreeItem): ...@@ -170,8 +170,8 @@ class ModuleBrowserTreeItem(TreeItem):
def listchildren(self): def listchildren(self):
"Return sequenced classes and functions in the module." "Return sequenced classes and functions in the module."
dir, file = os.path.split(self.file) dir, base = os.path.split(self.file)
name, ext = os.path.splitext(file) name, ext = os.path.splitext(base)
if os.path.normcase(ext) != ".py": if os.path.normcase(ext) != ".py":
return [] return []
try: try:
...@@ -227,25 +227,22 @@ class ChildBrowserTreeItem(TreeItem): ...@@ -227,25 +227,22 @@ class ChildBrowserTreeItem(TreeItem):
def _module_browser(parent): # htest # def _module_browser(parent): # htest #
try: if len(sys.argv) > 1: # If pass file on command line.
file = sys.argv[1] # If pass file on command line file = sys.argv[1]
# If this succeeds, unittest will fail. else:
except IndexError:
file = __file__ file = __file__
# Add objects for htest # Add nested objects for htest.
class Nested_in_func(TreeNode): class Nested_in_func(TreeNode):
def nested_in_class(): pass def nested_in_class(): pass
def closure(): def closure():
class Nested_in_closure: pass class Nested_in_closure: pass
dir, file = os.path.split(file)
name = os.path.splitext(file)[0]
flist = pyshell.PyShellFileList(parent)
global file_open global file_open
file_open = flist.open file_open = pyshell.PyShellFileList(parent).open
ModuleBrowser(flist, name, [dir], _htest=True) ModuleBrowser(parent, file, _htest=True)
if __name__ == "__main__": if __name__ == "__main__":
from unittest import main if len(sys.argv) == 1: # If pass file on command line, unittest fails.
main('idlelib.idle_test.test_browser', verbosity=2, exit=False) from unittest import main
main('idlelib.idle_test.test_browser', verbosity=2, exit=False)
from idlelib.idle_test.htest import run from idlelib.idle_test.htest import run
run(_module_browser) run(_module_browser)
...@@ -664,10 +664,8 @@ class EditorWindow(object): ...@@ -664,10 +664,8 @@ class EditorWindow(object):
filename = self.open_module() filename = self.open_module()
if filename is None: if filename is None:
return "break" return "break"
head, tail = os.path.split(filename)
base, ext = os.path.splitext(tail)
from idlelib import browser from idlelib import browser
browser.ModuleBrowser(self.flist, base, [head]) browser.ModuleBrowser(self.root, filename)
return "break" return "break"
def open_path_browser(self, event=None): def open_path_browser(self, event=None):
......
...@@ -24,30 +24,24 @@ class ModuleBrowserTest(unittest.TestCase): ...@@ -24,30 +24,24 @@ class ModuleBrowserTest(unittest.TestCase):
requires('gui') requires('gui')
cls.root = Tk() cls.root = Tk()
cls.root.withdraw() cls.root.withdraw()
cls.flist = filelist.FileList(cls.root) cls.mb = browser.ModuleBrowser(cls.root, __file__, _utest=True)
cls.file = __file__
cls.path = os.path.dirname(cls.file)
cls.module = os.path.basename(cls.file).rstrip('.py')
cls.mb = browser.ModuleBrowser(cls.flist, cls.module, [cls.path], _utest=True)
@classmethod @classmethod
def tearDownClass(cls): def tearDownClass(cls):
cls.mb.close() cls.mb.close()
cls.root.destroy() cls.root.destroy()
del cls.root, cls.flist, cls.mb del cls.root, cls.mb
def test_init(self): def test_init(self):
mb = self.mb mb = self.mb
eq = self.assertEqual eq = self.assertEqual
eq(mb.name, self.module) eq(mb.path, __file__)
eq(mb.file, self.file)
eq(mb.flist, self.flist)
eq(pyclbr._modules, {}) eq(pyclbr._modules, {})
self.assertIsInstance(mb.node, TreeNode) self.assertIsInstance(mb.node, TreeNode)
def test_settitle(self): def test_settitle(self):
mb = self.mb mb = self.mb
self.assertIn(self.module, mb.top.title()) self.assertIn(os.path.basename(__file__), mb.top.title())
self.assertEqual(mb.top.iconname(), 'Module Browser') self.assertEqual(mb.top.iconname(), 'Module Browser')
def test_rootnode(self): def test_rootnode(self):
......
Simplify the API of IDLE's Module Browser.
Passing a widget instead of an flist with a root widget opens the option of
creating a browser frame that is only part of a window. Passing a full file
name instead of pieces assumed to come from a .py file opens the possibility
of browsing python files that do not end in .py.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment