Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Sep 30, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 31 additions & 34 deletions Lib/idlelib/browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@
- 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)
- 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
- finish removing limitation to x.py files (ModuleBrowserTreeItem)
"""

import os
Expand Down Expand Up @@ -58,19 +57,18 @@ def transform_children(child_dict, modname=None):
class ModuleBrowser:
"""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.
# PathBrowser.__init__ does not call __init__ below.

def __init__(self, flist, name, path, *, _htest=False, _utest=False):
# XXX This API should change, if the file doesn't end in ".py"
# XXX the code here is bogus!
def __init__(self, master, path, *, _htest=False, _utest=False):
"""Create a window for browsing a module's structure.

Args:
flist: filelist.FileList instance used as the root for the window.
name: Python module to parse.
path: Module search path.
_htest - bool, change box when location running htest.
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.
Expand All @@ -84,35 +82,36 @@ def __init__(self, flist, name, path, *, _htest=False, _utest=False):
global file_open
if not (_htest or _utest):
file_open = pyshell.flist.open
self.name = name
self.file = os.path.join(path[0], self.name + ".py")
self.master = master
self.path = path
self._htest = _htest
self._utest = _utest
self.init(flist)
self.init()

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

def init(self, flist):
def init(self):
"Create browser tkinter widgets, including the tree."
self.flist = flist
root = self.master
# reset pyclbr
pyclbr._modules.clear()
# create top
self.top = top = ListedToplevel(flist.root)
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" %
(flist.root.winfo_rootx(), flist.root.winfo_rooty() + 200))
(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 = 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)
Expand All @@ -122,18 +121,19 @@ def init(self, flist):

def settitle(self):
"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")

def rootnode(self):
"Return a ModuleBrowserTreeItem as the root of the tree."
return ModuleBrowserTreeItem(self.file)
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):
Expand Down Expand Up @@ -170,8 +170,8 @@ def IsExpandable(self):

def listchildren(self):
"Return sequenced classes and functions in the module."
dir, file = os.path.split(self.file)
name, ext = os.path.splitext(file)
dir, base = os.path.split(self.file)
name, ext = os.path.splitext(base)
if os.path.normcase(ext) != ".py":
return []
try:
Expand Down Expand Up @@ -227,25 +227,22 @@ def OnDoubleClick(self):


def _module_browser(parent): # htest #
try:
file = sys.argv[1] # If pass file on command line
# If this succeeds, unittest will fail.
except IndexError:
if len(sys.argv) > 1: # If pass file on command line.
file = sys.argv[1]
else:
file = __file__
# Add objects for htest
# Add nested objects for htest.
class Nested_in_func(TreeNode):
def nested_in_class(): pass
def closure():
class Nested_in_closure: pass
dir, file = os.path.split(file)
name = os.path.splitext(file)[0]
flist = pyshell.PyShellFileList(parent)
global file_open
file_open = flist.open
ModuleBrowser(flist, name, [dir], _htest=True)
file_open = pyshell.PyShellFileList(parent).open
ModuleBrowser(parent, file, _htest=True)

if __name__ == "__main__":
from unittest import main
main('idlelib.idle_test.test_browser', verbosity=2, exit=False)
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)
4 changes: 1 addition & 3 deletions Lib/idlelib/editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -664,10 +664,8 @@ def open_module_browser(self, event=None):
filename = self.open_module()
if filename is None:
return "break"
head, tail = os.path.split(filename)
base, ext = os.path.splitext(tail)
from idlelib import browser
browser.ModuleBrowser(self.flist, base, [head])
browser.ModuleBrowser(self.root, filename)
return "break"

def open_path_browser(self, event=None):
Expand Down
14 changes: 4 additions & 10 deletions Lib/idlelib/idle_test/test_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,30 +24,24 @@ def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.root.withdraw()
cls.flist = filelist.FileList(cls.root)
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)
cls.mb = browser.ModuleBrowser(cls.root, __file__, _utest=True)

@classmethod
def tearDownClass(cls):
cls.mb.close()
cls.root.destroy()
del cls.root, cls.flist, cls.mb
del cls.root, cls.mb

def test_init(self):
mb = self.mb
eq = self.assertEqual
eq(mb.name, self.module)
eq(mb.file, self.file)
eq(mb.flist, self.flist)
eq(mb.path, __file__)
eq(pyclbr._modules, {})
self.assertIsInstance(mb.node, TreeNode)

def test_settitle(self):
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')

def test_rootnode(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
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.