diff options
Diffstat (limited to 'Lib/idlelib')
52 files changed, 1477 insertions, 397 deletions
diff --git a/Lib/idlelib/AutoComplete.py b/Lib/idlelib/AutoComplete.py index b20512dfa0..ff085d5c70 100644 --- a/Lib/idlelib/AutoComplete.py +++ b/Lib/idlelib/AutoComplete.py @@ -1,6 +1,6 @@ """AutoComplete.py - An IDLE extension for automatically completing names. -This extension can complete either attribute names of file names. It can pop +This extension can complete either attribute names or file names. It can pop a window with all available names, for the user to select from. """ import os @@ -64,7 +64,7 @@ class AutoComplete: def try_open_completions_event(self, event): """Happens when it would be nice to open a completion list, but not - really necessary, for example after an dot, so function + really necessary, for example after a dot, so function calls won't be made. """ lastchar = self.text.get("insert-1c") diff --git a/Lib/idlelib/CREDITS.txt b/Lib/idlelib/CREDITS.txt index 5ff599dee1..3a50eb8e7f 100644 --- a/Lib/idlelib/CREDITS.txt +++ b/Lib/idlelib/CREDITS.txt @@ -24,7 +24,7 @@ Noam Raphael (Code Context, Call Tips, many other patches), and Chui Tey (RPC integration, debugger integration and persistent breakpoints). Scott David Daniels, Tal Einat, Hernan Foffani, Christos Georgiou, -Jim Jewett, Martin v. Löwis, Jason Orendorff, Guilherme Polo, Josh Robb, +Jim Jewett, Martin v. Löwis, Jason Orendorff, Guilherme Polo, Josh Robb, Nigel Rowe, Bruce Sherwood, Jeff Shute, and Weeble have submitted useful patches. Thanks, guys! diff --git a/Lib/idlelib/CallTipWindow.py b/Lib/idlelib/CallTipWindow.py index 8e68a76b2a..9eec17506d 100644 --- a/Lib/idlelib/CallTipWindow.py +++ b/Lib/idlelib/CallTipWindow.py @@ -9,7 +9,7 @@ HIDE_VIRTUAL_EVENT_NAME = "<<calltipwindow-hide>>" HIDE_SEQUENCES = ("<Key-Escape>", "<FocusOut>") CHECKHIDE_VIRTUAL_EVENT_NAME = "<<calltipwindow-checkhide>>" CHECKHIDE_SEQUENCES = ("<KeyRelease>", "<ButtonRelease>") -CHECKHIDE_TIME = 100 # miliseconds +CHECKHIDE_TIME = 100 # milliseconds MARK_RIGHT = "calltipwindowregion_right" diff --git a/Lib/idlelib/ChangeLog b/Lib/idlelib/ChangeLog index 985871bee7..0c36664512 100644 --- a/Lib/idlelib/ChangeLog +++ b/Lib/idlelib/ChangeLog @@ -20,7 +20,7 @@ IDLEfork ChangeLog 2001-07-19 14:49 elguavas * ChangeLog, EditorWindow.py, INSTALLATION, NEWS.txt, README.txt, - TODO.txt, idlever.py: + TODO.txt, idlever.py: minor tidy-ups ready for 0.8.1 alpha tarball release 2001-07-17 15:12 kbk @@ -172,7 +172,7 @@ IDLEfork ChangeLog all this work w/ a future-stmt just looks harder and harder." --tim_one - (From Rel 1.8: "Hack to make this still work with Python 1.5.2. + (From Rel 1.8: "Hack to make this still work with Python 1.5.2. ;-( " --fdrake) 2001-07-14 14:51 kbk @@ -193,7 +193,7 @@ IDLEfork ChangeLog test() to _test()." --GvR This was an interesting merge. The join completely missed removing - goodname(), which was adjacent, but outside of, a small conflict. + goodname(), which was adjacent, but outside of, a small conflict. I only caught it by comparing the 1.1.3.2/1.1.3.3 diff. CVS ain't infallible. @@ -516,12 +516,12 @@ IDLEfork ChangeLog 2000-08-15 22:51 nowonder - * IDLEFORK.html: + * IDLEFORK.html: corrected email address 2000-08-15 22:47 nowonder - * IDLEFORK.html: + * IDLEFORK.html: added .html file for http://idlefork.sourceforge.net 2000-08-15 11:13 dscherer @@ -1574,7 +1574,7 @@ Mon Oct 12 23:59:27 1998 Guido van Rossum <guido@cnri.reston.va.us> * Attic/PopupMenu.py: Pass a root to the help window. * SearchBinding.py: - Add parent argument to 'to to line number' dialog box. + Add parent argument to 'go to line number' dialog box. Sat Oct 10 19:15:32 1998 Guido van Rossum <guido@cnri.reston.va.us> diff --git a/Lib/idlelib/CodeContext.py b/Lib/idlelib/CodeContext.py index 44783b69d0..7d25adaa4c 100644 --- a/Lib/idlelib/CodeContext.py +++ b/Lib/idlelib/CodeContext.py @@ -57,18 +57,18 @@ class CodeContext: # Calculate the border width and horizontal padding required to # align the context with the text in the main Text widget. # - # All values are passed through int(str(<value>)), since some + # All values are passed through getint(), since some # values may be pixel objects, which can't simply be added to ints. widgets = self.editwin.text, self.editwin.text_frame # Calculate the required vertical padding padx = 0 for widget in widgets: - padx += int(str( widget.pack_info()['padx'] )) - padx += int(str( widget.cget('padx') )) + padx += widget.tk.getint(widget.pack_info()['padx']) + padx += widget.tk.getint(widget.cget('padx')) # Calculate the required border width border = 0 for widget in widgets: - border += int(str( widget.cget('border') )) + border += widget.tk.getint(widget.cget('border')) self.label = tkinter.Label(self.editwin.top, text="\n" * (self.context_depth - 1), anchor=W, justify=LEFT, diff --git a/Lib/idlelib/ColorDelegator.py b/Lib/idlelib/ColorDelegator.py index 9f31349604..02eac47068 100644 --- a/Lib/idlelib/ColorDelegator.py +++ b/Lib/idlelib/ColorDelegator.py @@ -2,6 +2,7 @@ import time import re import keyword import builtins +from tkinter import TkVersion from idlelib.Delegator import Delegator from idlelib.configHandler import idleConf @@ -32,6 +33,28 @@ def make_pat(): prog = re.compile(make_pat(), re.S) idprog = re.compile(r"\s+(\w+)", re.S) +def color_config(text): # Called from htest, Editor, and Turtle Demo. + '''Set color opitons of Text widget. + + Should be called whenever ColorDelegator is called. + ''' + # Not automatic because ColorDelegator does not know 'text'. + theme = idleConf.CurrentTheme() + normal_colors = idleConf.GetHighlight(theme, 'normal') + cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg') + select_colors = idleConf.GetHighlight(theme, 'hilite') + text.config( + foreground=normal_colors['foreground'], + background=normal_colors['background'], + insertbackground=cursor_color, + selectforeground=select_colors['foreground'], + selectbackground=select_colors['background'], + ) + if TkVersion >= 8.5: + text.config( + inactiveselectbackground=select_colors['background']) + + class ColorDelegator(Delegator): def __init__(self): @@ -233,6 +256,7 @@ class ColorDelegator(Delegator): for tag in self.tagdefs: self.tag_remove(tag, "1.0", "end") + def _color_delegator(parent): # htest # from tkinter import Toplevel, Text from idlelib.Percolator import Percolator @@ -247,6 +271,7 @@ def _color_delegator(parent): # htest # text.insert("insert", source) text.focus_set() + color_config(text) p = Percolator(text) d = ColorDelegator() p.insertfilter(d) diff --git a/Lib/idlelib/Debugger.py b/Lib/idlelib/Debugger.py index 250422edbb..d5e217dde9 100644 --- a/Lib/idlelib/Debugger.py +++ b/Lib/idlelib/Debugger.py @@ -372,7 +372,7 @@ class StackViewer(ScrolledList): def __init__(self, master, flist, gui): if macosxSupport.isAquaTk(): # At least on with the stock AquaTk version on OSX 10.4 you'll - # get an shaking GUI that eventually kills IDLE if the width + # get a shaking GUI that eventually kills IDLE if the width # argument is specified. ScrolledList.__init__(self, master) else: diff --git a/Lib/idlelib/Delegator.py b/Lib/idlelib/Delegator.py index c4765163f8..dc2a1aaeea 100644 --- a/Lib/idlelib/Delegator.py +++ b/Lib/idlelib/Delegator.py @@ -1,10 +1,10 @@ class Delegator: - # The cache is only used to be able to change delegates! - def __init__(self, delegate=None): self.delegate = delegate self.__cache = set() + # Cache is used to only remove added attributes + # when changing the delegate. def __getattr__(self, name): attr = getattr(self.delegate, name) # May raise AttributeError @@ -13,6 +13,9 @@ class Delegator: return attr def resetcache(self): + "Removes added attributes while leaving original attributes." + # Function is really about resetting delagator dict + # to original state. Cache is just a means for key in self.__cache: try: delattr(self, key) @@ -21,5 +24,10 @@ class Delegator: self.__cache.clear() def setdelegate(self, delegate): + "Reset attributes and change delegate." self.resetcache() self.delegate = delegate + +if __name__ == '__main__': + from unittest import main + main('idlelib.idle_test.test_delegator', verbosity=2) diff --git a/Lib/idlelib/EditorWindow.py b/Lib/idlelib/EditorWindow.py index b5868be3fb..9944da3e70 100644 --- a/Lib/idlelib/EditorWindow.py +++ b/Lib/idlelib/EditorWindow.py @@ -90,7 +90,7 @@ helpDialog = HelpDialog() # singleton instance, no longer used class EditorWindow(object): from idlelib.Percolator import Percolator - from idlelib.ColorDelegator import ColorDelegator + from idlelib.ColorDelegator import ColorDelegator, color_config from idlelib.UndoDelegator import UndoDelegator from idlelib.IOBinding import IOBinding, filesystemencoding, encoding from idlelib import Bindings @@ -742,20 +742,7 @@ class EditorWindow(object): # Called from self.filename_change_hook and from configDialog.py self._rmcolorizer() self._addcolorizer() - theme = idleConf.CurrentTheme() - normal_colors = idleConf.GetHighlight(theme, 'normal') - cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg') - select_colors = idleConf.GetHighlight(theme, 'hilite') - self.text.config( - foreground=normal_colors['foreground'], - background=normal_colors['background'], - insertbackground=cursor_color, - selectforeground=select_colors['foreground'], - selectbackground=select_colors['background'], - ) - if TkVersion >= 8.5: - self.text.config( - inactiveselectbackground=select_colors['background']) + EditorWindow.color_config(self.text) IDENTCHARS = string.ascii_letters + string.digits + "_" diff --git a/Lib/idlelib/HISTORY.txt b/Lib/idlelib/HISTORY.txt index 01d73ed2ba..731fabd185 100644 --- a/Lib/idlelib/HISTORY.txt +++ b/Lib/idlelib/HISTORY.txt @@ -11,7 +11,7 @@ What's New in IDLEfork 0.8.1? *Release date: 22-Jul-2001* - New tarball released as a result of the 'revitalisation' of the IDLEfork - project. + project. - This release requires python 2.1 or better. Compatibility with earlier versions of python (especially ancient ones like 1.5x) is no longer a @@ -26,8 +26,8 @@ What's New in IDLEfork 0.8.1? not working, but I believe this was the case with the previous IDLE fork release (0.7.1) as well. -- This release is being made now to mark the point at which IDLEfork is - launching into a new stage of development. +- This release is being made now to mark the point at which IDLEfork is + launching into a new stage of development. - IDLEfork CVS will now be branched to enable further development and exploration of the two "execution in a remote process" patches submitted by @@ -96,7 +96,7 @@ IDLEfork 0.7.1 - 29 May 2000 instead of the IDLE help; shift-TAB is now a synonym for unindent. - New modules: - + ExecBinding.py Executes program through loader loader.py Bootstraps user program protocol.py RPC protocol diff --git a/Lib/idlelib/IOBinding.py b/Lib/idlelib/IOBinding.py index 5ec9d546fd..84f39a2fee 100644 --- a/Lib/idlelib/IOBinding.py +++ b/Lib/idlelib/IOBinding.py @@ -10,6 +10,7 @@ import tkinter.filedialog as tkFileDialog import tkinter.messagebox as tkMessageBox from tkinter.simpledialog import askstring +from idlelib.configHandler import idleConf # Try setting the locale, so that we can find out @@ -61,7 +62,7 @@ locale_encoding = locale_encoding.lower() encoding = locale_encoding ### KBK 07Sep07 This is used all over IDLE, check! ### 'encoding' is used below in encode(), check! -coding_re = re.compile(r'^[ \t\f]*#.*coding[:=][ \t]*([-\w.]+)', re.ASCII) +coding_re = re.compile(r'^[ \t\f]*#.*?coding[:=][ \t]*([-\w.]+)', re.ASCII) blank_re = re.compile(r'^[ \t\f]*(?:[#\r\n]|$)', re.ASCII) def coding_spec(data): @@ -525,7 +526,6 @@ class IOBinding: def _io_binding(parent): # htest # from tkinter import Toplevel, Text - from idlelib.configHandler import idleConf root = Toplevel(parent) root.title("Test IOBinding") @@ -536,14 +536,23 @@ def _io_binding(parent): # htest # self.text = text self.flist = None self.text.bind("<Control-o>", self.open) + self.text.bind('<Control-p>', self.print) self.text.bind("<Control-s>", self.save) + self.text.bind("<Alt-s>", self.saveas) + self.text.bind('<Control-c>', self.savecopy) def get_saved(self): return 0 def set_saved(self, flag): pass def reset_undo(self): pass def open(self, event): self.text.event_generate("<<open-window-from-file>>") + def print(self, event): + self.text.event_generate("<<print-window>>") def save(self, event): self.text.event_generate("<<save-window>>") + def saveas(self, event): + self.text.event_generate("<<save-window-as-file>>") + def savecopy(self, event): + self.text.event_generate("<<save-copy-of-window-as-file>>") text = Text(root) text.pack() diff --git a/Lib/idlelib/MultiCall.py b/Lib/idlelib/MultiCall.py index 251a84d083..8462854921 100644 --- a/Lib/idlelib/MultiCall.py +++ b/Lib/idlelib/MultiCall.py @@ -111,7 +111,7 @@ class _SimpleBinder: raise # An int in range(1 << len(_modifiers)) represents a combination of modifiers -# (if the least significent bit is on, _modifiers[0] is on, and so on). +# (if the least significant bit is on, _modifiers[0] is on, and so on). # _state_subsets gives for each combination of modifiers, or *state*, # a list of the states which are a subset of it. This list is ordered by the # number of modifiers is the state - the most specific state comes first. diff --git a/Lib/idlelib/NEWS.txt b/Lib/idlelib/NEWS.txt index 8b8e10b5ea..b7ab98fc2f 100644 --- a/Lib/idlelib/NEWS.txt +++ b/Lib/idlelib/NEWS.txt @@ -1,6 +1,81 @@ -What's New in Idle 3.4.4? +What's New in IDLE 3.5.3? ========================= -*Release date: 2015-12-20* +*Release date: 2017-01-01?* + +- Issue #27714: text_textview and test_autocomplete now pass when re-run + in the same process. This occurs when test_idle fails when run with the + -w option but without -jn. Fix warning from test_config. + +- Issue #25507: IDLE no longer runs buggy code because of its tkinter imports. + Users must include the same imports required to run directly in Python. + +- Issue #27452: add line counter and crc to IDLE configHandler test dump. + +- Issue #27365: Allow non-ascii chars in IDLE NEWS.txt, for contributor names. + +- Issue #27245: IDLE: Cleanly delete custom themes and key bindings. + Previously, when IDLE was started from a console or by import, a cascade + of warnings was emitted. Patch by Serhiy Storchaka. + + +What's New in IDLE 3.5.2? +========================= +*Release date: 2016-06-26* + +- Issue #5124: Paste with text selected now replaces the selection on X11. + This matches how paste works on Windows, Mac, most modern Linux apps, + and ttk widgets. Original patch by Serhiy Storchaka. + +- Issue #24759: Make clear in idlelib.idle_test.__init__ that the directory + is a private implementation of test.test_idle and tool for maintainers. + +- Issue #27196: Stop 'ThemeChangef' warnings when running IDLE tests. + These persisted after other warnings were suppressed in #20567. + Apply Serhiy Storchaka's update_idletasks solution to four test files. + Record this additional advice in idle_test/README.txt + +- Issue #20567: Revise idle_test/README.txt with advice about avoiding + tk warning messages from tests. Apply advice to several IDLE tests. + +- Issue #27117: Make colorizer htest and turtledemo work with dark themes. + Move code for configuring text widget colors to a new function. + +- Issue #26673: When tk reports font size as 0, change to size 10. + Such fonts on Linux prevented the configuration dialog from opening. + +- Issue #21939: Add test for IDLE's percolator. + Original patch by Saimadhav Heblikar. + +- Issue #21676: Add test for IDLE's replace dialog. + Original patch by Saimadhav Heblikar. + +- Issue #18410: Add test for IDLE's search dialog. + Original patch by Westley MartÃnez. + +- Issue #21703: Add test for undo delegator. + Original patch by Saimadhav Heblikar . + +- Issue #27044: Add ConfigDialog.remove_var_callbacks to stop memory leaks. + +- Issue #23977: Add more asserts to test_delegator. + +- Issue #20640: Add tests for idlelib.configHelpSourceEdit. + Patch by Saimadhav Heblikar. + +- In the 'IDLE-console differences' section of the IDLE doc, clarify + how running with IDLE affects sys.modules and the standard streams. + +- Issue #25507: fix incorrect change in IOBinding that prevented printing. + Augment IOBinding htest to include all major IOBinding functions. + +- Issue #25905: Revert unwanted conversion of ' to ’ RIGHT SINGLE QUOTATION + MARK in README.txt and open this and NEWS.txt with 'ascii'. + Re-encode CREDITS.txt to utf-8 and open it with 'utf-8'. + + +What's New in IDLE 3.5.1? +========================= +*Release date: 2015-12-06* - Issue 15348: Stop the debugger engine (normally in a user process) before closing the debugger window (running in the IDLE process). @@ -96,6 +171,11 @@ What's New in Idle 3.4.4? - Issue #24790: Remove extraneous code (which also create 2 & 3 conflicts). + +What's New in IDLE 3.5.0? +========================= +*Release date: 2015-09-13* + - Issue #23672: Allow Idle to edit and run files with astral chars in name. Patch by Mohd Sanad Zaki Rizvi. @@ -112,11 +192,6 @@ What's New in Idle 3.4.4? - Issue #23184: remove unused names and imports in idlelib. Initial patch by Al Sweigart. - -What's New in Idle 3.4.3? -========================= -*Release date: 2015-02-25* - - Issue #20577: Configuration of the max line length for the FormatParagraph extension has been moved from the General tab of the Idle preferences dialog to the FormatParagraph tab of the Config Extensions dialog. @@ -129,7 +204,7 @@ What's New in Idle 3.4.3? Changes are written to HOME/.idlerc/config-extensions.cfg. Original patch by Tal Einat. -- Issue #16233: A module browser (File : Class Browser, Alt+C) requires a +- Issue #16233: A module browser (File : Class Browser, Alt+C) requires an editor window with a filename. When Class Browser is requested otherwise, from a shell, output window, or 'Untitled' editor, Idle no longer displays an error box. It now pops up an Open Module box (Alt+M). If a valid name @@ -145,16 +220,11 @@ What's New in Idle 3.4.3? - Issue #23180: Rename IDLE "Windows" menu item to "Window". Patch by Al Sweigart. - -What's New in IDLE 3.4.2? -========================= -*Release date: 2014-10-06* - - Issue #17390: Adjust Editor window title; remove 'Python', move version to end. - Issue #14105: Idle debugger breakpoints no longer disappear - when inseting or deleting lines. + when inserting or deleting lines. - Issue #17172: Turtledemo can now be run from Idle. Currently, the entry is on the Help menu, but it may move to Run. @@ -184,14 +254,8 @@ What's New in IDLE 3.4.2? - Issue #18409: Add unittest for AutoComplete. Patch by Phil Webster. -- Issue #18104: Add idlelib/idle_test/htest.py with a few sample tests to begin - consolidating and improving human-validated tests of Idle. Change other files - as needed to work with htest. Running the module as __main__ runs all tests. - - -What's New in IDLE 3.4.1? -========================= -*Release date: 2014-05-18* +- Issue #21477: htest.py - Improve framework, complete set of tests. + Patches by Saimadhav Heblikar - Issue #18104: Add idlelib/idle_test/htest.py with a few sample tests to begin consolidating and improving human-validated tests of Idle. Change other files @@ -290,11 +354,6 @@ What's New in IDLE 3.1b1? - Use of 'filter' in keybindingDialog.py was causing custom key assignment to fail. Patch 5707 amaury.forgeotdarc. - -What's New in IDLE 3.1a1? -========================= -*Release date: 07-Mar-09* - - Issue #4815: Offer conversion to UTF-8 if source files have no encoding declaration and are not encoded in UTF-8. @@ -334,11 +393,6 @@ What's New in IDLE 2.7? (UNRELEASED, but merged into 3.1 releases above.) - Issue #3549: On MacOS the preferences menu was not present - -What's New in IDLE 3.0 final? -============================= -*Release date: 03-Dec-2008* - - IDLE would print a "Unhandled server exception!" message when internal debugging is enabled. @@ -348,11 +402,6 @@ What's New in IDLE 3.0 final? - Issue #4383: When IDLE cannot make the connection to its subprocess, it would fail to properly display the error message. - -What's New in IDLE 3.0a3? -========================= -*Release date: 29-Feb-2008* - - help() was not paging to the shell. Issue1650. - CodeContext was not importing. @@ -364,19 +413,9 @@ What's New in IDLE 3.0a3? - Issue #1585: IDLE uses non-existent xrange() function. - -What's New in IDLE 3.0a2? -========================= -*Release date: 06-Dec-2007* - - Windows EOL sequence not converted correctly, encoding error. Caused file save to fail. Bug 1130. - -What's New in IDLE 3.0a1? -========================= -*Release date: 31-Aug-2007* - - IDLE converted to Python 3000 syntax. - Strings became Unicode. diff --git a/Lib/idlelib/ParenMatch.py b/Lib/idlelib/ParenMatch.py index 19bad8ce38..47e10f3517 100644 --- a/Lib/idlelib/ParenMatch.py +++ b/Lib/idlelib/ParenMatch.py @@ -9,7 +9,7 @@ from idlelib.HyperParser import HyperParser from idlelib.configHandler import idleConf _openers = {')':'(',']':'[','}':'{'} -CHECK_DELAY = 100 # miliseconds +CHECK_DELAY = 100 # milliseconds class ParenMatch: """Highlight matching parentheses diff --git a/Lib/idlelib/Percolator.py b/Lib/idlelib/Percolator.py index 9e9331940f..b8be2aa746 100644 --- a/Lib/idlelib/Percolator.py +++ b/Lib/idlelib/Percolator.py @@ -1,6 +1,7 @@ from idlelib.WidgetRedirector import WidgetRedirector from idlelib.Delegator import Delegator + class Percolator: def __init__(self, text): @@ -16,8 +17,10 @@ class Percolator: while self.top is not self.bottom: self.removefilter(self.top) self.top = None - self.bottom.setdelegate(None); self.bottom = None - self.redir.close(); self.redir = None + self.bottom.setdelegate(None) + self.bottom = None + self.redir.close() + self.redir = None self.text = None def insert(self, index, chars, tags=None): @@ -51,54 +54,52 @@ class Percolator: f.setdelegate(filter.delegate) filter.setdelegate(None) -def _percolator(parent): + +def _percolator(parent): # htest # import tkinter as tk import re + class Tracer(Delegator): def __init__(self, name): self.name = name Delegator.__init__(self, None) + def insert(self, *args): print(self.name, ": insert", args) self.delegate.insert(*args) + def delete(self, *args): print(self.name, ": delete", args) self.delegate.delete(*args) - root = tk.Tk() - root.title("Test Percolator") + + box = tk.Toplevel(parent) + box.title("Test Percolator") width, height, x, y = list(map(int, re.split('[x+]', parent.geometry()))) - root.geometry("+%d+%d"%(x, y + 150)) - text = tk.Text(root) + box.geometry("+%d+%d" % (x, y + 150)) + text = tk.Text(box) p = Percolator(text) + pin = p.insertfilter + pout = p.removefilter t1 = Tracer("t1") t2 = Tracer("t2") def toggle1(): - if var1.get() == 0: - var1.set(1) - p.insertfilter(t1) - elif var1.get() == 1: - var1.set(0) - p.removefilter(t1) - + (pin if var1.get() else pout)(t1) def toggle2(): - if var2.get() == 0: - var2.set(1) - p.insertfilter(t2) - elif var2.get() == 1: - var2.set(0) - p.removefilter(t2) + (pin if var2.get() else pout)(t2) text.pack() var1 = tk.IntVar() - cb1 = tk.Checkbutton(root, text="Tracer1", command=toggle1, variable=var1) + cb1 = tk.Checkbutton(box, text="Tracer1", command=toggle1, variable=var1) cb1.pack() var2 = tk.IntVar() - cb2 = tk.Checkbutton(root, text="Tracer2", command=toggle2, variable=var2) + cb2 = tk.Checkbutton(box, text="Tracer2", command=toggle2, variable=var2) cb2.pack() - root.mainloop() - if __name__ == "__main__": + import unittest + unittest.main('idlelib.idle_test.test_percolator', verbosity=2, + exit=False) + from idlelib.idle_test.htest import run run(_percolator) diff --git a/Lib/idlelib/PyShell.py b/Lib/idlelib/PyShell.py index 1bcc9b6814..5dec68e580 100755 --- a/Lib/idlelib/PyShell.py +++ b/Lib/idlelib/PyShell.py @@ -1396,6 +1396,17 @@ class PseudoInputFile(PseudoFile): self.shell.close() +def fix_x11_paste(root): + "Make paste replace selection on x11. See issue #5124." + if root._windowingsystem == 'x11': + for cls in 'Text', 'Entry', 'Spinbox': + root.bind_class( + cls, + '<<Paste>>', + 'catch {%W delete sel.first sel.last}\n' + + root.bind_class(cls, '<<Paste>>')) + + usage_msg = """\ USAGE: idle [-deins] [-t title] [file]* @@ -1528,8 +1539,10 @@ def main(): 'editor-on-startup', type='bool') enable_edit = enable_edit or edit_start enable_shell = enable_shell or not enable_edit + # start editor and/or shell windows: root = Tk(className="Idle") + root.withdraw() # set application icon icondir = os.path.join(os.path.dirname(__file__), 'Icons') @@ -1544,7 +1557,7 @@ def main(): root.wm_iconphoto(True, *icons) fixwordbreaks(root) - root.withdraw() + fix_x11_paste(root) flist = PyShellFileList(root) macosxSupport.setupApp(root, flist) diff --git a/Lib/idlelib/ReplaceDialog.py b/Lib/idlelib/ReplaceDialog.py index 2665a1c630..f2ea22e7f7 100644 --- a/Lib/idlelib/ReplaceDialog.py +++ b/Lib/idlelib/ReplaceDialog.py @@ -1,3 +1,8 @@ +"""Replace dialog for IDLE. Inherits SearchDialogBase for GUI. +Uses idlelib.SearchEngine for search capability. +Defines various replace related functions like replace, replace all, +replace+find. +""" from tkinter import * from idlelib import SearchEngine @@ -6,6 +11,8 @@ import re def replace(text): + """Returns a singleton ReplaceDialog instance.The single dialog + saves user entries and preferences across instances.""" root = text._root() engine = SearchEngine.get(root) if not hasattr(engine, "_replacedialog"): @@ -24,6 +31,7 @@ class ReplaceDialog(SearchDialogBase): self.replvar = StringVar(root) def open(self, text): + """Display the replace dialog""" SearchDialogBase.open(self, text) try: first = text.index("sel.first") @@ -39,6 +47,7 @@ class ReplaceDialog(SearchDialogBase): self.ok = 1 def create_entries(self): + """Create label and text entry widgets""" SearchDialogBase.create_entries(self) self.replent = self.make_entry("Replace with:", self.replvar)[0] @@ -57,9 +66,10 @@ class ReplaceDialog(SearchDialogBase): self.do_replace() def default_command(self, event=None): + "Replace and find next." if self.do_find(self.ok): - if self.do_replace(): # Only find next match if replace succeeded. - # A bad re can cause it to fail. + if self.do_replace(): # Only find next match if replace succeeded. + # A bad re can cause it to fail. self.do_find(0) def _replace_expand(self, m, repl): @@ -77,6 +87,7 @@ class ReplaceDialog(SearchDialogBase): return new def replace_all(self, event=None): + """Replace all instances of patvar with replvar in text""" prog = self.engine.getprog() if not prog: return @@ -173,6 +184,8 @@ class ReplaceDialog(SearchDialogBase): return True def show_hit(self, first, last): + """Highlight text from 'first' to 'last'. + 'first', 'last' - Text indices""" text = self.text text.mark_set("insert", first) text.tag_remove("sel", "1.0", "end") @@ -189,11 +202,13 @@ class ReplaceDialog(SearchDialogBase): SearchDialogBase.close(self, event) self.text.tag_remove("hit", "1.0", "end") -def _replace_dialog(parent): - root = Tk() - root.title("Test ReplaceDialog") + +def _replace_dialog(parent): # htest # + """htest wrapper function""" + box = Toplevel(parent) + box.title("Test ReplaceDialog") width, height, x, y = list(map(int, re.split('[x+]', parent.geometry()))) - root.geometry("+%d+%d"%(x, y + 150)) + box.geometry("+%d+%d"%(x, y + 150)) # mock undo delegator methods def undo_block_start(): @@ -202,20 +217,25 @@ def _replace_dialog(parent): def undo_block_stop(): pass - text = Text(root) + text = Text(box, inactiveselectbackground='gray') text.undo_block_start = undo_block_start text.undo_block_stop = undo_block_stop text.pack() - text.insert("insert","This is a sample string.\n"*10) + text.insert("insert","This is a sample sTring\nPlus MORE.") + text.focus_set() def show_replace(): text.tag_add(SEL, "1.0", END) replace(text) text.tag_remove(SEL, "1.0", END) - button = Button(root, text="Replace", command=show_replace) + button = Button(box, text="Replace", command=show_replace) button.pack() if __name__ == '__main__': + import unittest + unittest.main('idlelib.idle_test.test_replacedialog', + verbosity=2, exit=False) + from idlelib.idle_test.htest import run run(_replace_dialog) diff --git a/Lib/idlelib/SearchDialog.py b/Lib/idlelib/SearchDialog.py index 77ef7b9a82..765d53fe92 100644 --- a/Lib/idlelib/SearchDialog.py +++ b/Lib/idlelib/SearchDialog.py @@ -4,6 +4,7 @@ from idlelib import SearchEngine from idlelib.SearchDialogBase import SearchDialogBase def _setup(text): + "Create or find the singleton SearchDialog instance." root = text._root() engine = SearchEngine.get(root) if not hasattr(engine, "_searchdialog"): @@ -11,13 +12,16 @@ def _setup(text): return engine._searchdialog def find(text): + "Handle the editor edit menu item and corresponding event." pat = text.get("sel.first", "sel.last") - return _setup(text).open(text,pat) + return _setup(text).open(text, pat) # Open is inherited from SDBase. def find_again(text): + "Handle the editor edit menu item and corresponding event." return _setup(text).find_again(text) def find_selection(text): + "Handle the editor edit menu item and corresponding event." return _setup(text).find_selection(text) class SearchDialog(SearchDialogBase): @@ -66,24 +70,28 @@ class SearchDialog(SearchDialogBase): self.engine.setcookedpat(pat) return self.find_again(text) -def _search_dialog(parent): - root = Tk() - root.title("Test SearchDialog") + +def _search_dialog(parent): # htest # + '''Display search test box.''' + box = Toplevel(parent) + box.title("Test SearchDialog") width, height, x, y = list(map(int, re.split('[x+]', parent.geometry()))) - root.geometry("+%d+%d"%(x, y + 150)) - text = Text(root) + box.geometry("+%d+%d"%(x, y + 150)) + text = Text(box, inactiveselectbackground='gray') text.pack() - text.insert("insert","This is a sample string.\n"*10) + text.insert("insert","This is a sample string.\n"*5) def show_find(): text.tag_add(SEL, "1.0", END) - s = _setup(text) - s.open(text) + _setup(text).open(text) text.tag_remove(SEL, "1.0", END) - button = Button(root, text="Search", command=show_find) + button = Button(box, text="Search (selection ignored)", command=show_find) button.pack() if __name__ == '__main__': + import unittest + unittest.main('idlelib.idle_test.test_searchdialog', + verbosity=2, exit=False) from idlelib.idle_test.htest import run run(_search_dialog) diff --git a/Lib/idlelib/UndoDelegator.py b/Lib/idlelib/UndoDelegator.py index 04c1cf5a27..1c2502d818 100644 --- a/Lib/idlelib/UndoDelegator.py +++ b/Lib/idlelib/UndoDelegator.py @@ -336,30 +336,33 @@ class CommandSequence(Command): self.depth = self.depth + incr return self.depth -def _undo_delegator(parent): + +def _undo_delegator(parent): # htest # + import re + import tkinter as tk from idlelib.Percolator import Percolator - root = Tk() - root.title("Test UndoDelegator") + undowin = tk.Toplevel() + undowin.title("Test UndoDelegator") width, height, x, y = list(map(int, re.split('[x+]', parent.geometry()))) - root.geometry("+%d+%d"%(x, y + 150)) + undowin.geometry("+%d+%d"%(x, y + 150)) - text = Text(root) - text.config(height=10) + text = Text(undowin, height=10) text.pack() text.focus_set() p = Percolator(text) d = UndoDelegator() p.insertfilter(d) - undo = Button(root, text="Undo", command=lambda:d.undo_event(None)) + undo = Button(undowin, text="Undo", command=lambda:d.undo_event(None)) undo.pack(side='left') - redo = Button(root, text="Redo", command=lambda:d.redo_event(None)) + redo = Button(undowin, text="Redo", command=lambda:d.redo_event(None)) redo.pack(side='left') - dump = Button(root, text="Dump", command=lambda:d.dump_event(None)) + dump = Button(undowin, text="Dump", command=lambda:d.dump_event(None)) dump.pack(side='left') - root.mainloop() - if __name__ == "__main__": + import unittest + unittest.main('idlelib.idle_test.test_undodelegator', verbosity=2, + exit=False) from idlelib.idle_test.htest import run run(_undo_delegator) diff --git a/Lib/idlelib/WidgetRedirector.py b/Lib/idlelib/WidgetRedirector.py index b3d7bfa3c4..b66be9e721 100644 --- a/Lib/idlelib/WidgetRedirector.py +++ b/Lib/idlelib/WidgetRedirector.py @@ -47,8 +47,9 @@ class WidgetRedirector: tk.createcommand(w, self.dispatch) def __repr__(self): - return "WidgetRedirector(%s<%s>)" % (self.widget.__class__.__name__, - self.widget._w) + return "%s(%s<%s>)" % (self.__class__.__name__, + self.widget.__class__.__name__, + self.widget._w) def close(self): "Unregister operations and revert redirection created by .__init__." @@ -67,7 +68,7 @@ class WidgetRedirector: '''Return OriginalCommand(operation) after registering function. Registration adds an operation: function pair to ._operations. - It also adds an widget function attribute that masks the tkinter + It also adds a widget function attribute that masks the tkinter class instance method. Method masking operates independently from command dispatch. @@ -142,7 +143,8 @@ class OriginalCommand: self.orig_and_operation = (redir.orig, operation) def __repr__(self): - return "OriginalCommand(%r, %r)" % (self.redir, self.operation) + return "%s(%r, %r)" % (self.__class__.__name__, + self.redir, self.operation) def __call__(self, *args): return self.tk_call(self.orig_and_operation + args) diff --git a/Lib/idlelib/aboutDialog.py b/Lib/idlelib/aboutDialog.py index d876a97115..a8f75d2537 100644 --- a/Lib/idlelib/aboutDialog.py +++ b/Lib/idlelib/aboutDialog.py @@ -111,6 +111,7 @@ class AboutDialog(Toplevel): command=self.ShowIDLECredits) idle_credits_b.pack(side=LEFT, padx=10, pady=10) + # License, et all, are of type _sitebuiltins._Printer def ShowLicense(self): self.display_printer_text('About - License', license) @@ -120,14 +121,16 @@ class AboutDialog(Toplevel): def ShowPythonCredits(self): self.display_printer_text('About - Python Credits', credits) + # Encode CREDITS.txt to utf-8 for proper version of Loewis. + # Specify others as ascii until need utf-8, so catch errors. def ShowIDLECredits(self): - self.display_file_text('About - Credits', 'CREDITS.txt', 'iso-8859-1') + self.display_file_text('About - Credits', 'CREDITS.txt', 'utf-8') def ShowIDLEAbout(self): - self.display_file_text('About - Readme', 'README.txt') + self.display_file_text('About - Readme', 'README.txt', 'ascii') def ShowIDLENEWS(self): - self.display_file_text('About - NEWS', 'NEWS.txt') + self.display_file_text('About - NEWS', 'NEWS.txt', 'utf-8') def display_printer_text(self, title, printer): printer._Printer__setup() @@ -142,5 +145,7 @@ class AboutDialog(Toplevel): self.destroy() if __name__ == '__main__': + import unittest + unittest.main('idlelib.idle_test.test_helpabout', verbosity=2, exit=False) from idlelib.idle_test.htest import run run(AboutDialog) diff --git a/Lib/idlelib/configDialog.py b/Lib/idlelib/configDialog.py index 9b16459d54..5f5bd36f8a 100644 --- a/Lib/idlelib/configDialog.py +++ b/Lib/idlelib/configDialog.py @@ -483,6 +483,17 @@ class ConfigDialog(Toplevel): self.autoSave.trace_variable('w', self.VarChanged_autoSave) self.encoding.trace_variable('w', self.VarChanged_encoding) + def remove_var_callbacks(self): + "Remove callbacks to prevent memory leaks." + for var in ( + self.fontSize, self.fontName, self.fontBold, + self.spaceNum, self.colour, self.builtinTheme, + self.customTheme, self.themeIsBuiltin, self.highlightTarget, + self.keyBinding, self.builtinKeys, self.customKeys, + self.keysAreBuiltin, self.winWidth, self.winHeight, + self.startupEdit, self.autoSave, self.encoding,): + var.trace_vdelete('w', var.trace_vinfo()[0][1]) + def VarChanged_font(self, *params): '''When one font attribute changes, save them all, as they are not independent from each other. In particular, when we are @@ -740,6 +751,7 @@ class ConfigDialog(Toplevel): if not tkMessageBox.askyesno( 'Delete Key Set', delmsg % keySetName, parent=self): return + self.DeactivateCurrentConfig() #remove key set from config idleConf.userCfg['keys'].remove_section(keySetName) if keySetName in self.changedItems['keys']: @@ -758,7 +770,8 @@ class ConfigDialog(Toplevel): self.keysAreBuiltin.set(idleConf.defaultCfg['main'].Get('Keys', 'default')) self.builtinKeys.set(idleConf.defaultCfg['main'].Get('Keys', 'name')) #user can't back out of these changes, they must be applied now - self.Apply() + self.SaveAllChangedConfigs() + self.ActivateConfigChanges() self.SetKeysType() def DeleteCustomTheme(self): @@ -767,6 +780,7 @@ class ConfigDialog(Toplevel): if not tkMessageBox.askyesno( 'Delete Theme', delmsg % themeName, parent=self): return + self.DeactivateCurrentConfig() #remove theme from config idleConf.userCfg['highlight'].remove_section(themeName) if themeName in self.changedItems['highlight']: @@ -785,7 +799,8 @@ class ConfigDialog(Toplevel): self.themeIsBuiltin.set(idleConf.defaultCfg['main'].Get('Theme', 'default')) self.builtinTheme.set(idleConf.defaultCfg['main'].Get('Theme', 'name')) #user can't back out of these changes, they must be applied now - self.Apply() + self.SaveAllChangedConfigs() + self.ActivateConfigChanges() self.SetThemeType() def GetColour(self): @@ -1196,7 +1211,7 @@ class ConfigDialog(Toplevel): All values are treated as text, and it is up to the user to supply reasonable values. The only exception to this are the 'enable*' options, - which are boolean, and can be toggled with an True/False button. + which are boolean, and can be toggled with a True/False button. """ parent = self.parent frame = self.tabPages.pages['Extensions'].frame diff --git a/Lib/idlelib/configHandler.py b/Lib/idlelib/configHandler.py index 531efb4b3e..8954488fd6 100644 --- a/Lib/idlelib/configHandler.py +++ b/Lib/idlelib/configHandler.py @@ -720,7 +720,7 @@ class IdleConf: actualFont = Font.actual(f) family = actualFont['family'] size = actualFont['size'] - if size < 0: + if size <= 0: size = 10 # if font in pixels, ignore actual size bold = actualFont['weight']=='bold' return (family, size, 'bold' if bold else 'normal') @@ -740,21 +740,32 @@ class IdleConf: idleConf = IdleConf() # TODO Revise test output, write expanded unittest -### module test +# if __name__ == '__main__': + from zlib import crc32 + line, crc = 0, 0 + + def sprint(obj): + global line, crc + txt = str(obj) + line += 1 + crc = crc32(txt.encode(encoding='utf-8'), crc) + print(txt) + #print('***', line, crc, '***') # uncomment for diagnosis + def dumpCfg(cfg): - print('\n', cfg, '\n') - for key in cfg: + print('\n', cfg, '\n') # has variable '0xnnnnnnnn' addresses + for key in sorted(cfg.keys()): sections = cfg[key].sections() - print(key) - print(sections) + sprint(key) + sprint(sections) for section in sections: options = cfg[key].options(section) - print(section) - print(options) + sprint(section) + sprint(options) for option in options: - print(option, '=', cfg[key].Get(section, option)) + sprint(option + ' = ' + cfg[key].Get(section, option)) + dumpCfg(idleConf.defaultCfg) dumpCfg(idleConf.userCfg) - print(idleConf.userCfg['main'].Get('Theme', 'name')) - #print idleConf.userCfg['highlight'].GetDefHighlight('Foo','normal') + print('\nlines = ', line, ', crc = ', crc, sep='') diff --git a/Lib/idlelib/configHelpSourceEdit.py b/Lib/idlelib/configHelpSourceEdit.py index 242b08db56..cde8118fe6 100644 --- a/Lib/idlelib/configHelpSourceEdit.py +++ b/Lib/idlelib/configHelpSourceEdit.py @@ -23,10 +23,10 @@ class GetHelpSourceDialog(Toplevel): self.title(title) self.transient(parent) self.grab_set() - self.protocol("WM_DELETE_WINDOW", self.Cancel) + self.protocol("WM_DELETE_WINDOW", self.cancel) self.parent = parent self.result = None - self.CreateWidgets() + self.create_widgets() self.menu.set(menuItem) self.path.set(filePath) self.withdraw() #hide while setting geometry @@ -41,10 +41,10 @@ class GetHelpSourceDialog(Toplevel): ((parent.winfo_height()/2 - self.winfo_reqheight()/2) if not _htest else 150))) self.deiconify() #geometry set, unhide - self.bind('<Return>', self.Ok) + self.bind('<Return>', self.ok) self.wait_window() - def CreateWidgets(self): + def create_widgets(self): self.menu = StringVar(self) self.path = StringVar(self) self.fontSize = StringVar(self) @@ -65,18 +65,18 @@ class GetHelpSourceDialog(Toplevel): labelPath.pack(anchor=W, padx=5, pady=3) self.entryPath.pack(anchor=W, padx=5, pady=3) browseButton = Button(self.frameMain, text='Browse', width=8, - command=self.browseFile) + command=self.browse_file) browseButton.pack(pady=3) frameButtons = Frame(self) frameButtons.pack(side=BOTTOM, fill=X) self.buttonOk = Button(frameButtons, text='OK', - width=8, default=ACTIVE, command=self.Ok) + width=8, default=ACTIVE, command=self.ok) self.buttonOk.grid(row=0, column=0, padx=5,pady=5) self.buttonCancel = Button(frameButtons, text='Cancel', - width=8, command=self.Cancel) + width=8, command=self.cancel) self.buttonCancel.grid(row=0, column=1, padx=5, pady=5) - def browseFile(self): + def browse_file(self): filetypes = [ ("HTML Files", "*.htm *.html", "TEXT"), ("PDF Files", "*.pdf", "TEXT"), @@ -99,9 +99,9 @@ class GetHelpSourceDialog(Toplevel): if file: self.path.set(file) - def MenuOk(self): + def menu_ok(self): "Simple validity check for a sensible menu item name" - menuOk = True + menu_ok = True menu = self.menu.get() menu.strip() if not menu: @@ -109,19 +109,19 @@ class GetHelpSourceDialog(Toplevel): message='No menu item specified', parent=self) self.entryMenu.focus_set() - menuOk = False + menu_ok = False elif len(menu) > 30: tkMessageBox.showerror(title='Menu Item Error', message='Menu item too long:' '\nLimit 30 characters.', parent=self) self.entryMenu.focus_set() - menuOk = False - return menuOk + menu_ok = False + return menu_ok - def PathOk(self): + def path_ok(self): "Simple validity check for menu file path" - pathOk = True + path_ok = True path = self.path.get() path.strip() if not path: #no path specified @@ -129,7 +129,7 @@ class GetHelpSourceDialog(Toplevel): message='No help file path specified.', parent=self) self.entryPath.focus_set() - pathOk = False + path_ok = False elif path.startswith(('www.', 'http')): pass else: @@ -140,16 +140,16 @@ class GetHelpSourceDialog(Toplevel): message='Help file path does not exist.', parent=self) self.entryPath.focus_set() - pathOk = False - return pathOk + path_ok = False + return path_ok - def Ok(self, event=None): - if self.MenuOk() and self.PathOk(): + def ok(self, event=None): + if self.menu_ok() and self.path_ok(): self.result = (self.menu.get().strip(), self.path.get().strip()) if sys.platform == 'darwin': path = self.result[1] - if path.startswith(('www', 'file:', 'http:')): + if path.startswith(('www', 'file:', 'http:', 'https:')): pass else: # Mac Safari insists on using the URI form for local files @@ -157,10 +157,14 @@ class GetHelpSourceDialog(Toplevel): self.result[1] = "file://" + path self.destroy() - def Cancel(self, event=None): + def cancel(self, event=None): self.result = None self.destroy() if __name__ == '__main__': + import unittest + unittest.main('idlelib.idle_test.test_config_help', + verbosity=2, exit=False) + from idlelib.idle_test.htest import run run(GetHelpSourceDialog) diff --git a/Lib/idlelib/help.html b/Lib/idlelib/help.html index 2189fd4cf0..7860bfbc21 100644 --- a/Lib/idlelib/help.html +++ b/Lib/idlelib/help.html @@ -6,7 +6,7 @@ <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> - <title>25.5. IDLE — Python 3.4.3 documentation</title> + <title>25.5. IDLE — Python 3.5.2 documentation</title> <link rel="stylesheet" href="../_static/pydoctheme.css" type="text/css" /> <link rel="stylesheet" href="../_static/pygments.css" type="text/css" /> @@ -14,7 +14,7 @@ <script type="text/javascript"> var DOCUMENTATION_OPTIONS = { URL_ROOT: '../', - VERSION: '3.4.3', + VERSION: '3.5.2', COLLAPSE_INDEX: false, FILE_SUFFIX: '.html', HAS_SOURCE: true @@ -25,11 +25,11 @@ <script type="text/javascript" src="../_static/doctools.js"></script> <script type="text/javascript" src="../_static/sidebar.js"></script> <link rel="search" type="application/opensearchdescription+xml" - title="Search within Python 3.4.3 documentation" + title="Search within Python 3.5.2 documentation" href="../_static/opensearch.xml"/> <link rel="author" title="About these documents" href="../about.html" /> <link rel="copyright" title="Copyright" href="../copyright.html" /> - <link rel="top" title="Python 3.4.3 documentation" href="../index.html" /> + <link rel="top" title="Python 3.5.2 documentation" href="../contents.html" /> <link rel="up" title="25. Graphical User Interfaces with Tk" href="tk.html" /> <link rel="next" title="25.6. Other Graphical User Interface Packages" href="othergui.html" /> <link rel="prev" title="25.4. tkinter.scrolledtext — Scrolled Text Widget" href="tkinter.scrolledtext.html" /> @@ -40,8 +40,8 @@ </head> - <body> - <div class="related"> + <body role="document"> + <div class="related" role="navigation" aria-label="related navigation"> <h3>Navigation</h3> <ul> <li class="right" style="margin-right: 10px"> @@ -60,25 +60,27 @@ style="vertical-align: middle; margin-top: -1px"/></li> <li><a href="https://www.python.org/">Python</a> »</li> <li> - <a href="../index.html">3.4.3 Documentation</a> » + <a href="../index.html">3.5.2 Documentation</a> » </li> - <li><a href="index.html" >The Python Standard Library</a> »</li> - <li><a href="tk.html" accesskey="U">25. Graphical User Interfaces with Tk</a> »</li> + <li class="nav-item nav-item-1"><a href="index.html" >The Python Standard Library</a> »</li> + <li class="nav-item nav-item-2"><a href="tk.html" accesskey="U">25. Graphical User Interfaces with Tk</a> »</li> </ul> </div> <div class="document"> <div class="documentwrapper"> <div class="bodywrapper"> - <div class="body"> + <div class="body" role="main"> <div class="section" id="idle"> <span id="id1"></span><h1>25.5. IDLE<a class="headerlink" href="#idle" title="Permalink to this headline">¶</a></h1> -<p id="index-0">IDLE is Python’s Integrated Development and Learning Environment.</p> +<p><strong>Source code:</strong> <a class="reference external" href="https://hg.python.org/cpython/file/3.5/Lib/idlelib/">Lib/idlelib/</a></p> +<hr class="docutils" id="index-0" /> +<p>IDLE is Python’s Integrated Development and Learning Environment.</p> <p>IDLE has the following features:</p> <ul class="simple"> -<li>coded in 100% pure Python, using the <a class="reference internal" href="tkinter.html#module-tkinter" title="tkinter: Interface to Tcl/Tk for graphical user interfaces"><tt class="xref py py-mod docutils literal"><span class="pre">tkinter</span></tt></a> GUI toolkit</li> +<li>coded in 100% pure Python, using the <a class="reference internal" href="tkinter.html#module-tkinter" title="tkinter: Interface to Tcl/Tk for graphical user interfaces"><code class="xref py py-mod docutils literal"><span class="pre">tkinter</span></code></a> GUI toolkit</li> <li>cross-platform: works mostly the same on Windows, Unix, and Mac OS X</li> <li>Python shell window (interactive interpreter) with colorizing of code input, output, and error messages</li> @@ -163,7 +165,7 @@ be undone.</dd> <dt>Find Selection</dt> <dd>Search for the currently selected string, if there is one.</dd> <dt>Find in Files...</dt> -<dd>Open a file search dialog. Put results in an new output window.</dd> +<dd>Open a file search dialog. Put results in a new output window.</dd> <dt>Replace...</dt> <dd>Open a search-and-replace dialog.</dd> <dt>Go to Line</dt> @@ -224,10 +226,10 @@ Editor window.</dd> <dt>Run Module</dt> <dd>Do Check Module (above). If no error, restart the shell to clean the environment, then execute the module. Output is displayed in the Shell -window. Note that output requires use of <tt class="docutils literal"><span class="pre">print</span></tt> or <tt class="docutils literal"><span class="pre">write</span></tt>. +window. Note that output requires use of <code class="docutils literal"><span class="pre">print</span></code> or <code class="docutils literal"><span class="pre">write</span></code>. When execution is complete, the Shell retains focus and displays a prompt. At this point, one may interactively explore the result of execution. -This is similar to executing a file with <tt class="docutils literal"><span class="pre">python</span> <span class="pre">-i</span> <span class="pre">file</span></tt> at a command +This is similar to executing a file with <code class="docutils literal"><span class="pre">python</span> <span class="pre">-i</span> <span class="pre">file</span></code> at a command line.</dd> </dl> </div> @@ -339,47 +341,47 @@ debugger. Breakpoints for a file are saved in the user’s .idlerc director </div> <div class="section" id="editing-and-navigation"> <h2>25.5.2. Editing and navigation<a class="headerlink" href="#editing-and-navigation" title="Permalink to this headline">¶</a></h2> -<p>In this section, ‘C’ refers to the <tt class="kbd docutils literal"><span class="pre">Control</span></tt> key on Windows and Unix and -the <tt class="kbd docutils literal"><span class="pre">Command</span></tt> key on Mac OSX.</p> +<p>In this section, ‘C’ refers to the <code class="kbd docutils literal"><span class="pre">Control</span></code> key on Windows and Unix and +the <code class="kbd docutils literal"><span class="pre">Command</span></code> key on Mac OSX.</p> <ul> -<li><p class="first"><tt class="kbd docutils literal"><span class="pre">Backspace</span></tt> deletes to the left; <tt class="kbd docutils literal"><span class="pre">Del</span></tt> deletes to the right</p> +<li><p class="first"><code class="kbd docutils literal"><span class="pre">Backspace</span></code> deletes to the left; <code class="kbd docutils literal"><span class="pre">Del</span></code> deletes to the right</p> </li> -<li><p class="first"><tt class="kbd docutils literal"><span class="pre">C-Backspace</span></tt> delete word left; <tt class="kbd docutils literal"><span class="pre">C-Del</span></tt> delete word to the right</p> +<li><p class="first"><code class="kbd docutils literal"><span class="pre">C-Backspace</span></code> delete word left; <code class="kbd docutils literal"><span class="pre">C-Del</span></code> delete word to the right</p> </li> -<li><p class="first">Arrow keys and <tt class="kbd docutils literal"><span class="pre">Page</span> <span class="pre">Up</span></tt>/<tt class="kbd docutils literal"><span class="pre">Page</span> <span class="pre">Down</span></tt> to move around</p> +<li><p class="first">Arrow keys and <code class="kbd docutils literal"><span class="pre">Page</span> <span class="pre">Up</span></code>/<code class="kbd docutils literal"><span class="pre">Page</span> <span class="pre">Down</span></code> to move around</p> </li> -<li><p class="first"><tt class="kbd docutils literal"><span class="pre">C-LeftArrow</span></tt> and <tt class="kbd docutils literal"><span class="pre">C-RightArrow</span></tt> moves by words</p> +<li><p class="first"><code class="kbd docutils literal"><span class="pre">C-LeftArrow</span></code> and <code class="kbd docutils literal"><span class="pre">C-RightArrow</span></code> moves by words</p> </li> -<li><p class="first"><tt class="kbd docutils literal"><span class="pre">Home</span></tt>/<tt class="kbd docutils literal"><span class="pre">End</span></tt> go to begin/end of line</p> +<li><p class="first"><code class="kbd docutils literal"><span class="pre">Home</span></code>/<code class="kbd docutils literal"><span class="pre">End</span></code> go to begin/end of line</p> </li> -<li><p class="first"><tt class="kbd docutils literal"><span class="pre">C-Home</span></tt>/<tt class="kbd docutils literal"><span class="pre">C-End</span></tt> go to begin/end of file</p> +<li><p class="first"><code class="kbd docutils literal"><span class="pre">C-Home</span></code>/<code class="kbd docutils literal"><span class="pre">C-End</span></code> go to begin/end of file</p> </li> <li><p class="first">Some useful Emacs bindings are inherited from Tcl/Tk:</p> <blockquote> <div><ul class="simple"> -<li><tt class="kbd docutils literal"><span class="pre">C-a</span></tt> beginning of line</li> -<li><tt class="kbd docutils literal"><span class="pre">C-e</span></tt> end of line</li> -<li><tt class="kbd docutils literal"><span class="pre">C-k</span></tt> kill line (but doesn’t put it in clipboard)</li> -<li><tt class="kbd docutils literal"><span class="pre">C-l</span></tt> center window around the insertion point</li> -<li><tt class="kbd docutils literal"><span class="pre">C-b</span></tt> go backwards one character without deleting (usually you can +<li><code class="kbd docutils literal"><span class="pre">C-a</span></code> beginning of line</li> +<li><code class="kbd docutils literal"><span class="pre">C-e</span></code> end of line</li> +<li><code class="kbd docutils literal"><span class="pre">C-k</span></code> kill line (but doesn’t put it in clipboard)</li> +<li><code class="kbd docutils literal"><span class="pre">C-l</span></code> center window around the insertion point</li> +<li><code class="kbd docutils literal"><span class="pre">C-b</span></code> go backwards one character without deleting (usually you can also use the cursor key for this)</li> -<li><tt class="kbd docutils literal"><span class="pre">C-f</span></tt> go forward one character without deleting (usually you can +<li><code class="kbd docutils literal"><span class="pre">C-f</span></code> go forward one character without deleting (usually you can also use the cursor key for this)</li> -<li><tt class="kbd docutils literal"><span class="pre">C-p</span></tt> go up one line (usually you can also use the cursor key for +<li><code class="kbd docutils literal"><span class="pre">C-p</span></code> go up one line (usually you can also use the cursor key for this)</li> -<li><tt class="kbd docutils literal"><span class="pre">C-d</span></tt> delete next character</li> +<li><code class="kbd docutils literal"><span class="pre">C-d</span></code> delete next character</li> </ul> </div></blockquote> </li> </ul> -<p>Standard keybindings (like <tt class="kbd docutils literal"><span class="pre">C-c</span></tt> to copy and <tt class="kbd docutils literal"><span class="pre">C-v</span></tt> to paste) +<p>Standard keybindings (like <code class="kbd docutils literal"><span class="pre">C-c</span></code> to copy and <code class="kbd docutils literal"><span class="pre">C-v</span></code> to paste) may work. Keybindings are selected in the Configure IDLE dialog.</p> <div class="section" id="automatic-indentation"> <h3>25.5.2.1. Automatic indentation<a class="headerlink" href="#automatic-indentation" title="Permalink to this headline">¶</a></h3> <p>After a block-opening statement, the next line is indented by 4 spaces (in the Python Shell window by one tab). After certain keywords (break, return etc.) -the next line is dedented. In leading indentation, <tt class="kbd docutils literal"><span class="pre">Backspace</span></tt> deletes up -to 4 spaces if they are there. <tt class="kbd docutils literal"><span class="pre">Tab</span></tt> inserts spaces (in the Python +the next line is dedented. In leading indentation, <code class="kbd docutils literal"><span class="pre">Backspace</span></code> deletes up +to 4 spaces if they are there. <code class="kbd docutils literal"><span class="pre">Tab</span></code> inserts spaces (in the Python Shell window one tab), number depends on Indent width. Currently tabs are restricted to four spaces due to Tcl/Tk limitations.</p> <p>See also the indent/dedent region commands in the edit menu.</p> @@ -394,25 +396,25 @@ two seconds) after a ‘.’ or (in a string) an os.sep is typed. If aft of those characters (plus zero or more other characters) a tab is typed the ACW will open immediately if a possible continuation is found.</p> <p>If there is only one possible completion for the characters entered, a -<tt class="kbd docutils literal"><span class="pre">Tab</span></tt> will supply that completion without opening the ACW.</p> +<code class="kbd docutils literal"><span class="pre">Tab</span></code> will supply that completion without opening the ACW.</p> <p>‘Show Completions’ will force open a completions window, by default the -<tt class="kbd docutils literal"><span class="pre">C-space</span></tt> will open a completions window. In an empty +<code class="kbd docutils literal"><span class="pre">C-space</span></code> will open a completions window. In an empty string, this will contain the files in the current directory. On a blank line, it will contain the built-in and user-defined functions and classes in the current name spaces, plus any modules imported. If some characters have been entered, the ACW will attempt to be more specific.</p> <p>If a string of characters is typed, the ACW selection will jump to the -entry most closely matching those characters. Entering a <tt class="kbd docutils literal"><span class="pre">tab</span></tt> will +entry most closely matching those characters. Entering a <code class="kbd docutils literal"><span class="pre">tab</span></code> will cause the longest non-ambiguous match to be entered in the Editor window or -Shell. Two <tt class="kbd docutils literal"><span class="pre">tab</span></tt> in a row will supply the current ACW selection, as +Shell. Two <code class="kbd docutils literal"><span class="pre">tab</span></code> in a row will supply the current ACW selection, as will return or a double click. Cursor keys, Page Up/Down, mouse selection, and the scroll wheel all operate on the ACW.</p> <p>“Hidden” attributes can be accessed by typing the beginning of hidden name after a ‘.’, e.g. ‘_’. This allows access to modules with -<tt class="docutils literal"><span class="pre">__all__</span></tt> set, or to class-private attributes.</p> +<code class="docutils literal"><span class="pre">__all__</span></code> set, or to class-private attributes.</p> <p>Completions and the ‘Expand Word’ facility can save a lot of typing!</p> <p>Completions are currently limited to those in the namespaces. Names in -an Editor window which are not via <tt class="docutils literal"><span class="pre">__main__</span></tt> and <a class="reference internal" href="sys.html#sys.modules" title="sys.modules"><tt class="xref py py-data docutils literal"><span class="pre">sys.modules</span></tt></a> will +an Editor window which are not via <code class="docutils literal"><span class="pre">__main__</span></code> and <a class="reference internal" href="sys.html#sys.modules" title="sys.modules"><code class="xref py py-data docutils literal"><span class="pre">sys.modules</span></code></a> will not be found. Run the module once with your imports to correct this situation. Note that IDLE itself places quite a few modules in sys.modules, so much can be found by default, e.g. the re module.</p> @@ -421,10 +423,10 @@ longer or disable the extension.</p> </div> <div class="section" id="calltips"> <h3>25.5.2.3. Calltips<a class="headerlink" href="#calltips" title="Permalink to this headline">¶</a></h3> -<p>A calltip is shown when one types <tt class="kbd docutils literal"><span class="pre">(</span></tt> after the name of an <em>acccessible</em> +<p>A calltip is shown when one types <code class="kbd docutils literal"><span class="pre">(</span></code> after the name of an <em>acccessible</em> function. A name expression may include dots and subscripts. A calltip remains until it is clicked, the cursor is moved out of the argument area, -or <tt class="kbd docutils literal"><span class="pre">)</span></tt> is typed. When the cursor is in the argument part of a definition, +or <code class="kbd docutils literal"><span class="pre">)</span></code> is typed. When the cursor is in the argument part of a definition, the menu or shortcut display a calltip.</p> <p>A calltip consists of the function signature and the first line of the docstring. For builtins without an accessible signature, the calltip @@ -433,11 +435,11 @@ details may change.</p> <p>The set of <em>accessible</em> functions depends on what modules have been imported into the user process, including those imported by Idle itself, and what definitions have been run, all since the last restart.</p> -<p>For example, restart the Shell and enter <tt class="docutils literal"><span class="pre">itertools.count(</span></tt>. A calltip +<p>For example, restart the Shell and enter <code class="docutils literal"><span class="pre">itertools.count(</span></code>. A calltip appears because Idle imports itertools into the user process for its own use. -(This could change.) Enter <tt class="docutils literal"><span class="pre">turtle.write(</span></tt> and nothing appears. Idle does +(This could change.) Enter <code class="docutils literal"><span class="pre">turtle.write(</span></code> and nothing appears. Idle does not import turtle. The menu or shortcut do nothing either. Enter -<tt class="docutils literal"><span class="pre">import</span> <span class="pre">turtle</span></tt> and then <tt class="docutils literal"><span class="pre">turtle.write(</span></tt> will work.</p> +<code class="docutils literal"><span class="pre">import</span> <span class="pre">turtle</span></code> and then <code class="docutils literal"><span class="pre">turtle.write(</span></code> will work.</p> <p>In an editor, import statements have no effect until one runs the file. One might want to run a file after writing the import statements at the top, or immediately run an existing file before editing.</p> @@ -445,17 +447,17 @@ or immediately run an existing file before editing.</p> <div class="section" id="python-shell-window"> <h3>25.5.2.4. Python Shell window<a class="headerlink" href="#python-shell-window" title="Permalink to this headline">¶</a></h3> <ul> -<li><p class="first"><tt class="kbd docutils literal"><span class="pre">C-c</span></tt> interrupts executing command</p> +<li><p class="first"><code class="kbd docutils literal"><span class="pre">C-c</span></code> interrupts executing command</p> </li> -<li><p class="first"><tt class="kbd docutils literal"><span class="pre">C-d</span></tt> sends end-of-file; closes window if typed at a <tt class="docutils literal"><span class="pre">>>></span></tt> prompt</p> +<li><p class="first"><code class="kbd docutils literal"><span class="pre">C-d</span></code> sends end-of-file; closes window if typed at a <code class="docutils literal"><span class="pre">>>></span></code> prompt</p> </li> -<li><p class="first"><tt class="kbd docutils literal"><span class="pre">Alt-/</span></tt> (Expand word) is also useful to reduce typing</p> +<li><p class="first"><code class="kbd docutils literal"><span class="pre">Alt-/</span></code> (Expand word) is also useful to reduce typing</p> <p>Command history</p> <ul class="simple"> -<li><tt class="kbd docutils literal"><span class="pre">Alt-p</span></tt> retrieves previous command matching what you have typed. On -OS X use <tt class="kbd docutils literal"><span class="pre">C-p</span></tt>.</li> -<li><tt class="kbd docutils literal"><span class="pre">Alt-n</span></tt> retrieves next. On OS X use <tt class="kbd docutils literal"><span class="pre">C-n</span></tt>.</li> -<li><tt class="kbd docutils literal"><span class="pre">Return</span></tt> while on any previous command retrieves that command</li> +<li><code class="kbd docutils literal"><span class="pre">Alt-p</span></code> retrieves previous command matching what you have typed. On +OS X use <code class="kbd docutils literal"><span class="pre">C-p</span></code>.</li> +<li><code class="kbd docutils literal"><span class="pre">Alt-n</span></code> retrieves next. On OS X use <code class="kbd docutils literal"><span class="pre">C-n</span></code>.</li> +<li><code class="kbd docutils literal"><span class="pre">Return</span></code> while on any previous command retrieves that command</li> </ul> </li> </ul> @@ -465,8 +467,8 @@ OS X use <tt class="kbd docutils literal"><span class="pre">C-p</span></tt>.</li <p>Idle defaults to black on white text, but colors text with special meanings. For the shell, these are shell output, shell error, user output, and user error. For Python code, at the shell prompt or in an editor, these are -keywords, builtin class and function names, names following <tt class="docutils literal"><span class="pre">class</span></tt> and -<tt class="docutils literal"><span class="pre">def</span></tt>, strings, and comments. For any text window, these are the cursor (when +keywords, builtin class and function names, names following <code class="docutils literal"><span class="pre">class</span></code> and +<code class="docutils literal"><span class="pre">def</span></code>, strings, and comments. For any text window, these are the cursor (when present), found text (when possible), and selected text.</p> <p>Text coloring is done in the background, so uncolorized text is occasionally visible. To change the color scheme, use the Configure IDLE dialog @@ -476,26 +478,26 @@ text in popups and dialogs is not user-configurable.</p> </div> <div class="section" id="startup-and-code-execution"> <h2>25.5.3. Startup and code execution<a class="headerlink" href="#startup-and-code-execution" title="Permalink to this headline">¶</a></h2> -<p>Upon startup with the <tt class="docutils literal"><span class="pre">-s</span></tt> option, IDLE will execute the file referenced by -the environment variables <span class="target" id="index-5"></span><tt class="xref std std-envvar docutils literal"><span class="pre">IDLESTARTUP</span></tt> or <span class="target" id="index-6"></span><a class="reference internal" href="../using/cmdline.html#envvar-PYTHONSTARTUP"><tt class="xref std std-envvar docutils literal"><span class="pre">PYTHONSTARTUP</span></tt></a>. -IDLE first checks for <tt class="docutils literal"><span class="pre">IDLESTARTUP</span></tt>; if <tt class="docutils literal"><span class="pre">IDLESTARTUP</span></tt> is present the file -referenced is run. If <tt class="docutils literal"><span class="pre">IDLESTARTUP</span></tt> is not present, IDLE checks for -<tt class="docutils literal"><span class="pre">PYTHONSTARTUP</span></tt>. Files referenced by these environment variables are +<p>Upon startup with the <code class="docutils literal"><span class="pre">-s</span></code> option, IDLE will execute the file referenced by +the environment variables <span class="target" id="index-5"></span><code class="xref std std-envvar docutils literal"><span class="pre">IDLESTARTUP</span></code> or <span class="target" id="index-6"></span><a class="reference internal" href="../using/cmdline.html#envvar-PYTHONSTARTUP"><code class="xref std std-envvar docutils literal"><span class="pre">PYTHONSTARTUP</span></code></a>. +IDLE first checks for <code class="docutils literal"><span class="pre">IDLESTARTUP</span></code>; if <code class="docutils literal"><span class="pre">IDLESTARTUP</span></code> is present the file +referenced is run. If <code class="docutils literal"><span class="pre">IDLESTARTUP</span></code> is not present, IDLE checks for +<code class="docutils literal"><span class="pre">PYTHONSTARTUP</span></code>. Files referenced by these environment variables are convenient places to store functions that are used frequently from the IDLE shell, or for executing import statements to import common modules.</p> -<p>In addition, <tt class="docutils literal"><span class="pre">Tk</span></tt> also loads a startup file if it is present. Note that the -Tk file is loaded unconditionally. This additional file is <tt class="docutils literal"><span class="pre">.Idle.py</span></tt> and is +<p>In addition, <code class="docutils literal"><span class="pre">Tk</span></code> also loads a startup file if it is present. Note that the +Tk file is loaded unconditionally. This additional file is <code class="docutils literal"><span class="pre">.Idle.py</span></code> and is looked for in the user’s home directory. Statements in this file will be executed in the Tk namespace, so this file is not useful for importing functions to be used from IDLE’s Python shell.</p> <div class="section" id="command-line-usage"> <h3>25.5.3.1. Command line usage<a class="headerlink" href="#command-line-usage" title="Permalink to this headline">¶</a></h3> -<div class="highlight-python3"><div class="highlight"><pre>idle.py [-c command] [-d] [-e] [-h] [-i] [-r file] [-s] [-t title] [-] [arg] ... +<div class="highlight-none"><div class="highlight"><pre><span></span>idle.py [-c command] [-d] [-e] [-h] [-i] [-r file] [-s] [-t title] [-] [arg] ... -c command run command in the shell window -d enable debugger and open shell window -e open editor window --h print help message with legal combinatios and exit +-h print help message with legal combinations and exit -i open shell window -r file run file in shell window -s run $IDLESTARTUP or $PYTHONSTARTUP first, in shell window @@ -505,27 +507,31 @@ functions to be used from IDLE’s Python shell.</p> </div> <p>If there are arguments:</p> <ul class="simple"> -<li>If <tt class="docutils literal"><span class="pre">-</span></tt>, <tt class="docutils literal"><span class="pre">-c</span></tt>, or <tt class="docutils literal"><span class="pre">r</span></tt> is used, all arguments are placed in -<tt class="docutils literal"><span class="pre">sys.argv[1:...]</span></tt> and <tt class="docutils literal"><span class="pre">sys.argv[0]</span></tt> is set to <tt class="docutils literal"><span class="pre">''</span></tt>, <tt class="docutils literal"><span class="pre">'-c'</span></tt>, -or <tt class="docutils literal"><span class="pre">'-r'</span></tt>. No editor window is opened, even if that is the default +<li>If <code class="docutils literal"><span class="pre">-</span></code>, <code class="docutils literal"><span class="pre">-c</span></code>, or <code class="docutils literal"><span class="pre">r</span></code> is used, all arguments are placed in +<code class="docutils literal"><span class="pre">sys.argv[1:...]</span></code> and <code class="docutils literal"><span class="pre">sys.argv[0]</span></code> is set to <code class="docutils literal"><span class="pre">''</span></code>, <code class="docutils literal"><span class="pre">'-c'</span></code>, +or <code class="docutils literal"><span class="pre">'-r'</span></code>. No editor window is opened, even if that is the default set in the Options dialog.</li> <li>Otherwise, arguments are files opened for editing and -<tt class="docutils literal"><span class="pre">sys.argv</span></tt> reflects the arguments passed to IDLE itself.</li> +<code class="docutils literal"><span class="pre">sys.argv</span></code> reflects the arguments passed to IDLE itself.</li> </ul> </div> <div class="section" id="idle-console-differences"> <h3>25.5.3.2. IDLE-console differences<a class="headerlink" href="#idle-console-differences" title="Permalink to this headline">¶</a></h3> <p>As much as possible, the result of executing Python code with IDLE is the same as executing the same code in a console window. However, the different -interface and operation occasionally affects results.</p> -<p>For instance, IDLE normally executes user code in a separate process from -the IDLE GUI itself. The IDLE versions of sys.stdin, .stdout, and .stderr in the -execution process get input from and send output to the GUI process, -which keeps control of the keyboard and screen. This is normally transparent, -but code that access these object will see different attribute values. -Also, functions that directly access the keyboard and screen will not work.</p> +interface and operation occasionally affects visible results. For instance, +<code class="docutils literal"><span class="pre">sys.modules</span></code> starts with more entries.</p> +<p>IDLE also replaces <code class="docutils literal"><span class="pre">sys.stdin</span></code>, <code class="docutils literal"><span class="pre">sys.stdout</span></code>, and <code class="docutils literal"><span class="pre">sys.stderr</span></code> with +objects that get input from and send output to the Shell window. +When this window has the focus, it controls the keyboard and screen. +This is normally transparent, but functions that directly access the keyboard +and screen will not work. If <code class="docutils literal"><span class="pre">sys</span></code> is reset with <code class="docutils literal"><span class="pre">importlib.reload(sys)</span></code>, +IDLE’s changes are lost and things like <code class="docutils literal"><span class="pre">input</span></code>, <code class="docutils literal"><span class="pre">raw_input</span></code>, and +<code class="docutils literal"><span class="pre">print</span></code> will not work correctly.</p> <p>With IDLE’s Shell, one enters, edits, and recalls complete statements. -Some consoles only work with a single physical line at a time.</p> +Some consoles only work with a single physical line at a time. IDLE uses +<code class="docutils literal"><span class="pre">exec</span></code> to run each statement. As a result, <code class="docutils literal"><span class="pre">'__builtins__'</span></code> is always +defined for each statement.</p> </div> <div class="section" id="running-without-a-subprocess"> <h3>25.5.3.3. Running without a subprocess<a class="headerlink" href="#running-without-a-subprocess" title="Permalink to this headline">¶</a></h3> @@ -595,7 +601,7 @@ are currently:</p> </div> </div> </div> - <div class="sphinxsidebar"> + <div class="sphinxsidebar" role="navigation" aria-label="main navigation"> <div class="sphinxsidebarwrapper"> <h3><a href="../contents.html">Table Of Contents</a></h3> <ul> @@ -639,7 +645,7 @@ are currently:</p> <h4>Previous topic</h4> <p class="topless"><a href="tkinter.scrolledtext.html" - title="previous chapter">25.4. <tt class="docutils literal"><span class="pre">tkinter.scrolledtext</span></tt> — Scrolled Text Widget</a></p> + title="previous chapter">25.4. <code class="docutils literal"><span class="pre">tkinter.scrolledtext</span></code> — Scrolled Text Widget</a></p> <h4>Next topic</h4> <p class="topless"><a href="othergui.html" title="next chapter">25.6. Other Graphical User Interface Packages</a></p> @@ -650,7 +656,7 @@ are currently:</p> rel="nofollow">Show Source</a></li> </ul> -<div id="searchbox" style="display: none"> +<div id="searchbox" style="display: none" role="search"> <h3>Quick search</h3> <form class="search" action="../search.html" method="get"> <input type="text" name="q" /> @@ -667,7 +673,7 @@ are currently:</p> </div> <div class="clearer"></div> </div> - <div class="related"> + <div class="related" role="navigation" aria-label="related navigation"> <h3>Navigation</h3> <ul> <li class="right" style="margin-right: 10px"> @@ -686,23 +692,23 @@ are currently:</p> style="vertical-align: middle; margin-top: -1px"/></li> <li><a href="https://www.python.org/">Python</a> »</li> <li> - <a href="../index.html">3.4.3 Documentation</a> » + <a href="../index.html">3.5.2 Documentation</a> » </li> - <li><a href="index.html" >The Python Standard Library</a> »</li> - <li><a href="tk.html" >25. Graphical User Interfaces with Tk</a> »</li> + <li class="nav-item nav-item-1"><a href="index.html" >The Python Standard Library</a> »</li> + <li class="nav-item nav-item-2"><a href="tk.html" >25. Graphical User Interfaces with Tk</a> »</li> </ul> </div> <div class="footer"> - © <a href="../copyright.html">Copyright</a> 1990-2015, Python Software Foundation. + © <a href="../copyright.html">Copyright</a> 2001-2016, Python Software Foundation. <br /> The Python Software Foundation is a non-profit corporation. <a href="https://www.python.org/psf/donations/">Please donate.</a> <br /> - Last updated on Oct 13, 2015. + Last updated on Aug 30, 2016. <a href="../bugs.html">Found a bug</a>? <br /> - Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.2.3. + Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.3.6. </div> </body> diff --git a/Lib/idlelib/help.py b/Lib/idlelib/help.py index b31596c2eb..a7008e94ed 100644 --- a/Lib/idlelib/help.py +++ b/Lib/idlelib/help.py @@ -11,7 +11,7 @@ Help => IDLE Help: Display help.html with proper formatting. Doc/library/idle.rst (Sphinx)=> Doc/build/html/library/idle.html (help.copy_strip)=> Lib/idlelib/help.html -HelpParser - Parse help.html and and render to tk Text. +HelpParser - Parse help.html and render to tk Text. HelpText - Display formatted help.html. @@ -26,6 +26,7 @@ show_idlehelp - Create HelpWindow. Called in EditorWindow.help_dialog. """ from html.parser import HTMLParser from os.path import abspath, dirname, isdir, isfile, join +from platform import python_version from tkinter import Tk, Toplevel, Frame, Text, Scrollbar, Menu, Menubutton from tkinter import font as tkfont from idlelib.configHandler import idleConf @@ -45,6 +46,8 @@ class HelpParser(HTMLParser): The overridden handle_xyz methods handle a subset of html tags. The supplied text should have the needed tag configurations. The behavior for unsupported tags, such as table, is undefined. + If the tags generated by Sphinx change, this class, especially + the handle_starttag and handle_endtags methods, might have to also. """ def __init__(self, text): HTMLParser.__init__(self, convert_charrefs=True) @@ -226,7 +229,28 @@ class HelpWindow(Toplevel): def copy_strip(): - "Copy idle.html to idlelib/help.html, stripping trailing whitespace." + """Copy idle.html to idlelib/help.html, stripping trailing whitespace. + + Files with trailing whitespace cannot be pushed to the hg cpython + repository. For 3.x (on Windows), help.html is generated, after + editing idle.rst in the earliest maintenance version, with + sphinx-build -bhtml . build/html + python_d.exe -c "from idlelib.help import copy_strip; copy_strip()" + After refreshing TortoiseHG workshop to generate a diff, + check both the diff and displayed text. Push the diff along with + the idle.rst change and merge both into default (or an intermediate + maintenance version). + + When the 'earlist' version gets its final maintenance release, + do an update as described above, without editing idle.rst, to + rebase help.html on the next version of idle.rst. Do not worry + about version changes as version is not displayed. Examine other + changes and the result of Help -> IDLE Help. + + If maintenance and default versions of idle.rst diverge, and + merging does not go smoothly, then consider generating + separate help.html files from separate idle.htmls. + """ src = join(abspath(dirname(dirname(dirname(__file__)))), 'Doc', 'build', 'html', 'library', 'idle.html') dst = join(abspath(dirname(__file__)), 'help.html') @@ -242,7 +266,7 @@ def show_idlehelp(parent): if not isfile(filename): # try copy_strip, present message return - HelpWindow(parent, filename, 'IDLE Help') + HelpWindow(parent, filename, 'IDLE Help (%s)' % python_version()) if __name__ == '__main__': from idlelib.idle_test.htest import run diff --git a/Lib/idlelib/idle_test/README.txt b/Lib/idlelib/idle_test/README.txt index 2339926ef3..dc7a28697c 100644 --- a/Lib/idlelib/idle_test/README.txt +++ b/Lib/idlelib/idle_test/README.txt @@ -2,12 +2,12 @@ README FOR IDLE TESTS IN IDLELIB.IDLE_TEST 0. Quick Start -Automated unit tests were added in 2.7 for Python 2.x and 3.3 for Python 3.x. +Automated unit tests were added in 3.3 for Python 3.x. To run the tests from a command line: python -m test.test_idle -Human-mediated tests were added later in 2.7 and in 3.4. +Human-mediated tests were added later in 3.4. python -m idlelib.idle_test.htest @@ -15,9 +15,9 @@ python -m idlelib.idle_test.htest 1. Test Files The idle directory, idlelib, has over 60 xyz.py files. The idle_test -subdirectory should contain a test_xyz.py for each, where 'xyz' is lowercased -even if xyz.py is not. Here is a possible template, with the blanks after after -'.' and 'as', and before and after '_' to be filled in. +subdirectory should contain a test_xyz.py for each, where 'xyz' is +lowercased even if xyz.py is not. Here is a possible template, with the +blanks after '.' and 'as', and before and after '_' to be filled in. import unittest from test.support import requires @@ -30,9 +30,9 @@ class _Test(unittest.TestCase): if __name__ == '__main__': unittest.main(verbosity=2) -Add the following at the end of xyy.py, with the appropriate name added after -'test_'. Some files already have something like this for htest. If so, insert -the import and unittest.main lines before the htest lines. +Add the following at the end of xyy.py, with the appropriate name added +after 'test_'. Some files already have something like this for htest. +If so, insert the import and unittest.main lines before the htest lines. if __name__ == "__main__": import unittest @@ -42,64 +42,82 @@ if __name__ == "__main__": 2. GUI Tests -When run as part of the Python test suite, Idle gui tests need to run -test.support.requires('gui') (test.test_support in 2.7). A test is a gui test -if it creates a Tk root or master object either directly or indirectly by -instantiating a tkinter or idle class. For the benefit of test processes that -either have no graphical environment available or are not allowed to use it, gui -tests must be 'guarded' by "requires('gui')" in a setUp function or method. -This will typically be setUpClass. +When run as part of the Python test suite, Idle GUI tests need to run +test.support.requires('gui'). A test is a GUI test if it creates a +tkinter.Tk root or master object either directly or indirectly by +instantiating a tkinter or idle class. GUI tests cannot run in test +processes that either have no graphical environment available or are not +allowed to use it. -To avoid interfering with other gui tests, all gui objects must be destroyed and -deleted by the end of the test. Widgets, such as a Tk root, created in a setUpX -function, should be destroyed in the corresponding tearDownX. Module and class -widget attributes should also be deleted.. +To guard a module consisting entirely of GUI tests, start with + +from test.support import requires +requires('gui') + +To guard a test class, put "requires('gui')" in its setUpClass function. + +To avoid interfering with other GUI tests, all GUI objects must be destroyed and +deleted by the end of the test. The Tk root created in a setUpX function should +be destroyed in the corresponding tearDownX and the module or class attribute +deleted. Others widgets should descend from the single root and the attributes +deleted BEFORE root is destroyed. See https://bugs.python.org/issue20567. @classmethod def setUpClass(cls): requires('gui') cls.root = tk.Tk() + cls.text = tk.Text(root) @classmethod def tearDownClass(cls): + del cls.text + cls.root.update_idletasks() cls.root.destroy() del cls.root +The update_idletasks call is sometimes needed to prevent the following warning +either when running a test alone or as part of the test suite (#27196). + can't invoke "event" command: application has been destroyed + ... + "ttk::ThemeChanged" Requires('gui') causes the test(s) it guards to be skipped if any of -a few conditions are met: - - - The tests are being run by regrtest.py, and it was started without enabling - the "gui" resource with the "-u" command line option. - - - The tests are being run on Windows by a service that is not allowed to - interact with the graphical environment. - - - The tests are being run on Mac OSX in a process that cannot make a window - manager connection. - +these conditions are met: + + - The tests are being run by regrtest.py, and it was started without + enabling the "gui" resource with the "-u" command line option. + + - The tests are being run on Windows by a service that is not allowed + to interact with the graphical environment. + + - The tests are being run on Linux and X Windows is not available. + + - The tests are being run on Mac OSX in a process that cannot make a + window manager connection. + - tkinter.Tk cannot be successfully instantiated for some reason. - + - test.support.use_resources has been set by something other than regrtest.py and does not contain "gui". - -Tests of non-gui operations should avoid creating tk widgets. Incidental uses of -tk variables and messageboxes can be replaced by the mock classes in -idle_test/mock_tk.py. The mock text handles some uses of the tk Text widget. + +Tests of non-GUI operations should avoid creating tk widgets. Incidental +uses of tk variables and messageboxes can be replaced by the mock +classes in idle_test/mock_tk.py. The mock text handles some uses of the +tk Text widget. 3. Running Unit Tests Assume that xyz.py and test_xyz.py both end with a unittest.main() call. -Running either from an Idle editor runs all tests in the test_xyz file with the -version of Python running Idle. Test output appears in the Shell window. The -'verbosity=2' option lists all test methods in the file, which is appropriate -when developing tests. The 'exit=False' option is needed in xyx.py files when an -htest follows. +Running either from an Idle editor runs all tests in the test_xyz file +with the version of Python running Idle. Test output appears in the +Shell window. The 'verbosity=2' option lists all test methods in the +file, which is appropriate when developing tests. The 'exit=False' +option is needed in xyx.py files when an htest follows. The following command lines also run all test methods, including -gui tests, in test_xyz.py. (Both '-m idlelib' and '-m idlelib.idle' start -Idle and so cannot run tests.) +GUI tests, in test_xyz.py. (Both '-m idlelib' and '-m idlelib.idle' +start Idle and so cannot run tests.) python -m idlelib.xyz python -m idlelib.idle_test.test_xyz @@ -109,35 +127,35 @@ The following runs all idle_test/test_*.py tests interactively. >>> import unittest >>> unittest.main('idlelib.idle_test', verbosity=2) -The following run all Idle tests at a command line. Option '-v' is the same as -'verbosity=2'. (For 2.7, replace 'test' in the second line with -'test.regrtest'.) +The following run all Idle tests at a command line. Option '-v' is the +same as 'verbosity=2'. python -m unittest -v idlelib.idle_test python -m test -v -ugui test_idle python -m test.test_idle -The idle tests are 'discovered' by idlelib.idle_test.__init__.load_tests, -which is also imported into test.test_idle. Normally, neither file should be -changed when working on individual test modules. The third command runs -unittest indirectly through regrtest. The same happens when the entire test -suite is run with 'python -m test'. So that command must work for buildbots -to stay green. Idle tests must not disturb the environment in a way that -makes other tests fail (issue 18081). +The idle tests are 'discovered' by +idlelib.idle_test.__init__.load_tests, which is also imported into +test.test_idle. Normally, neither file should be changed when working on +individual test modules. The third command runs unittest indirectly +through regrtest. The same happens when the entire test suite is run +with 'python -m test'. So that command must work for buildbots to stay +green. Idle tests must not disturb the environment in a way that makes +other tests fail (issue 18081). -To run an individual Testcase or test method, extend the dotted name given to -unittest on the command line. +To run an individual Testcase or test method, extend the dotted name +given to unittest on the command line. python -m unittest -v idlelib.idle_test.test_xyz.Test_case.test_meth 4. Human-mediated Tests -Human-mediated tests are widget tests that cannot be automated but need human -verification. They are contained in idlelib/idle_test/htest.py, which has -instructions. (Some modules need an auxiliary function, identified with # htest -# on the header line.) The set is about complete, though some tests need -improvement. To run all htests, run the htest file from an editor or from the -command line with: +Human-mediated tests are widget tests that cannot be automated but need +human verification. They are contained in idlelib/idle_test/htest.py, +which has instructions. (Some modules need an auxiliary function, +identified with "# htest # on the header line.) The set is about +complete, though some tests need improvement. To run all htests, run the +htest file from an editor or from the command line with: python -m idlelib.idle_test.htest diff --git a/Lib/idlelib/idle_test/__init__.py b/Lib/idlelib/idle_test/__init__.py index 1bc953643e..845c92d372 100644 --- a/Lib/idlelib/idle_test/__init__.py +++ b/Lib/idlelib/idle_test/__init__.py @@ -1,3 +1,9 @@ +'''idlelib.idle_test is a private implementation of test.test_idle, +which tests the IDLE application as part of the stdlib test suite. +Run IDLE tests alone with "python -m test.test_idle". +This package and its contained modules are subject to change and +any direct use is at your own risk. +''' from os.path import dirname def load_tests(loader, standard_tests, pattern): diff --git a/Lib/idlelib/idle_test/htest.py b/Lib/idlelib/idle_test/htest.py index 3e24518a0f..58e62cb4e2 100644 --- a/Lib/idlelib/idle_test/htest.py +++ b/Lib/idlelib/idle_test/htest.py @@ -192,7 +192,10 @@ _io_binding_spec = { 'msg': "Test the following bindings.\n" "<Control-o> to open file from dialog.\n" "Edit the file.\n" + "<Control-p> to print the file.\n" "<Control-s> to save the file.\n" + "<Alt-s> to save-as another file.\n" + "<Control-c> to save-copy-as another file.\n" "Check that changes were saved by opening the file elsewhere." } diff --git a/Lib/idlelib/idle_test/mock_tk.py b/Lib/idlelib/idle_test/mock_tk.py index 86fe84884f..6e351297d7 100644 --- a/Lib/idlelib/idle_test/mock_tk.py +++ b/Lib/idlelib/idle_test/mock_tk.py @@ -296,3 +296,8 @@ class Text: def bind(sequence=None, func=None, add=None): "Bind to this widget at event sequence a call to function func." pass + +class Entry: + "Mock for tkinter.Entry." + def focus_set(self): + pass diff --git a/Lib/idlelib/idle_test/test_autocomplete.py b/Lib/idlelib/idle_test/test_autocomplete.py index 3a2192e8af..5fc899dcd4 100644 --- a/Lib/idlelib/idle_test/test_autocomplete.py +++ b/Lib/idlelib/idle_test/test_autocomplete.py @@ -4,7 +4,6 @@ from tkinter import Tk, Text import idlelib.AutoComplete as ac import idlelib.AutoCompleteWindow as acw -import idlelib.macosxSupport as mac from idlelib.idle_test.mock_idle import Func from idlelib.idle_test.mock_tk import Event @@ -27,15 +26,13 @@ class AutoCompleteTest(unittest.TestCase): def setUpClass(cls): requires('gui') cls.root = Tk() - mac.setupApp(cls.root, None) cls.text = Text(cls.root) cls.editor = DummyEditwin(cls.root, cls.text) @classmethod def tearDownClass(cls): + del cls.editor, cls.text cls.root.destroy() - del cls.text - del cls.editor del cls.root def setUp(self): diff --git a/Lib/idlelib/idle_test/test_autoexpand.py b/Lib/idlelib/idle_test/test_autoexpand.py index 7ca941ec29..d2a3156dca 100644 --- a/Lib/idlelib/idle_test/test_autoexpand.py +++ b/Lib/idlelib/idle_test/test_autoexpand.py @@ -25,10 +25,10 @@ class AutoExpandTest(unittest.TestCase): @classmethod def tearDownClass(cls): + del cls.text, cls.auto_expand if hasattr(cls, 'tk'): cls.tk.destroy() del cls.tk - del cls.text, cls.auto_expand def tearDown(self): self.text.delete('1.0', 'end') diff --git a/Lib/idlelib/idle_test/test_config_help.py b/Lib/idlelib/idle_test/test_config_help.py new file mode 100644 index 0000000000..664f8edc62 --- /dev/null +++ b/Lib/idlelib/idle_test/test_config_help.py @@ -0,0 +1,106 @@ +"""Unittests for idlelib.configHelpSourceEdit""" +import unittest +from idlelib.idle_test.mock_tk import Var, Mbox, Entry +from idlelib import configHelpSourceEdit as help_dialog_module + +help_dialog = help_dialog_module.GetHelpSourceDialog + + +class Dummy_help_dialog: + # Mock for testing the following methods of help_dialog + menu_ok = help_dialog.menu_ok + path_ok = help_dialog.path_ok + ok = help_dialog.ok + cancel = help_dialog.cancel + # Attributes, constant or variable, needed for tests + menu = Var() + entryMenu = Entry() + path = Var() + entryPath = Entry() + result = None + destroyed = False + + def destroy(self): + self.destroyed = True + + +# menu_ok and path_ok call Mbox.showerror if menu and path are not ok. +orig_mbox = help_dialog_module.tkMessageBox +showerror = Mbox.showerror + + +class ConfigHelpTest(unittest.TestCase): + dialog = Dummy_help_dialog() + + @classmethod + def setUpClass(cls): + help_dialog_module.tkMessageBox = Mbox + + @classmethod + def tearDownClass(cls): + help_dialog_module.tkMessageBox = orig_mbox + + def test_blank_menu(self): + self.dialog.menu.set('') + self.assertFalse(self.dialog.menu_ok()) + self.assertEqual(showerror.title, 'Menu Item Error') + self.assertIn('No', showerror.message) + + def test_long_menu(self): + self.dialog.menu.set('hello' * 10) + self.assertFalse(self.dialog.menu_ok()) + self.assertEqual(showerror.title, 'Menu Item Error') + self.assertIn('long', showerror.message) + + def test_good_menu(self): + self.dialog.menu.set('help') + showerror.title = 'No Error' # should not be called + self.assertTrue(self.dialog.menu_ok()) + self.assertEqual(showerror.title, 'No Error') + + def test_blank_path(self): + self.dialog.path.set('') + self.assertFalse(self.dialog.path_ok()) + self.assertEqual(showerror.title, 'File Path Error') + self.assertIn('No', showerror.message) + + def test_invalid_file_path(self): + self.dialog.path.set('foobar' * 100) + self.assertFalse(self.dialog.path_ok()) + self.assertEqual(showerror.title, 'File Path Error') + self.assertIn('not exist', showerror.message) + + def test_invalid_url_path(self): + self.dialog.path.set('ww.foobar.com') + self.assertFalse(self.dialog.path_ok()) + self.assertEqual(showerror.title, 'File Path Error') + self.assertIn('not exist', showerror.message) + + self.dialog.path.set('htt.foobar.com') + self.assertFalse(self.dialog.path_ok()) + self.assertEqual(showerror.title, 'File Path Error') + self.assertIn('not exist', showerror.message) + + def test_good_path(self): + self.dialog.path.set('https://docs.python.org') + showerror.title = 'No Error' # should not be called + self.assertTrue(self.dialog.path_ok()) + self.assertEqual(showerror.title, 'No Error') + + def test_ok(self): + self.dialog.destroyed = False + self.dialog.menu.set('help') + self.dialog.path.set('https://docs.python.org') + self.dialog.ok() + self.assertEqual(self.dialog.result, ('help', + 'https://docs.python.org')) + self.assertTrue(self.dialog.destroyed) + + def test_cancel(self): + self.dialog.destroyed = False + self.dialog.cancel() + self.assertEqual(self.dialog.result, None) + self.assertTrue(self.dialog.destroyed) + +if __name__ == '__main__': + unittest.main(verbosity=2, exit=False) diff --git a/Lib/idlelib/idle_test/test_configdialog.py b/Lib/idlelib/idle_test/test_configdialog.py index 68831236b7..5c09790c64 100644 --- a/Lib/idlelib/idle_test/test_configdialog.py +++ b/Lib/idlelib/idle_test/test_configdialog.py @@ -1,31 +1,32 @@ -'''Unittests for idlelib/configHandler.py - -Coverage: 46% just by creating dialog. The other half is change code. +'''Test idlelib.configDialog. +Coverage: 46% just by creating dialog. +The other half is code for working with user customizations. ''' -import unittest +from idlelib.configDialog import ConfigDialog # always test import from test.support import requires +requires('gui') from tkinter import Tk -from idlelib.configDialog import ConfigDialog -from idlelib.macosxSupport import _initializeTkVariantTests - +import unittest +from idlelib import macosxSupport as macosx class ConfigDialogTest(unittest.TestCase): @classmethod def setUpClass(cls): - requires('gui') cls.root = Tk() - _initializeTkVariantTests(cls.root) + cls.root.withdraw() + macosx._initializeTkVariantTests(cls.root) @classmethod def tearDownClass(cls): + cls.root.update_idletasks() cls.root.destroy() del cls.root def test_dialog(self): - d=ConfigDialog(self.root, 'Test', _utest=True) - d.destroy() + d = ConfigDialog(self.root, 'Test', _utest=True) + d.remove_var_callbacks() if __name__ == '__main__': diff --git a/Lib/idlelib/idle_test/test_delegator.py b/Lib/idlelib/idle_test/test_delegator.py index b8ae5eeefe..1f0baa9c0d 100644 --- a/Lib/idlelib/idle_test/test_delegator.py +++ b/Lib/idlelib/idle_test/test_delegator.py @@ -4,34 +4,37 @@ from idlelib.Delegator import Delegator class DelegatorTest(unittest.TestCase): def test_mydel(self): - # test a simple use scenario + # Test a simple use scenario. - # initialize + # Initialize an int delegator. mydel = Delegator(int) self.assertIs(mydel.delegate, int) self.assertEqual(mydel._Delegator__cache, set()) - - # add an attribute: + # Trying to access a non-attribute of int fails. self.assertRaises(AttributeError, mydel.__getattr__, 'xyz') + + # Add real int attribute 'bit_length' by accessing it. bl = mydel.bit_length self.assertIs(bl, int.bit_length) self.assertIs(mydel.__dict__['bit_length'], int.bit_length) self.assertEqual(mydel._Delegator__cache, {'bit_length'}) - # add a second attribute + # Add attribute 'numerator'. mydel.numerator self.assertEqual(mydel._Delegator__cache, {'bit_length', 'numerator'}) - # delete the second (which, however, leaves it in the name cache) + # Delete 'numerator'. del mydel.numerator self.assertNotIn('numerator', mydel.__dict__) - self.assertIn('numerator', mydel._Delegator__cache) + # The current implementation leaves it in the name cache. + # self.assertIn('numerator', mydel._Delegator__cache) + # However, this is not required and not part of the specification - # reset by calling .setdelegate, which calls .resetcache - mydel.setdelegate(float) - self.assertIs(mydel.delegate, float) + # Change delegate to float, first resetting the attributes. + mydel.setdelegate(float) # calls resetcache self.assertNotIn('bit_length', mydel.__dict__) self.assertEqual(mydel._Delegator__cache, set()) + self.assertIs(mydel.delegate, float) if __name__ == '__main__': unittest.main(verbosity=2, exit=2) diff --git a/Lib/idlelib/idle_test/test_editmenu.py b/Lib/idlelib/idle_test/test_editmenu.py new file mode 100644 index 0000000000..a258e29e0f --- /dev/null +++ b/Lib/idlelib/idle_test/test_editmenu.py @@ -0,0 +1,72 @@ +'''Test (selected) IDLE Edit menu items. + +Edit modules have their own test files files +''' +from test.support import requires +requires('gui') +import tkinter as tk +import unittest +from idlelib import PyShell + +class PasteTest(unittest.TestCase): + '''Test pasting into widgets that allow pasting. + + On X11, replacing selections requires tk fix. + ''' + @classmethod + def setUpClass(cls): + cls.root = root = tk.Tk() + root.withdraw() + PyShell.fix_x11_paste(root) + cls.text = tk.Text(root) + cls.entry = tk.Entry(root) + cls.spin = tk.Spinbox(root) + root.clipboard_clear() + root.clipboard_append('two') + + @classmethod + def tearDownClass(cls): + del cls.text, cls.entry, cls.spin + cls.root.clipboard_clear() + cls.root.update_idletasks() + cls.root.destroy() + del cls.root + + def test_paste_text(self): + "Test pasting into text with and without a selection." + text = self.text + for tag, ans in ('', 'onetwo\n'), ('sel', 'two\n'): + with self.subTest(tag=tag, ans=ans): + text.delete('1.0', 'end') + text.insert('1.0', 'one', tag) + text.event_generate('<<Paste>>') + self.assertEqual(text.get('1.0', 'end'), ans) + + def test_paste_entry(self): + "Test pasting into an entry with and without a selection." + # On 3.6, generated <<Paste>> fails without empty select range + # for 'no selection'. Live widget works fine. + entry = self.entry + for end, ans in (0, 'onetwo'), ('end', 'two'): + with self.subTest(entry=entry, end=end, ans=ans): + entry.delete(0, 'end') + entry.insert(0, 'one') + entry.select_range(0, end) # see note + entry.event_generate('<<Paste>>') + self.assertEqual(entry.get(), ans) + + def test_paste_spin(self): + "Test pasting into a spinbox with and without a selection." + # See note above for entry. + spin = self.spin + for end, ans in (0, 'onetwo'), ('end', 'two'): + with self.subTest(end=end, ans=ans): + spin.delete(0, 'end') + spin.insert(0, 'one') + spin.selection('range', 0, end) # see note + spin.event_generate('<<Paste>>') + self.assertEqual(spin.get(), ans) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_formatparagraph.py b/Lib/idlelib/idle_test/test_formatparagraph.py index f6039e6ab4..e5561d84a6 100644 --- a/Lib/idlelib/idle_test/test_formatparagraph.py +++ b/Lib/idlelib/idle_test/test_formatparagraph.py @@ -276,10 +276,9 @@ class FormatEventTest(unittest.TestCase): @classmethod def tearDownClass(cls): + del cls.text, cls.formatter cls.root.destroy() del cls.root - del cls.text - del cls.formatter def test_short_line(self): self.text.insert('1.0', "Short line\n") diff --git a/Lib/idlelib/idle_test/test_help_about.py b/Lib/idlelib/idle_test/test_help_about.py new file mode 100644 index 0000000000..d0a012767a --- /dev/null +++ b/Lib/idlelib/idle_test/test_help_about.py @@ -0,0 +1,52 @@ +'''Test idlelib.help_about. + +Coverage: +''' +from idlelib import aboutDialog as help_about +from idlelib import textView as textview +from idlelib.idle_test.mock_idle import Func +from idlelib.idle_test.mock_tk import Mbox +import unittest + +About = help_about.AboutDialog +class Dummy_about_dialog(): + # Dummy class for testing file display functions. + idle_credits = About.ShowIDLECredits + idle_readme = About.ShowIDLEAbout + idle_news = About.ShowIDLENEWS + # Called by the above + display_file_text = About.display_file_text + + +class DisplayFileTest(unittest.TestCase): + "Test that .txt files are found and properly decoded." + dialog = Dummy_about_dialog() + + @classmethod + def setUpClass(cls): + cls.orig_mbox = textview.tkMessageBox + cls.orig_view = textview.view_text + cls.mbox = Mbox() + cls.view = Func() + textview.tkMessageBox = cls.mbox + textview.view_text = cls.view + cls.About = Dummy_about_dialog() + + @classmethod + def tearDownClass(cls): + textview.tkMessageBox = cls.orig_mbox + textview.view_text = cls.orig_view + + def test_file_isplay(self): + for handler in (self.dialog.idle_credits, + self.dialog.idle_readme, + self.dialog.idle_news): + self.mbox.showerror.message = '' + self.view.called = False + handler() + self.assertEqual(self.mbox.showerror.message, '') + self.assertEqual(self.view.called, True) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_hyperparser.py b/Lib/idlelib/idle_test/test_hyperparser.py index edfc783fe8..9ce3f2c67f 100644 --- a/Lib/idlelib/idle_test/test_hyperparser.py +++ b/Lib/idlelib/idle_test/test_hyperparser.py @@ -37,6 +37,7 @@ class HyperParserTest(unittest.TestCase): def setUpClass(cls): requires('gui') cls.root = Tk() + cls.root.withdraw() cls.text = Text(cls.root) cls.editwin = DummyEditwin(cls.text) diff --git a/Lib/idlelib/idle_test/test_idlehistory.py b/Lib/idlelib/idle_test/test_idlehistory.py index d7c3d70393..6e7c6c3bff 100644 --- a/Lib/idlelib/idle_test/test_idlehistory.py +++ b/Lib/idlelib/idle_test/test_idlehistory.py @@ -68,6 +68,7 @@ class FetchTest(unittest.TestCase): def setUpClass(cls): requires('gui') cls.root = tk.Tk() + cls.root.withdraw() def setUp(self): self.text = text = TextWrapper(self.root) diff --git a/Lib/idlelib/idle_test/test_parenmatch.py b/Lib/idlelib/idle_test/test_parenmatch.py index 9aba4bec93..95cc22cf68 100644 --- a/Lib/idlelib/idle_test/test_parenmatch.py +++ b/Lib/idlelib/idle_test/test_parenmatch.py @@ -1,10 +1,13 @@ -"""Test idlelib.ParenMatch.""" -# This must currently be a gui test because ParenMatch methods use -# several text methods not defined on idlelib.idle_test.mock_tk.Text. +'''Test idlelib.ParenMatch. + +This must currently be a gui test because ParenMatch methods use +several text methods not defined on idlelib.idle_test.mock_tk.Text. +''' +from test.support import requires +requires('gui') import unittest from unittest.mock import Mock -from test.support import requires from tkinter import Tk, Text from idlelib.ParenMatch import ParenMatch @@ -20,7 +23,6 @@ class ParenMatchTest(unittest.TestCase): @classmethod def setUpClass(cls): - requires('gui') cls.root = Tk() cls.text = Text(cls.root) cls.editwin = DummyEditwin(cls.text) @@ -29,6 +31,7 @@ class ParenMatchTest(unittest.TestCase): @classmethod def tearDownClass(cls): del cls.text, cls.editwin + cls.root.update_idletasks() cls.root.destroy() del cls.root diff --git a/Lib/idlelib/idle_test/test_percolator.py b/Lib/idlelib/idle_test/test_percolator.py new file mode 100644 index 0000000000..4c0a7ad2bd --- /dev/null +++ b/Lib/idlelib/idle_test/test_percolator.py @@ -0,0 +1,118 @@ +'''Test Percolator''' +from test.support import requires +requires('gui') + +import unittest +from tkinter import Text, Tk, END +from idlelib.Percolator import Percolator, Delegator + + +class MyFilter(Delegator): + def __init__(self): + Delegator.__init__(self, None) + + def insert(self, *args): + self.insert_called_with = args + self.delegate.insert(*args) + + def delete(self, *args): + self.delete_called_with = args + self.delegate.delete(*args) + + def uppercase_insert(self, index, chars, tags=None): + chars = chars.upper() + self.delegate.insert(index, chars) + + def lowercase_insert(self, index, chars, tags=None): + chars = chars.lower() + self.delegate.insert(index, chars) + + def dont_insert(self, index, chars, tags=None): + pass + + +class PercolatorTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.root = Tk() + cls.text = Text(cls.root) + + @classmethod + def tearDownClass(cls): + del cls.text + cls.root.destroy() + del cls.root + + def setUp(self): + self.percolator = Percolator(self.text) + self.filter_one = MyFilter() + self.filter_two = MyFilter() + self.percolator.insertfilter(self.filter_one) + self.percolator.insertfilter(self.filter_two) + + def tearDown(self): + self.percolator.close() + self.text.delete('1.0', END) + + def test_insertfilter(self): + self.assertIsNotNone(self.filter_one.delegate) + self.assertEqual(self.percolator.top, self.filter_two) + self.assertEqual(self.filter_two.delegate, self.filter_one) + self.assertEqual(self.filter_one.delegate, self.percolator.bottom) + + def test_removefilter(self): + filter_three = MyFilter() + self.percolator.removefilter(self.filter_two) + self.assertEqual(self.percolator.top, self.filter_one) + self.assertIsNone(self.filter_two.delegate) + + filter_three = MyFilter() + self.percolator.insertfilter(self.filter_two) + self.percolator.insertfilter(filter_three) + self.percolator.removefilter(self.filter_one) + self.assertEqual(self.percolator.top, filter_three) + self.assertEqual(filter_three.delegate, self.filter_two) + self.assertEqual(self.filter_two.delegate, self.percolator.bottom) + self.assertIsNone(self.filter_one.delegate) + + def test_insert(self): + self.text.insert('insert', 'foo') + self.assertEqual(self.text.get('1.0', END), 'foo\n') + self.assertTupleEqual(self.filter_one.insert_called_with, + ('insert', 'foo', None)) + + def test_modify_insert(self): + self.filter_one.insert = self.filter_one.uppercase_insert + self.text.insert('insert', 'bAr') + self.assertEqual(self.text.get('1.0', END), 'BAR\n') + + def test_modify_chain_insert(self): + filter_three = MyFilter() + self.percolator.insertfilter(filter_three) + self.filter_two.insert = self.filter_two.uppercase_insert + self.filter_one.insert = self.filter_one.lowercase_insert + self.text.insert('insert', 'BaR') + self.assertEqual(self.text.get('1.0', END), 'bar\n') + + def test_dont_insert(self): + self.filter_one.insert = self.filter_one.dont_insert + self.text.insert('insert', 'foo bar') + self.assertEqual(self.text.get('1.0', END), '\n') + self.filter_one.insert = self.filter_one.dont_insert + self.text.insert('insert', 'foo bar') + self.assertEqual(self.text.get('1.0', END), '\n') + + def test_without_filter(self): + self.text.insert('insert', 'hello') + self.assertEqual(self.text.get('1.0', 'end'), 'hello\n') + + def test_delete(self): + self.text.insert('insert', 'foo') + self.text.delete('1.0', '1.2') + self.assertEqual(self.text.get('1.0', END), 'o\n') + self.assertTupleEqual(self.filter_one.delete_called_with, + ('1.0', '1.2')) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_replacedialog.py b/Lib/idlelib/idle_test/test_replacedialog.py new file mode 100644 index 0000000000..ff44820809 --- /dev/null +++ b/Lib/idlelib/idle_test/test_replacedialog.py @@ -0,0 +1,293 @@ +"""Unittest for idlelib.ReplaceDialog""" +from test.support import requires +requires('gui') + +import unittest +from unittest.mock import Mock +from tkinter import Tk, Text +from idlelib.idle_test.mock_tk import Mbox +import idlelib.SearchEngine as se +import idlelib.ReplaceDialog as rd + +orig_mbox = se.tkMessageBox +showerror = Mbox.showerror + + +class ReplaceDialogTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.root = Tk() + cls.root.withdraw() + se.tkMessageBox = Mbox + cls.engine = se.SearchEngine(cls.root) + cls.dialog = rd.ReplaceDialog(cls.root, cls.engine) + cls.dialog.ok = Mock() + cls.text = Text(cls.root) + cls.text.undo_block_start = Mock() + cls.text.undo_block_stop = Mock() + cls.dialog.text = cls.text + + @classmethod + def tearDownClass(cls): + se.tkMessageBox = orig_mbox + del cls.text, cls.dialog, cls.engine + cls.root.destroy() + del cls.root + + def setUp(self): + self.text.insert('insert', 'This is a sample sTring') + + def tearDown(self): + self.engine.patvar.set('') + self.dialog.replvar.set('') + self.engine.wordvar.set(False) + self.engine.casevar.set(False) + self.engine.revar.set(False) + self.engine.wrapvar.set(True) + self.engine.backvar.set(False) + showerror.title = '' + showerror.message = '' + self.text.delete('1.0', 'end') + + def test_replace_simple(self): + # Test replace function with all options at default setting. + # Wrap around - True + # Regular Expression - False + # Match case - False + # Match word - False + # Direction - Forwards + text = self.text + equal = self.assertEqual + pv = self.engine.patvar + rv = self.dialog.replvar + replace = self.dialog.replace_it + + # test accessor method + self.engine.setpat('asdf') + equal(self.engine.getpat(), pv.get()) + + # text found and replaced + pv.set('a') + rv.set('asdf') + self.dialog.open(self.text) + replace() + equal(text.get('1.8', '1.12'), 'asdf') + + # dont "match word" case + text.mark_set('insert', '1.0') + pv.set('is') + rv.set('hello') + replace() + equal(text.get('1.2', '1.7'), 'hello') + + # dont "match case" case + pv.set('string') + rv.set('world') + replace() + equal(text.get('1.23', '1.28'), 'world') + + # without "regular expression" case + text.mark_set('insert', 'end') + text.insert('insert', '\nline42:') + before_text = text.get('1.0', 'end') + pv.set('[a-z][\d]+') + replace() + after_text = text.get('1.0', 'end') + equal(before_text, after_text) + + # test with wrap around selected and complete a cycle + text.mark_set('insert', '1.9') + pv.set('i') + rv.set('j') + replace() + equal(text.get('1.8'), 'i') + equal(text.get('2.1'), 'j') + replace() + equal(text.get('2.1'), 'j') + equal(text.get('1.8'), 'j') + before_text = text.get('1.0', 'end') + replace() + after_text = text.get('1.0', 'end') + equal(before_text, after_text) + + # text not found + before_text = text.get('1.0', 'end') + pv.set('foobar') + replace() + after_text = text.get('1.0', 'end') + equal(before_text, after_text) + + # test access method + self.dialog.find_it(0) + + def test_replace_wrap_around(self): + text = self.text + equal = self.assertEqual + pv = self.engine.patvar + rv = self.dialog.replvar + replace = self.dialog.replace_it + self.engine.wrapvar.set(False) + + # replace candidate found both after and before 'insert' + text.mark_set('insert', '1.4') + pv.set('i') + rv.set('j') + replace() + equal(text.get('1.2'), 'i') + equal(text.get('1.5'), 'j') + replace() + equal(text.get('1.2'), 'i') + equal(text.get('1.20'), 'j') + replace() + equal(text.get('1.2'), 'i') + + # replace candidate found only before 'insert' + text.mark_set('insert', '1.8') + pv.set('is') + before_text = text.get('1.0', 'end') + replace() + after_text = text.get('1.0', 'end') + equal(before_text, after_text) + + def test_replace_whole_word(self): + text = self.text + equal = self.assertEqual + pv = self.engine.patvar + rv = self.dialog.replvar + replace = self.dialog.replace_it + self.engine.wordvar.set(True) + + pv.set('is') + rv.set('hello') + replace() + equal(text.get('1.0', '1.4'), 'This') + equal(text.get('1.5', '1.10'), 'hello') + + def test_replace_match_case(self): + equal = self.assertEqual + text = self.text + pv = self.engine.patvar + rv = self.dialog.replvar + replace = self.dialog.replace_it + self.engine.casevar.set(True) + + before_text = self.text.get('1.0', 'end') + pv.set('this') + rv.set('that') + replace() + after_text = self.text.get('1.0', 'end') + equal(before_text, after_text) + + pv.set('This') + replace() + equal(text.get('1.0', '1.4'), 'that') + + def test_replace_regex(self): + equal = self.assertEqual + text = self.text + pv = self.engine.patvar + rv = self.dialog.replvar + replace = self.dialog.replace_it + self.engine.revar.set(True) + + before_text = text.get('1.0', 'end') + pv.set('[a-z][\d]+') + rv.set('hello') + replace() + after_text = text.get('1.0', 'end') + equal(before_text, after_text) + + text.insert('insert', '\nline42') + replace() + equal(text.get('2.0', '2.8'), 'linhello') + + pv.set('') + replace() + self.assertIn('error', showerror.title) + self.assertIn('Empty', showerror.message) + + pv.set('[\d') + replace() + self.assertIn('error', showerror.title) + self.assertIn('Pattern', showerror.message) + + showerror.title = '' + showerror.message = '' + pv.set('[a]') + rv.set('test\\') + replace() + self.assertIn('error', showerror.title) + self.assertIn('Invalid Replace Expression', showerror.message) + + # test access method + self.engine.setcookedpat("\'") + equal(pv.get(), "\\'") + + def test_replace_backwards(self): + equal = self.assertEqual + text = self.text + pv = self.engine.patvar + rv = self.dialog.replvar + replace = self.dialog.replace_it + self.engine.backvar.set(True) + + text.insert('insert', '\nis as ') + + pv.set('is') + rv.set('was') + replace() + equal(text.get('1.2', '1.4'), 'is') + equal(text.get('2.0', '2.3'), 'was') + replace() + equal(text.get('1.5', '1.8'), 'was') + replace() + equal(text.get('1.2', '1.5'), 'was') + + def test_replace_all(self): + text = self.text + pv = self.engine.patvar + rv = self.dialog.replvar + replace_all = self.dialog.replace_all + + text.insert('insert', '\n') + text.insert('insert', text.get('1.0', 'end')*100) + pv.set('is') + rv.set('was') + replace_all() + self.assertNotIn('is', text.get('1.0', 'end')) + + self.engine.revar.set(True) + pv.set('') + replace_all() + self.assertIn('error', showerror.title) + self.assertIn('Empty', showerror.message) + + pv.set('[s][T]') + rv.set('\\') + replace_all() + + self.engine.revar.set(False) + pv.set('text which is not present') + rv.set('foobar') + replace_all() + + def test_default_command(self): + text = self.text + pv = self.engine.patvar + rv = self.dialog.replvar + replace_find = self.dialog.default_command + equal = self.assertEqual + + pv.set('This') + rv.set('was') + replace_find() + equal(text.get('sel.first', 'sel.last'), 'was') + + self.engine.revar.set(True) + pv.set('') + replace_find() + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_searchdialog.py b/Lib/idlelib/idle_test/test_searchdialog.py new file mode 100644 index 0000000000..190c866a18 --- /dev/null +++ b/Lib/idlelib/idle_test/test_searchdialog.py @@ -0,0 +1,80 @@ +"""Test SearchDialog class in SearchDialogue.py""" + +# Does not currently test the event handler wrappers. +# A usage test should simulate clicks and check hilighting. +# Tests need to be coordinated with SearchDialogBase tests +# to avoid duplication. + +from test.support import requires +requires('gui') + +import unittest +import tkinter as tk +from tkinter import BooleanVar +import idlelib.SearchEngine as se +import idlelib.SearchDialog as sd + + +class SearchDialogTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.root = tk.Tk() + + @classmethod + def tearDownClass(cls): + cls.root.destroy() + del cls.root + + def setUp(self): + self.engine = se.SearchEngine(self.root) + self.dialog = sd.SearchDialog(self.root, self.engine) + self.text = tk.Text(self.root) + self.text.insert('1.0', 'Hello World!') + + def test_find_again(self): + # Search for various expressions + text = self.text + + self.engine.setpat('') + self.assertFalse(self.dialog.find_again(text)) + + self.engine.setpat('Hello') + self.assertTrue(self.dialog.find_again(text)) + + self.engine.setpat('Goodbye') + self.assertFalse(self.dialog.find_again(text)) + + self.engine.setpat('World!') + self.assertTrue(self.dialog.find_again(text)) + + self.engine.setpat('Hello World!') + self.assertTrue(self.dialog.find_again(text)) + + # Regular expression + self.engine.revar = BooleanVar(self.root, True) + self.engine.setpat('W[aeiouy]r') + self.assertTrue(self.dialog.find_again(text)) + + def test_find_selection(self): + # Select some text and make sure it's found + text = self.text + # Add additional line to find + self.text.insert('2.0', 'Hello World!') + + text.tag_add('sel', '1.0', '1.4') # Select 'Hello' + self.assertTrue(self.dialog.find_selection(text)) + + text.tag_remove('sel', '1.0', 'end') + text.tag_add('sel', '1.6', '1.11') # Select 'World!' + self.assertTrue(self.dialog.find_selection(text)) + + text.tag_remove('sel', '1.0', 'end') + text.tag_add('sel', '1.0', '1.11') # Select 'Hello World!' + self.assertTrue(self.dialog.find_selection(text)) + + # Remove additional line + text.delete('2.0', 'end') + +if __name__ == '__main__': + unittest.main(verbosity=2, exit=2) diff --git a/Lib/idlelib/idle_test/test_searchengine.py b/Lib/idlelib/idle_test/test_searchengine.py index c7792fb188..edbd558133 100644 --- a/Lib/idlelib/idle_test/test_searchengine.py +++ b/Lib/idlelib/idle_test/test_searchengine.py @@ -178,7 +178,7 @@ class SearchEngineTest(unittest.TestCase): engine.revar.set(1) Equal(engine.getprog(), None) self.assertEqual(Mbox.showerror.message, - 'Error: nothing to repeat\nPattern: +') + 'Error: nothing to repeat at position 0\nPattern: +') def test_report_error(self): showerror = Mbox.showerror diff --git a/Lib/idlelib/idle_test/test_textview.py b/Lib/idlelib/idle_test/test_textview.py index 68e5b82ad9..5d2e60019d 100644 --- a/Lib/idlelib/idle_test/test_textview.py +++ b/Lib/idlelib/idle_test/test_textview.py @@ -1,4 +1,4 @@ -'''Test the functions and main class method of textView.py. +'''Test idlelib.textView. Since all methods and functions create (or destroy) a TextViewer, which is a widget containing multiple widgets, all tests must be gui tests. @@ -20,14 +20,16 @@ from idlelib.idle_test.mock_tk import Mbox def setUpModule(): global root root = Tk() + root.withdraw() def tearDownModule(): global root - root.destroy() # pyflakes falsely sees root as undefined + root.update_idletasks() + root.destroy() # Pyflakes falsely sees root as undefined. del root -class TV(tv.TextViewer): # used by TextViewTest +class TV(tv.TextViewer): # Used in TextViewTest. transient = Func() grab_set = Func() wait_window = Func() @@ -58,8 +60,8 @@ class TextViewTest(unittest.TestCase): view.destroy = Func() view.Ok() self.assertTrue(view.destroy.called) - del view.destroy # unmask real function - view.destroy + del view.destroy # Unmask real function. + view.destroy() class textviewTest(unittest.TestCase): @@ -75,9 +77,10 @@ class textviewTest(unittest.TestCase): del cls.orig_mbox def test_view_text(self): - # If modal True, tkinter will error with 'can't invoke "event" command' + # If modal True, get tk error 'can't invoke "event" command'. view = tv.view_text(root, 'Title', 'test text', modal=False) self.assertIsInstance(view, tv.TextViewer) + view.Ok() def test_view_file(self): test_dir = os.path.dirname(__file__) diff --git a/Lib/idlelib/idle_test/test_undodelegator.py b/Lib/idlelib/idle_test/test_undodelegator.py new file mode 100644 index 0000000000..2b83c991e2 --- /dev/null +++ b/Lib/idlelib/idle_test/test_undodelegator.py @@ -0,0 +1,135 @@ +"""Unittest for UndoDelegator in idlelib.UndoDelegator. + +Coverage about 80% (retest). +""" +from test.support import requires +requires('gui') + +import unittest +from unittest.mock import Mock +from tkinter import Text, Tk +from idlelib.UndoDelegator import UndoDelegator +from idlelib.Percolator import Percolator + + +class UndoDelegatorTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.root = Tk() + cls.text = Text(cls.root) + cls.percolator = Percolator(cls.text) + + @classmethod + def tearDownClass(cls): + cls.percolator.redir.close() + del cls.percolator, cls.text + cls.root.destroy() + del cls.root + + def setUp(self): + self.delegator = UndoDelegator() + self.percolator.insertfilter(self.delegator) + self.delegator.bell = Mock(wraps=self.delegator.bell) + + def tearDown(self): + self.percolator.removefilter(self.delegator) + self.text.delete('1.0', 'end') + self.delegator.resetcache() + + def test_undo_event(self): + text = self.text + + text.insert('insert', 'foobar') + text.insert('insert', 'h') + text.event_generate('<<undo>>') + self.assertEqual(text.get('1.0', 'end'), '\n') + + text.insert('insert', 'foo') + text.insert('insert', 'bar') + text.delete('1.2', '1.4') + text.insert('insert', 'hello') + text.event_generate('<<undo>>') + self.assertEqual(text.get('1.0', '1.4'), 'foar') + text.event_generate('<<undo>>') + self.assertEqual(text.get('1.0', '1.6'), 'foobar') + text.event_generate('<<undo>>') + self.assertEqual(text.get('1.0', '1.3'), 'foo') + text.event_generate('<<undo>>') + self.delegator.undo_event('event') + self.assertTrue(self.delegator.bell.called) + + def test_redo_event(self): + text = self.text + + text.insert('insert', 'foo') + text.insert('insert', 'bar') + text.delete('1.0', '1.3') + text.event_generate('<<undo>>') + text.event_generate('<<redo>>') + self.assertEqual(text.get('1.0', '1.3'), 'bar') + text.event_generate('<<redo>>') + self.assertTrue(self.delegator.bell.called) + + def test_dump_event(self): + """ + Dump_event cannot be tested directly without changing + environment variables. So, test statements in dump_event + indirectly + """ + text = self.text + d = self.delegator + + text.insert('insert', 'foo') + text.insert('insert', 'bar') + text.delete('1.2', '1.4') + self.assertTupleEqual((d.pointer, d.can_merge), (3, True)) + text.event_generate('<<undo>>') + self.assertTupleEqual((d.pointer, d.can_merge), (2, False)) + + def test_get_set_saved(self): + # test the getter method get_saved + # test the setter method set_saved + # indirectly test check_saved + d = self.delegator + + self.assertTrue(d.get_saved()) + self.text.insert('insert', 'a') + self.assertFalse(d.get_saved()) + d.saved_change_hook = Mock() + + d.set_saved(True) + self.assertEqual(d.pointer, d.saved) + self.assertTrue(d.saved_change_hook.called) + + d.set_saved(False) + self.assertEqual(d.saved, -1) + self.assertTrue(d.saved_change_hook.called) + + def test_undo_start_stop(self): + # test the undo_block_start and undo_block_stop methods + text = self.text + + text.insert('insert', 'foo') + self.delegator.undo_block_start() + text.insert('insert', 'bar') + text.insert('insert', 'bar') + self.delegator.undo_block_stop() + self.assertEqual(text.get('1.0', '1.3'), 'foo') + + # test another code path + self.delegator.undo_block_start() + text.insert('insert', 'bar') + self.delegator.undo_block_stop() + self.assertEqual(text.get('1.0', '1.3'), 'foo') + + def test_addcmd(self): + text = self.text + # when number of undo operations exceeds max_undo + self.delegator.max_undo = max_undo = 10 + for i in range(max_undo + 10): + text.insert('insert', 'foo') + self.assertLessEqual(len(self.delegator.undolist), max_undo) + +if __name__ == '__main__': + unittest.main(verbosity=2, exit=False) diff --git a/Lib/idlelib/idle_test/test_warning.py b/Lib/idlelib/idle_test/test_warning.py index 54ac993e88..18627ddd23 100644 --- a/Lib/idlelib/idle_test/test_warning.py +++ b/Lib/idlelib/idle_test/test_warning.py @@ -68,15 +68,6 @@ class ShellWarnTest(unittest.TestCase): 'Test', UserWarning, 'test_warning.py', 99, f, 'Line of code') self.assertEqual(shellmsg.splitlines(), f.getvalue().splitlines()) -class ImportWarnTest(unittest.TestCase): - def test_idlever(self): - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") - import idlelib.idlever - self.assertEqual(len(w), 1) - self.assertTrue(issubclass(w[-1].category, DeprecationWarning)) - self.assertIn("version", str(w[-1].message)) - if __name__ == '__main__': unittest.main(verbosity=2, exit=False) diff --git a/Lib/idlelib/idle_test/test_widgetredir.py b/Lib/idlelib/idle_test/test_widgetredir.py index 64405615a0..baa975db35 100644 --- a/Lib/idlelib/idle_test/test_widgetredir.py +++ b/Lib/idlelib/idle_test/test_widgetredir.py @@ -1,7 +1,7 @@ -"""Unittest for idlelib.WidgetRedirector +'''Test idlelib.WidgetRedirector. 100% coverage -""" +''' from test.support import requires import unittest from idlelib.idle_test.mock_idle import Func @@ -14,14 +14,15 @@ class InitCloseTest(unittest.TestCase): @classmethod def setUpClass(cls): requires('gui') - cls.tk = Tk() - cls.text = Text(cls.tk) + cls.root = Tk() + cls.root.withdraw() + cls.text = Text(cls.root) @classmethod def tearDownClass(cls): - cls.text.destroy() - cls.tk.destroy() - del cls.text, cls.tk + del cls.text + cls.root.destroy() + del cls.root def test_init(self): redir = WidgetRedirector(self.text) @@ -43,14 +44,16 @@ class WidgetRedirectorTest(unittest.TestCase): @classmethod def setUpClass(cls): requires('gui') - cls.tk = Tk() - cls.text = Text(cls.tk) + cls.root = Tk() + cls.root.withdraw() + cls.text = Text(cls.root) @classmethod def tearDownClass(cls): - cls.text.destroy() - cls.tk.destroy() - del cls.text, cls.tk + del cls.text + cls.root.update_idletasks() + cls.root.destroy() + del cls.root def setUp(self): self.redir = WidgetRedirector(self.text) @@ -108,13 +111,13 @@ class WidgetRedirectorTest(unittest.TestCase): def test_command_dispatch(self): # Test that .__init__ causes redirection of tk calls # through redir.dispatch - self.tk.call(self.text._w, 'insert', 'hello') + self.root.call(self.text._w, 'insert', 'hello') self.assertEqual(self.func.args, ('hello',)) self.assertEqual(self.text.get('1.0', 'end'), '\n') # Ensure that called through redir .dispatch and not through # self.text.insert by having mock raise TclError. self.func.__init__(TclError()) - self.assertEqual(self.tk.call(self.text._w, 'insert', 'boo'), '') + self.assertEqual(self.root.call(self.text._w, 'insert', 'boo'), '') diff --git a/Lib/idlelib/rpc.py b/Lib/idlelib/rpc.py index aa33041e7b..48105f2aa1 100644 --- a/Lib/idlelib/rpc.py +++ b/Lib/idlelib/rpc.py @@ -1,4 +1,4 @@ -"""RPC Implemention, originally written for the Python Idle IDE +"""RPC Implementation, originally written for the Python Idle IDE For security reasons, GvR requested that Idle's Python execution server process connect to the Idle process, which listens for the connection. Since Idle has diff --git a/Lib/idlelib/run.py b/Lib/idlelib/run.py index 595e7bc3aa..28ce4200a9 100644 --- a/Lib/idlelib/run.py +++ b/Lib/idlelib/run.py @@ -19,6 +19,12 @@ from idlelib import IOBinding import __main__ +for mod in ('simpledialog', 'messagebox', 'font', + 'dialog', 'filedialog', 'commondialog', + 'colorchooser'): + delattr(tkinter, mod) + del sys.modules['tkinter.' + mod] + LOCALHOST = '127.0.0.1' import warnings diff --git a/Lib/idlelib/textView.py b/Lib/idlelib/textView.py index 01b2d8f4ab..12ac31962d 100644 --- a/Lib/idlelib/textView.py +++ b/Lib/idlelib/textView.py @@ -76,6 +76,10 @@ def view_file(parent, title, filename, encoding=None, modal=True): tkMessageBox.showerror(title='File Load Error', message='Unable to load file %r .' % filename, parent=parent) + except UnicodeDecodeError as err: + tkMessageBox.showerror(title='Unicode Decode Error', + message=str(err), + parent=parent) else: return view_text(parent, title, contents, modal) |