Skip to content

Commit d6bb65f

Browse files
authored
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.
1 parent bfebfd8 commit d6bb65f

File tree

4 files changed

+42
-47
lines changed

4 files changed

+42
-47
lines changed

Lib/idlelib/browser.py

Lines changed: 31 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@
55
- reparse when source changed (maybe just a button would be OK?)
66
(or recheck on window popup)
77
- add popup menu with more options (e.g. doc strings, base classes, imports)
8-
- show function argument list? (have to do pattern matching on source)
9-
- should the classes and methods lists also be in the module's menu bar?
108
- add base classes to class browser tree
9+
- finish removing limitation to x.py files (ModuleBrowserTreeItem)
1110
"""
1211

1312
import os
@@ -58,19 +57,18 @@ def transform_children(child_dict, modname=None):
5857
class ModuleBrowser:
5958
"""Browse module classes and functions in IDLE.
6059
"""
61-
# This class is the base class for pathbrowser.PathBrowser.
60+
# This class is also the base class for pathbrowser.PathBrowser.
6261
# Init and close are inherited, other methods are overriden.
62+
# PathBrowser.__init__ does not call __init__ below.
6363

64-
def __init__(self, flist, name, path, *, _htest=False, _utest=False):
65-
# XXX This API should change, if the file doesn't end in ".py"
66-
# XXX the code here is bogus!
64+
def __init__(self, master, path, *, _htest=False, _utest=False):
6765
"""Create a window for browsing a module's structure.
6866
6967
Args:
70-
flist: filelist.FileList instance used as the root for the window.
71-
name: Python module to parse.
72-
path: Module search path.
73-
_htest - bool, change box when location running htest.
68+
master: parent for widgets.
69+
path: full path of file to browse.
70+
_htest - bool; change box location when running htest.
71+
-utest - bool; suppress contents when running unittest.
7472
7573
Global variables:
7674
file_open: Function used for opening a file.
@@ -84,35 +82,36 @@ def __init__(self, flist, name, path, *, _htest=False, _utest=False):
8482
global file_open
8583
if not (_htest or _utest):
8684
file_open = pyshell.flist.open
87-
self.name = name
88-
self.file = os.path.join(path[0], self.name + ".py")
85+
self.master = master
86+
self.path = path
8987
self._htest = _htest
9088
self._utest = _utest
91-
self.init(flist)
89+
self.init()
9290

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

98-
def init(self, flist):
96+
def init(self):
9997
"Create browser tkinter widgets, including the tree."
100-
self.flist = flist
98+
root = self.master
10199
# reset pyclbr
102100
pyclbr._modules.clear()
103101
# create top
104-
self.top = top = ListedToplevel(flist.root)
102+
self.top = top = ListedToplevel(root)
105103
top.protocol("WM_DELETE_WINDOW", self.close)
106104
top.bind("<Escape>", self.close)
107105
if self._htest: # place dialog below parent if running htest
108106
top.geometry("+%d+%d" %
109-
(flist.root.winfo_rootx(), flist.root.winfo_rooty() + 200))
107+
(root.winfo_rootx(), root.winfo_rooty() + 200))
110108
self.settitle()
111109
top.focus_set()
112110
# create scrolled canvas
113111
theme = idleConf.CurrentTheme()
114112
background = idleConf.GetHighlight(theme, 'normal')['background']
115-
sc = ScrolledCanvas(top, bg=background, highlightthickness=0, takefocus=1)
113+
sc = ScrolledCanvas(top, bg=background, highlightthickness=0,
114+
takefocus=1)
116115
sc.frame.pack(expand=1, fill="both")
117116
item = self.rootnode()
118117
self.node = node = TreeNode(sc.canvas, None, item)
@@ -122,18 +121,19 @@ def init(self, flist):
122121

123122
def settitle(self):
124123
"Set the window title."
125-
self.top.wm_title("Module Browser - " + self.name)
124+
self.top.wm_title("Module Browser - " + os.path.basename(self.path))
126125
self.top.wm_iconname("Module Browser")
127126

128127
def rootnode(self):
129128
"Return a ModuleBrowserTreeItem as the root of the tree."
130-
return ModuleBrowserTreeItem(self.file)
129+
return ModuleBrowserTreeItem(self.path)
131130

132131

133132
class ModuleBrowserTreeItem(TreeItem):
134133
"""Browser tree for Python module.
135134
136135
Uses TreeItem as the basis for the structure of the tree.
136+
Used by both browsers.
137137
"""
138138

139139
def __init__(self, file):
@@ -170,8 +170,8 @@ def IsExpandable(self):
170170

171171
def listchildren(self):
172172
"Return sequenced classes and functions in the module."
173-
dir, file = os.path.split(self.file)
174-
name, ext = os.path.splitext(file)
173+
dir, base = os.path.split(self.file)
174+
name, ext = os.path.splitext(base)
175175
if os.path.normcase(ext) != ".py":
176176
return []
177177
try:
@@ -227,25 +227,22 @@ def OnDoubleClick(self):
227227

228228

229229
def _module_browser(parent): # htest #
230-
try:
231-
file = sys.argv[1] # If pass file on command line
232-
# If this succeeds, unittest will fail.
233-
except IndexError:
230+
if len(sys.argv) > 1: # If pass file on command line.
231+
file = sys.argv[1]
232+
else:
234233
file = __file__
235-
# Add objects for htest
234+
# Add nested objects for htest.
236235
class Nested_in_func(TreeNode):
237236
def nested_in_class(): pass
238237
def closure():
239238
class Nested_in_closure: pass
240-
dir, file = os.path.split(file)
241-
name = os.path.splitext(file)[0]
242-
flist = pyshell.PyShellFileList(parent)
243239
global file_open
244-
file_open = flist.open
245-
ModuleBrowser(flist, name, [dir], _htest=True)
240+
file_open = pyshell.PyShellFileList(parent).open
241+
ModuleBrowser(parent, file, _htest=True)
246242

247243
if __name__ == "__main__":
248-
from unittest import main
249-
main('idlelib.idle_test.test_browser', verbosity=2, exit=False)
244+
if len(sys.argv) == 1: # If pass file on command line, unittest fails.
245+
from unittest import main
246+
main('idlelib.idle_test.test_browser', verbosity=2, exit=False)
250247
from idlelib.idle_test.htest import run
251248
run(_module_browser)

Lib/idlelib/editor.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -664,10 +664,8 @@ def open_module_browser(self, event=None):
664664
filename = self.open_module()
665665
if filename is None:
666666
return "break"
667-
head, tail = os.path.split(filename)
668-
base, ext = os.path.splitext(tail)
669667
from idlelib import browser
670-
browser.ModuleBrowser(self.flist, base, [head])
668+
browser.ModuleBrowser(self.root, filename)
671669
return "break"
672670

673671
def open_path_browser(self, event=None):

Lib/idlelib/idle_test/test_browser.py

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,30 +24,24 @@ def setUpClass(cls):
2424
requires('gui')
2525
cls.root = Tk()
2626
cls.root.withdraw()
27-
cls.flist = filelist.FileList(cls.root)
28-
cls.file = __file__
29-
cls.path = os.path.dirname(cls.file)
30-
cls.module = os.path.basename(cls.file).rstrip('.py')
31-
cls.mb = browser.ModuleBrowser(cls.flist, cls.module, [cls.path], _utest=True)
27+
cls.mb = browser.ModuleBrowser(cls.root, __file__, _utest=True)
3228

3329
@classmethod
3430
def tearDownClass(cls):
3531
cls.mb.close()
3632
cls.root.destroy()
37-
del cls.root, cls.flist, cls.mb
33+
del cls.root, cls.mb
3834

3935
def test_init(self):
4036
mb = self.mb
4137
eq = self.assertEqual
42-
eq(mb.name, self.module)
43-
eq(mb.file, self.file)
44-
eq(mb.flist, self.flist)
38+
eq(mb.path, __file__)
4539
eq(pyclbr._modules, {})
4640
self.assertIsInstance(mb.node, TreeNode)
4741

4842
def test_settitle(self):
4943
mb = self.mb
50-
self.assertIn(self.module, mb.top.title())
44+
self.assertIn(os.path.basename(__file__), mb.top.title())
5145
self.assertEqual(mb.top.iconname(), 'Module Browser')
5246

5347
def test_rootnode(self):
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Simplify the API of IDLE's Module Browser.
2+
3+
Passing a widget instead of an flist with a root widget opens the option of
4+
creating a browser frame that is only part of a window. Passing a full file
5+
name instead of pieces assumed to come from a .py file opens the possibility
6+
of browsing python files that do not end in .py.

0 commit comments

Comments
 (0)