From d6bb65f378e34fe0c11fdb39588357ecf22964eb Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Sat, 30 Sep 2017 19:54:28 -0400 Subject: [PATCH] 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. --- Lib/idlelib/browser.py | 65 +++++++++---------- Lib/idlelib/editor.py | 4 +- Lib/idlelib/idle_test/test_browser.py | 14 ++-- .../2017-09-30-19-03-26.bpo-31460.HpveI6.rst | 6 ++ 4 files changed, 42 insertions(+), 47 deletions(-) create mode 100644 Misc/NEWS.d/next/IDLE/2017-09-30-19-03-26.bpo-31460.HpveI6.rst diff --git a/Lib/idlelib/browser.py b/Lib/idlelib/browser.py index 9b32315591..a185ed584a 100644 --- a/Lib/idlelib/browser.py +++ b/Lib/idlelib/browser.py @@ -5,9 +5,8 @@ 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) -- 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 @@ -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. @@ -84,35 +82,36 @@ class ModuleBrowser: 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("", 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) @@ -122,18 +121,19 @@ class ModuleBrowser: 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): @@ -170,8 +170,8 @@ class ModuleBrowserTreeItem(TreeItem): 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: @@ -227,25 +227,22 @@ class ChildBrowserTreeItem(TreeItem): 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) diff --git a/Lib/idlelib/editor.py b/Lib/idlelib/editor.py index 5b16ccee8a..87d1eef01e 100644 --- a/Lib/idlelib/editor.py +++ b/Lib/idlelib/editor.py @@ -664,10 +664,8 @@ class EditorWindow(object): 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): diff --git a/Lib/idlelib/idle_test/test_browser.py b/Lib/idlelib/idle_test/test_browser.py index a4add89b93..59e03c5aab 100644 --- a/Lib/idlelib/idle_test/test_browser.py +++ b/Lib/idlelib/idle_test/test_browser.py @@ -24,30 +24,24 @@ class ModuleBrowserTest(unittest.TestCase): 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): diff --git a/Misc/NEWS.d/next/IDLE/2017-09-30-19-03-26.bpo-31460.HpveI6.rst b/Misc/NEWS.d/next/IDLE/2017-09-30-19-03-26.bpo-31460.HpveI6.rst new file mode 100644 index 0000000000..6492115260 --- /dev/null +++ b/Misc/NEWS.d/next/IDLE/2017-09-30-19-03-26.bpo-31460.HpveI6.rst @@ -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. -- 2.40.0