diff options
Diffstat (limited to 'Lib/idlelib')
-rw-r--r-- | Lib/idlelib/NEWS.txt | 96 | ||||
-rw-r--r-- | Lib/idlelib/README.txt | 287 | ||||
-rw-r--r-- | Lib/idlelib/__init__.py | 2 | ||||
-rw-r--r-- | Lib/idlelib/__main__.py | 4 | ||||
-rw-r--r-- | Lib/idlelib/autocomplete.py (renamed from Lib/idlelib/AutoComplete.py) | 67 | ||||
-rw-r--r-- | Lib/idlelib/autocomplete_w.py (renamed from Lib/idlelib/AutoCompleteWindow.py) | 41 | ||||
-rw-r--r-- | Lib/idlelib/autoexpand.py (renamed from Lib/idlelib/AutoExpand.py) | 7 | ||||
-rw-r--r-- | Lib/idlelib/browser.py (renamed from Lib/idlelib/ClassBrowser.py) | 18 | ||||
-rw-r--r-- | Lib/idlelib/calltip_w.py (renamed from Lib/idlelib/CallTipWindow.py) | 8 | ||||
-rw-r--r-- | Lib/idlelib/calltips.py (renamed from Lib/idlelib/CallTips.py) | 12 | ||||
-rw-r--r-- | Lib/idlelib/codecontext.py (renamed from Lib/idlelib/CodeContext.py) | 12 | ||||
-rw-r--r-- | Lib/idlelib/colorizer.py (renamed from Lib/idlelib/ColorDelegator.py) | 29 | ||||
-rw-r--r-- | Lib/idlelib/config-keys.def | 51 | ||||
-rw-r--r-- | Lib/idlelib/config-main.def | 4 | ||||
-rw-r--r-- | Lib/idlelib/config.py (renamed from Lib/idlelib/configHandler.py) | 160 | ||||
-rw-r--r-- | Lib/idlelib/configHelpSourceEdit.py | 170 | ||||
-rw-r--r-- | Lib/idlelib/configSectionNameDialog.py | 98 | ||||
-rw-r--r-- | Lib/idlelib/config_key.py (renamed from Lib/idlelib/keybindingDialog.py) | 21 | ||||
-rw-r--r-- | Lib/idlelib/configdialog.py (renamed from Lib/idlelib/configDialog.py) | 131 | ||||
-rw-r--r-- | Lib/idlelib/debugger.py (renamed from Lib/idlelib/Debugger.py) | 21 | ||||
-rw-r--r-- | Lib/idlelib/debugger_r.py (renamed from Lib/idlelib/RemoteDebugger.py) | 8 | ||||
-rw-r--r-- | Lib/idlelib/debugobj.py (renamed from Lib/idlelib/ObjectBrowser.py) | 26 | ||||
-rw-r--r-- | Lib/idlelib/debugobj_r.py (renamed from Lib/idlelib/RemoteObjectBrowser.py) | 0 | ||||
-rw-r--r-- | Lib/idlelib/delegator.py (renamed from Lib/idlelib/Delegator.py) | 0 | ||||
-rw-r--r-- | Lib/idlelib/dynoption.py (renamed from Lib/idlelib/dynOptionMenuWidget.py) | 9 | ||||
-rw-r--r-- | Lib/idlelib/editor.py (renamed from Lib/idlelib/EditorWindow.py) | 276 | ||||
-rw-r--r-- | Lib/idlelib/filelist.py (renamed from Lib/idlelib/FileList.py) | 5 | ||||
-rw-r--r-- | Lib/idlelib/grep.py (renamed from Lib/idlelib/GrepDialog.py) | 48 | ||||
-rw-r--r-- | Lib/idlelib/help.py | 26 | ||||
-rw-r--r-- | Lib/idlelib/help.txt | 372 | ||||
-rw-r--r-- | Lib/idlelib/help_about.py (renamed from Lib/idlelib/aboutDialog.py) | 13 | ||||
-rw-r--r-- | Lib/idlelib/history.py (renamed from Lib/idlelib/IdleHistory.py) | 8 | ||||
-rw-r--r-- | Lib/idlelib/hyperparser.py (renamed from Lib/idlelib/HyperParser.py) | 7 | ||||
-rw-r--r-- | Lib/idlelib/idle.py | 3 | ||||
-rw-r--r-- | Lib/idlelib/idle.pyw | 12 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/__init__.py | 2 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/htest.py | 145 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/mock_idle.py | 4 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_autocomplete.py | 13 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_autoexpand.py | 6 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_calltips.py | 2 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_colorizer.py | 56 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_config.py | 160 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_config_help.py | 106 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_config_key.py | 33 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_config_name.py | 75 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_configdialog.py | 8 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_debugger.py | 29 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_delegator.py | 2 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_editmenu.py | 30 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_editor.py | 2 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_grep.py | 6 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_help.py | 34 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_help_about.py | 24 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_history.py (renamed from Lib/idlelib/idle_test/test_idlehistory.py) | 4 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_hyperparser.py | 6 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_iomenu.py (renamed from Lib/idlelib/idle_test/test_io.py) | 5 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_macosx.py | 103 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_paragraph.py (renamed from Lib/idlelib/idle_test/test_formatparagraph.py) | 8 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_parenmatch.py | 17 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_pathbrowser.py | 8 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_percolator.py | 4 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_query.py | 353 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_redirector.py (renamed from Lib/idlelib/idle_test/test_widgetredir.py) | 5 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_replace.py (renamed from Lib/idlelib/idle_test/test_replacedialog.py) | 16 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_rstrip.py | 4 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_scrolledlist.py | 29 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_search.py (renamed from Lib/idlelib/idle_test/test_searchdialog.py) | 8 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_searchbase.py (renamed from Lib/idlelib/idle_test/test_searchdialogbase.py) | 27 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_searchengine.py | 16 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_text.py | 21 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_textview.py | 20 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_tree.py | 36 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_undo.py (renamed from Lib/idlelib/idle_test/test_undodelegator.py) | 8 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_warning.py | 6 | ||||
-rw-r--r-- | Lib/idlelib/idlever.py | 12 | ||||
-rw-r--r-- | Lib/idlelib/iomenu.py (renamed from Lib/idlelib/IOBinding.py) | 95 | ||||
-rw-r--r-- | Lib/idlelib/macosx.py (renamed from Lib/idlelib/macosxSupport.py) | 99 | ||||
-rw-r--r-- | Lib/idlelib/mainmenu.py (renamed from Lib/idlelib/Bindings.py) | 4 | ||||
-rw-r--r-- | Lib/idlelib/multicall.py (renamed from Lib/idlelib/MultiCall.py) | 17 | ||||
-rw-r--r-- | Lib/idlelib/outwin.py (renamed from Lib/idlelib/OutputWindow.py) | 11 | ||||
-rw-r--r-- | Lib/idlelib/paragraph.py (renamed from Lib/idlelib/FormatParagraph.py) | 10 | ||||
-rw-r--r-- | Lib/idlelib/parenmatch.py (renamed from Lib/idlelib/ParenMatch.py) | 14 | ||||
-rw-r--r-- | Lib/idlelib/pathbrowser.py (renamed from Lib/idlelib/PathBrowser.py) | 11 | ||||
-rw-r--r-- | Lib/idlelib/percolator.py (renamed from Lib/idlelib/Percolator.py) | 13 | ||||
-rw-r--r-- | Lib/idlelib/pyparse.py (renamed from Lib/idlelib/PyParse.py) | 2 | ||||
-rwxr-xr-x | Lib/idlelib/pyshell.py (renamed from Lib/idlelib/PyShell.py) | 220 | ||||
-rw-r--r-- | Lib/idlelib/query.py | 308 | ||||
-rw-r--r-- | Lib/idlelib/redirector.py (renamed from Lib/idlelib/WidgetRedirector.py) | 21 | ||||
-rw-r--r-- | Lib/idlelib/replace.py (renamed from Lib/idlelib/ReplaceDialog.py) | 26 | ||||
-rw-r--r-- | Lib/idlelib/rpc.py | 26 | ||||
-rw-r--r-- | Lib/idlelib/rstrip.py (renamed from Lib/idlelib/RstripExtension.py) | 0 | ||||
-rw-r--r-- | Lib/idlelib/run.py | 159 | ||||
-rw-r--r-- | Lib/idlelib/runscript.py (renamed from Lib/idlelib/ScriptBinding.py) | 17 | ||||
-rw-r--r-- | Lib/idlelib/scrolledlist.py (renamed from Lib/idlelib/ScrolledList.py) | 21 | ||||
-rw-r--r-- | Lib/idlelib/search.py (renamed from Lib/idlelib/SearchDialog.py) | 29 | ||||
-rw-r--r-- | Lib/idlelib/searchbase.py (renamed from Lib/idlelib/SearchDialogBase.py) | 40 | ||||
-rw-r--r-- | Lib/idlelib/searchengine.py (renamed from Lib/idlelib/SearchEngine.py) | 5 | ||||
-rw-r--r-- | Lib/idlelib/stackviewer.py (renamed from Lib/idlelib/StackViewer.py) | 30 | ||||
-rw-r--r-- | Lib/idlelib/statusbar.py (renamed from Lib/idlelib/MultiStatusBar.py) | 35 | ||||
-rw-r--r-- | Lib/idlelib/tabbedpages.py | 26 | ||||
-rw-r--r-- | Lib/idlelib/textview.py (renamed from Lib/idlelib/textView.py) | 34 | ||||
-rw-r--r-- | Lib/idlelib/tooltip.py (renamed from Lib/idlelib/ToolTip.py) | 19 | ||||
-rw-r--r-- | Lib/idlelib/tree.py (renamed from Lib/idlelib/TreeWidget.py) | 23 | ||||
-rw-r--r-- | Lib/idlelib/undo.py (renamed from Lib/idlelib/UndoDelegator.py) | 26 | ||||
-rw-r--r-- | Lib/idlelib/windows.py (renamed from Lib/idlelib/WindowList.py) | 2 | ||||
-rw-r--r-- | Lib/idlelib/zoomheight.py (renamed from Lib/idlelib/ZoomHeight.py) | 6 |
107 files changed, 2721 insertions, 2133 deletions
diff --git a/Lib/idlelib/NEWS.txt b/Lib/idlelib/NEWS.txt index 72905c1..a3fc501 100644 --- a/Lib/idlelib/NEWS.txt +++ b/Lib/idlelib/NEWS.txt @@ -1,45 +1,101 @@ -What's New in IDLE 3.5.3? -========================= -*Release date: 2017-01-01?* +What's New in IDLE 3.6.0? +=========================== +*Release date: 2016-12-16?* - Issue #15308: Add 'interrupt execution' (^C) to Shell menu. Patch by Roger Serwy, updated by Bayard Randel. - Issue #27922: Stop IDLE tests from 'flashing' gui widgets on the screen. +- Issue #27891: Consistently group and sort imports within idlelib modules. + +- Issue #17642: add larger font sizes for classroom projection. + - Add version to title of IDLE help window. - Issue #25564: In section on IDLE -- console differences, mention that using exec means that __builtins__ is defined for each statement. +- Issue #27821: Fix 3.6.0a3 regression that prevented custom key sets + from being selected when no custom theme was defined. + - 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 #27621: Put query response validation error messages in the query + box itself instead of in a separate massagebox. Redo tests to match. + Add Mac OSX refinements. Original patch by Mark Roseman. + +- Issue #27620: Escape key now closes Query box as cancelled. + +- Issue #27609: IDLE: tab after initial whitespace should tab, not + autocomplete. This fixes problem with writing docstrings at least + twice indented. + +- Issue #27609: Explicitly return None when there are also non-None + returns. In a few cases, reverse a condition and eliminate a return. + - 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 #27173: Add 'IDLE Modern Unix' to the built-in key sets. + Make the default key set depend on the platform. + Add tests for the changes to the config module. + +- Issue #27452: add line counter and crc to IDLE configHandler test dump. + +- Issue #27477: IDLE search dialogs now use ttk widgets. + +- Issue #27173: Add 'IDLE Modern Unix' to the built-in key sets. + Make the default key set depend on the platform. + Add tests for the changes to the config module. + +- Issue #27452: make command line "idle-test> python test_help.py" work. + __file__ is relative when python is started in the file's directory. + - Issue #27452: add line counter and crc to IDLE configHandler test dump. +- Issue #27380: IDLE: add query.py with base Query dialog and ttk widgets. + Module had subclasses SectionName, ModuleName, and HelpSource, which are + used to get information from users by configdialog and file =>Load Module. + Each subclass has itw own validity checks. Using ModuleName allows users + to edit bad module names instead of starting over. + Add tests and delete the two files combined into the new one. + +- Issue #27372: Test_idle no longer changes the locale. + - 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. +- Issue #24137: Run IDLE, test_idle, and htest with tkinter default root disabled. + Fix code and tests that fail with this restriction. + Fix htests to not create a second and redundant root and mainloop. -What's New in IDLE 3.5.2? -========================= -*Release date: 2016-06-26* +- Issue #27310: Fix IDLE.app failure to launch on OS X due to vestigial import. - 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 #24750: Switch all scrollbars in IDLE to ttk versions. + Where needed, minimal tests are added to cover changes. + +- Issue #24759: IDLE requires tk 8.5 and availability ttk widgets. + Delete now unneeded tk version tests and code for older versions. + Add test for IDLE syntax colorizer. + +- Issue #27239: idlelib.macosx.isXyzTk functions initialize as needed. + +- Issue #27262: move Aqua unbinding code, which enable context menus, to maxosx. + - 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. +- Issue #27196: Stop 'ThemeChanged' 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 @@ -47,9 +103,26 @@ What's New in IDLE 3.5.2? - Issue #20567: Revise idle_test/README.txt with advice about avoiding tk warning messages from tests. Apply advice to several IDLE tests. +- Issue # 24225: Update idlelib/README.txt with new file names + and event handlers. + +- Issue #27156: Remove obsolete code not used by IDLE. Replacements: + 1. help.txt, replaced by help.html, is out-of-date and should not be used. + Its dedicated viewer has be replaced by the html viewer in help.py. + 2. 'import idlever; I = idlever.IDLE_VERSION' is the same as + 'import sys; I = version[:version.index(' ')]' + 3. After 'ob = stackviewer.VariablesTreeItem(*args)', + 'ob.keys()' == 'list(ob.object.keys). + 4. In macosc, runningAsOSXAPP == isAquaTk; idCarbonAquaTk == isCarbonTk + - Issue #27117: Make colorizer htest and turtledemo work with dark themes. Move code for configuring text widget colors to a new function. +- Issue #24225: Rename many idlelib/*.py and idle_test/test_*.py files. + Edit files to replace old names with new names when the old name + referred to the module rather than the class it contained. + See the issue and IDLE section in What's New in 3.6 for more. + - Issue #26673: When tk reports font size as 0, change to size 10. Such fonts on Linux prevented the configuration dialog from opening. @@ -62,8 +135,8 @@ What's New in IDLE 3.5.2? - 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 #21703: Add test for undo delegator. Patch mostly by + Saimadhav Heblikar . - Issue #27044: Add ConfigDialog.remove_var_callbacks to stop memory leaks. @@ -82,11 +155,6 @@ What's New in IDLE 3.5.2? 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). This prevents the RuntimeErrors that were being caught and ignored. diff --git a/Lib/idlelib/README.txt b/Lib/idlelib/README.txt index ff44504..51e8ef5 100644 --- a/Lib/idlelib/README.txt +++ b/Lib/idlelib/README.txt @@ -29,61 +29,61 @@ idle.pyw Implementation -------------- -AutoComplete.py # Complete attribute names or filenames. -AutoCompleteWindow.py # Display completions. -AutoExpand.py # Expand word with previous word in file. -Bindings.py # Define most of IDLE menu. -CallTipWindow.py # Display calltip. -CallTips.py # Create calltip text. -ClassBrowser.py # Create module browser window. -CodeContext.py # Show compound statement headers otherwise not visible. -ColorDelegator.py # Colorize text (nim). -Debugger.py # Debug code run from editor; show window. -Delegator.py # Define base class for delegators (nim). -EditorWindow.py # Define most of editor and utility functions. -FileList.py # Open files and manage list of open windows (nim). -FormatParagraph.py# Re-wrap multiline strings and comments. -GrepDialog.py # Find all occurrences of pattern in multiple files. -HyperParser.py # Parse code around a given index. -IOBinding.py # Open, read, and write files -IdleHistory.py # Get previous or next user input in shell (nim) -MultiCall.py # Wrap tk widget to allow multiple calls per event (nim). -MultiStatusBar.py # Define status bar for windows (nim). -ObjectBrowser.py # Define class used in StackViewer (nim). -OutputWindow.py # Create window for grep output. -ParenMatch.py # Match fenceposts: (), [], and {}. -PathBrowser.py # Create path browser window. -Percolator.py # Manage delegator stack (nim). -PyParse.py # Give information on code indentation -PyShell.py # Start IDLE, manage shell, complete editor window -RemoteDebugger.py # Debug code run in remote process. -RemoteObjectBrowser.py # Communicate objects between processes with rpc (nim). -ReplaceDialog.py # Search and replace pattern in text. -RstripExtension.py# Strip trailing whitespace -ScriptBinding.py # Check and run user code. -ScrolledList.py # Define ScrolledList widget for IDLE (nim). -SearchDialog.py # Search for pattern in text. -SearchDialogBase.py # Define base for search, replace, and grep dialogs. -SearchEngine.py # Define engine for all 3 search dialogs. -StackViewer.py # View stack after exception. -TreeWidget.py # Define tree widger, used in browsers (nim). -UndoDelegator.py # Manage undo stack. -WidgetRedirector.py # Intercept widget subcommands (for percolator) (nim). -WindowList.py # Manage window list and define listed top level. -ZoomHeight.py # Zoom window to full height of screen. -aboutDialog.py # Display About IDLE dialog. -configDialog.py # Display user configuration dialogs. -configHandler.py # Load, fetch, and save configuration (nim). -configHelpSourceEdit.py # Specify help source. -configSectionNameDialog.py # Spefify user config section name -dynOptionMenuWidget.py # define mutable OptionMenu widget (nim). +autocomplete.py # Complete attribute names or filenames. +autocomplete_w.py # Display completions. +autoexpand.py # Expand word with previous word in file. +browser.py # Create module browser window. +calltip_w.py # Display calltip. +calltips.py # Create calltip text. +codecontext.py # Show compound statement headers otherwise not visible. +colorizer.py # Colorize text (nim) +config.py # Load, fetch, and save configuration (nim). +configdialog.py # Display user configuration dialogs. +config_help.py # Specify help source in configdialog. +config_key.py # Change keybindings. +dynoption.py # Define mutable OptionMenu widget (nim). +debugobj.py # Define class used in stackviewer. +debugobj_r.py # Communicate objects between processes with rpc (nim). +debugger.py # Debug code run from shell or editor; show window. +debugger_r.py # Debug code run in remote process. +delegator.py # Define base class for delegators (nim). +editor.py # Define most of editor and utility functions. +filelist.py # Open files and manage list of open windows (nim). +grep.py # Find all occurrences of pattern in multiple files. help.py # Display IDLE's html doc. -keybindingDialog.py # Change keybindings. -macosxSupport.py # Help IDLE run on Macs (nim). +help_about.py # Display About IDLE dialog. +history.py # Get previous or next user input in shell (nim) +hyperparser.py # Parse code around a given index. +iomenu.py # Open, read, and write files +macosx.py # Help IDLE run on Macs (nim). +mainmenu.py # Define most of IDLE menu. +multicall.py # Wrap tk widget to allow multiple calls per event (nim). +outwin.py # Create window for grep output. +paragraph.py # Re-wrap multiline strings and comments. +parenmatch.py # Match fenceposts: (), [], and {}. +pathbrowser.py # Create path browser window. +percolator.py # Manage delegator stack (nim). +pyparse.py # Give information on code indentation +pyshell.py # Start IDLE, manage shell, complete editor window +query.py # Query user for information +redirector.py # Intercept widget subcommands (for percolator) (nim). +replace.py # Search and replace pattern in text. rpc.py # Commuicate between idle and user processes (nim). +rstrip.py # Strip trailing whitespace. run.py # Manage user code execution subprocess. +runscript.py # Check and run user code. +scrolledlist.py # Define scrolledlist widget for IDLE (nim). +search.py # Search for pattern in text. +searchbase.py # Define base for search, replace, and grep dialogs. +searchengine.py # Define engine for all 3 search dialogs. +stackviewer.py # View stack after exception. +statusbar.py # Define status bar for windows (nim). tabbedpages.py # Define tabbed pages widget (nim). -textView.py # Define read-only text widget (nim). +textview.py # Define read-only text widget (nim). +tree.py # Define tree widger, used in browsers (nim). +undo.py # Manage undo stack. +windows.py # Manage window list and define listed top level. +zoomheight.py # Zoom window to full height of screen. Configuration ------------- @@ -104,127 +104,148 @@ help.html # copy of idle.html in docs, displayed by IDLE Help Subdirectories -------------- -Icons # small image files -idle_test # files for human test and automated unit tests +Icons # small image files +idle_test # files for human test and automated unit tests Unused and Deprecated files and objects (nim) --------------------------------------------- -EditorWindow.py: Helpdialog and helpDialog -ToolTip.py: unused. -help.txt -idlever.py +tooltip.py # unused + IDLE MENUS -Top level items and most submenu items are defined in Bindings. +Top level items and most submenu items are defined in mainmenu. Extenstions add submenu items when active. The names given are found, quoted, in one of these modules, paired with a '<<pseudoevent>>'. Each pseudoevent is bound to an event handler. Some event handlers call another function that does the actual work. The annotations below are intended to at least give the module where the actual work is done. +'eEW' = editor.EditorWindow -File # IOBindig except as noted - New File - Open... # IOBinding.open - Open Module +File + New File # eEW.new_callback + Open... # iomenu.open + Open Module # eEw.open_module Recent Files - Class Browser # Class Browser - Path Browser # Path Browser + Class Browser # eEW.open_class_browser, browser.ClassBrowser + Path Browser # eEW.open_path_browser, pathbrowser --- - Save # IDBinding.save - Save As... # IOBinding.save_as - Save Copy As... # IOBindling.save_a_copy + Save # iomenu.save + Save As... # iomenu.save_as + Save Copy As... # iomenu.save_a_copy --- - Print Window # IOBinding.print_window + Print Window # iomenu.print_window --- - Close - Exit + Close # eEW.close_event + Exit # flist.close_all_callback (bound in eEW) Edit - Undo # undoDelegator - Redo # undoDelegator - --- - Cut - Copy - Paste - Select All - --- # Next 5 items use SearchEngine; dialogs use SearchDialogBase - Find # Search Dialog - Find Again - Find Selection - Find in Files... # GrepDialog - Replace... # ReplaceDialog - Go to Line - Show Completions # AutoComplete extension and AutoCompleteWidow (&HP) - Expand Word # AutoExpand extension - Show call tip # Calltips extension and CalltipWindow (& Hyperparser) - Show surrounding parens # ParenMatch (& Hyperparser) - -Shell # PyShell - View Last Restart # PyShell.PyShell.view_restart_mark - Restart Shell # PyShell.PyShell.restart_shell + Undo # undodelegator + Redo # undodelegator + --- # eEW.right_menu_event + Cut # eEW.cut + Copy # eEW.copy + Paste # eEW.past + Select All # eEW.select_all (+ see eEW.remove_selection) + --- # Next 5 items use searchengine; dialogs use searchbase + Find # eEW.find_event, search.SearchDialog.find + Find Again # eEW.find_again_event, sSD.find_again + Find Selection # eEW.find_selection_event, sSD.find_selection + Find in Files... # eEW.find_in_files_event, grep + Replace... # eEW.replace_event, replace.ReplaceDialog.replace + Go to Line # eEW.goto_line_event + Show Completions # autocomplete extension and autocompleteWidow (&HP) + Expand Word # autoexpand extension + Show call tip # Calltips extension and CalltipWindow (& Hyperparser) + Show surrounding parens # parenmatch (& Hyperparser) + +Shell # pyshell + View Last Restart # pyshell.PyShell.view_restart_mark + Restart Shell # pyshell.PyShell.restart_shell Interrupt Execution # pyshell.PyShell.cancel_callback Debug (Shell only) Go to File/Line - Debugger # Debugger, RemoteDebugger, PyShell.toggle_debuger - Stack Viewer # StackViewer, PyShell.open_stack_viewer - Auto-open Stack Viewer # StackViewer + debugger # debugger, debugger_r, PyShell.toggle_debuger + Stack Viewer # stackviewer, PyShell.open_stack_viewer + Auto-open Stack Viewer # stackviewer Format (Editor only) - Indent Region - Dedent Region - Comment Out Region - Uncomment Region - Tabify Region - Untabify Region - Toggle Tabs - New Indent Width - Format Paragraph # FormatParagraph extension + Indent Region # eEW.indent_region_event + Dedent Region # eEW.dedent_region_event + Comment Out Reg. # eEW.comment_region_event + Uncomment Region # eEW.uncomment_region_event + Tabify Region # eEW.tabify_region_event + Untabify Region # eEW.untabify_region_event + Toggle Tabs # eEW.toggle_tabs_event + New Indent Width # eEW.change_indentwidth_event + Format Paragraph # paragraph extension --- - Strip tailing whitespace # RstripExtension extension + Strip tailing whitespace # rstrip extension Run (Editor only) - Python Shell # PyShell + Python Shell # pyshell --- - Check Module # ScriptBinding - Run Module # ScriptBinding + Check Module # runscript + Run Module # runscript Options - Configure IDLE # configDialog + Configure IDLE # eEW.config_dialog, configdialog (tabs in the dialog) - Font tab # onfig-main.def - Highlight tab # configSectionNameDialog, config-highlight.def - Keys tab # keybindingDialog, configSectionNameDialog, onfig-keus.def - General tab # configHelpSourceEdit, config-main.def - Configure Extensions # configDialog - Xyz tab # xyz.py, config-extensions.def + Font tab # config-main.def + Highlight tab # query, config-highlight.def + Keys tab # query, config_key, config_keys.def + General tab # config_help, config-main.def + Extensions tab # config-extensions.def, corresponding .py --- - Code Context (editor only) # CodeContext extension + Code Context (ed)# codecontext extension Window - Zoomheight # ZoomHeight extension + Zoomheight # zoomheight extension --- - <open windows> # WindowList + <open windows> # windows Help - About IDLE # aboutDialog + About IDLE # eEW.about_dialog, help_about.AboutDialog --- - IDLE Help # help - Python Doc - Turtle Demo + IDLE Help # eEW.help_dialog, helpshow_idlehelp + Python Doc # eEW.python_docs + Turtle Demo # eEW.open_turtle_demo --- <other help sources> <Context Menu> (right click) -Defined in EditorWindow, PyShell, Output - Cut - Copy - Paste - --- - Go to file/line (shell and output only) - Set Breakpoint (editor only) - Clear Breakpoint (editor only) - Defined in Debugger - Go to source line - Show stack frame + Defined in editor, PyShelpyshellut + Cut + Copy + Paste + --- + Go to file/line (shell and output only) + Set Breakpoint (editor only) + Clear Breakpoint (editor only) + Defined in debugger + Go to source line + Show stack frame + +<No menu> +Center Insert # eEW.center_insert_event + + +CODE STYLE -- Generally PEP 8. + +import +------ +Put import at the top, unless there is a good reason otherwise. +PEP 8 says to group stdlib, 3rd-party dependencies, and package imports. +For idlelib, the groups are general stdlib, tkinter, and idlelib. +Sort modules within each group, except that tkinter.ttk follows tkinter. +Sort 'from idlelib import mod1' and 'from idlelib.mod2 import object' +together by module, ignoring within module objects. +Put 'import __main__' after other idlelib imports. + +Imports only needed for testing are put not at the top but in an +htest function def or "if __name__ == '__main__'" clause. + +Within module imports like "from idlelib.mod import class" may cause +circular imports to deadlock. Even without this, circular imports may +require at least one of the imports to be delayed until a function call. diff --git a/Lib/idlelib/__init__.py b/Lib/idlelib/__init__.py index 711f61b..791ddea 100644 --- a/Lib/idlelib/__init__.py +++ b/Lib/idlelib/__init__.py @@ -1,8 +1,10 @@ """The idlelib package implements the Idle application. Idle includes an interactive shell and editor. +Starting with Python 3.6, IDLE requires tcl/tk 8.5 or later. Use the files named idle.* to start Idle. The other files are private implementations. Their details are subject to change. See PEP 434 for more. Import them at your own risk. """ +testing = False # Set True by test.test_idle. diff --git a/Lib/idlelib/__main__.py b/Lib/idlelib/__main__.py index 2edf5f7..6349ec7 100644 --- a/Lib/idlelib/__main__.py +++ b/Lib/idlelib/__main__.py @@ -3,6 +3,6 @@ IDLE main entry point Run IDLE as python -m idlelib """ -import idlelib.PyShell -idlelib.PyShell.main() +import idlelib.pyshell +idlelib.pyshell.main() # This file does not work for 2.7; See issue 24212. diff --git a/Lib/idlelib/AutoComplete.py b/Lib/idlelib/autocomplete.py index ff085d5..1e44fa5 100644 --- a/Lib/idlelib/AutoComplete.py +++ b/Lib/idlelib/autocomplete.py @@ -1,29 +1,30 @@ -"""AutoComplete.py - An IDLE extension for automatically completing names. +"""autocomplete.py - An IDLE extension for automatically completing names. 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 -import sys import string +import sys -from idlelib.configHandler import idleConf - -# This string includes all chars that may be in an identifier -ID_CHARS = string.ascii_letters + string.digits + "_" - -# These constants represent the two different types of completions +# These constants represent the two different types of completions. +# They must be defined here so autocomple_w can import them. COMPLETE_ATTRIBUTES, COMPLETE_FILES = range(1, 2+1) -from idlelib import AutoCompleteWindow -from idlelib.HyperParser import HyperParser - +from idlelib import autocomplete_w +from idlelib.config import idleConf +from idlelib.hyperparser import HyperParser import __main__ +# This string includes all chars that may be in an identifier. +# TODO Update this here and elsewhere. +ID_CHARS = string.ascii_letters + string.digits + "_" + SEPS = os.sep if os.altsep: # e.g. '/' on Windows... SEPS += os.altsep + class AutoComplete: menudefs = [ @@ -37,19 +38,17 @@ class AutoComplete: def __init__(self, editwin=None): self.editwin = editwin - if editwin is None: # subprocess and test - return - self.text = editwin.text - self.autocompletewindow = None - - # id of delayed call, and the index of the text insert when the delayed - # call was issued. If _delayed_completion_id is None, there is no - # delayed call. - self._delayed_completion_id = None - self._delayed_completion_index = None + if editwin is not None: # not in subprocess or test + self.text = editwin.text + self.autocompletewindow = None + # id of delayed call, and the index of the text insert when + # the delayed call was issued. If _delayed_completion_id is + # None, there is no delayed call. + self._delayed_completion_id = None + self._delayed_completion_index = None def _make_autocomplete_window(self): - return AutoCompleteWindow.AutoCompleteWindow(self.text) + return autocomplete_w.AutoCompleteWindow(self.text) def _remove_autocomplete_window(self, event=None): if self.autocompletewindow: @@ -80,16 +79,17 @@ class AutoComplete: open a completion list after that (if there is more than one completion) """ - if hasattr(event, "mc_state") and event.mc_state: - # A modifier was pressed along with the tab, continue as usual. - return + if hasattr(event, "mc_state") and event.mc_state or\ + not self.text.get("insert linestart", "insert").strip(): + # A modifier was pressed along with the tab or + # there is only previous whitespace on this line, so tab. + return None if self.autocompletewindow and self.autocompletewindow.is_active(): self.autocompletewindow.complete() return "break" else: opened = self.open_completions(False, True, True) - if opened: - return "break" + return "break" if opened else None def _open_completions_later(self, *args): self._delayed_completion_index = self.text.index("insert") @@ -101,9 +101,8 @@ class AutoComplete: def _delayed_open_completions(self, *args): self._delayed_completion_id = None - if self.text.index("insert") != self._delayed_completion_index: - return - self.open_completions(*args) + if self.text.index("insert") == self._delayed_completion_index: + self.open_completions(*args) def open_completions(self, evalfuncs, complete, userWantsWin, mode=None): """Find the completions and create the AutoCompleteWindow. @@ -148,17 +147,17 @@ class AutoComplete: comp_what = hp.get_expression() if not comp_what or \ (not evalfuncs and comp_what.find('(') != -1): - return + return None else: comp_what = "" else: - return + return None if complete and not comp_what and not comp_start: - return + return None comp_lists = self.fetch_completions(comp_what, mode) if not comp_lists[0]: - return + return None self.autocompletewindow = self._make_autocomplete_window() return not self.autocompletewindow.show_window( comp_lists, "insert-%dc" % len(comp_start), diff --git a/Lib/idlelib/AutoCompleteWindow.py b/Lib/idlelib/autocomplete_w.py index 2ee6878..3374c6e 100644 --- a/Lib/idlelib/AutoCompleteWindow.py +++ b/Lib/idlelib/autocomplete_w.py @@ -1,9 +1,11 @@ """ -An auto-completion window for IDLE, used by the AutoComplete extension +An auto-completion window for IDLE, used by the autocomplete extension """ from tkinter import * -from idlelib.MultiCall import MC_SHIFT -from idlelib.AutoComplete import COMPLETE_FILES, COMPLETE_ATTRIBUTES +from tkinter.ttk import Scrollbar + +from idlelib.autocomplete import COMPLETE_FILES, COMPLETE_ATTRIBUTES +from idlelib.multicall import MC_SHIFT HIDE_VIRTUAL_EVENT_NAME = "<<autocompletewindow-hide>>" HIDE_SEQUENCES = ("<FocusOut>", "<ButtonPress>") @@ -34,8 +36,8 @@ class AutoCompleteWindow: self.completions = None # A list with more completions, or None self.morecompletions = None - # The completion mode. Either AutoComplete.COMPLETE_ATTRIBUTES or - # AutoComplete.COMPLETE_FILES + # The completion mode. Either autocomplete.COMPLETE_ATTRIBUTES or + # autocomplete.COMPLETE_FILES self.mode = None # The current completion start, on the text box (a string) self.start = None @@ -215,6 +217,7 @@ class AutoCompleteWindow: self.winconfigid = acw.bind(WINCONFIG_SEQUENCE, self.winconfig_event) self.doubleclickid = listbox.bind(DOUBLECLICK_SEQUENCE, self.doubleclick_event) + return None def winconfig_event(self, event): if not self.is_active(): @@ -238,16 +241,14 @@ class AutoCompleteWindow: acw.wm_geometry("+%d+%d" % (new_x, new_y)) def hide_event(self, event): - if not self.is_active(): - return - self.hide_window() + if self.is_active(): + self.hide_window() def listselect_event(self, event): - if not self.is_active(): - return - self.userwantswindow = True - cursel = int(self.listbox.curselection()[0]) - self._change_start(self.completions[cursel]) + if self.is_active(): + self.userwantswindow = True + cursel = int(self.listbox.curselection()[0]) + self._change_start(self.completions[cursel]) def doubleclick_event(self, event): # Put the selected completion in the text, and close the list @@ -257,7 +258,7 @@ class AutoCompleteWindow: def keypress_event(self, event): if not self.is_active(): - return + return None keysym = event.keysym if hasattr(event, "mc_state"): state = event.mc_state @@ -282,7 +283,7 @@ class AutoCompleteWindow: # keysym == "BackSpace" if len(self.start) == 0: self.hide_window() - return + return None self._change_start(self.start[:-1]) self.lasttypedstart = self.start self.listbox.select_clear(0, int(self.listbox.curselection()[0])) @@ -292,7 +293,7 @@ class AutoCompleteWindow: elif keysym == "Return": self.hide_window() - return + return None elif (self.mode == COMPLETE_ATTRIBUTES and keysym in ("period", "space", "parenleft", "parenright", "bracketleft", @@ -308,7 +309,7 @@ class AutoCompleteWindow: and (self.mode == COMPLETE_ATTRIBUTES or self.start): self._change_start(self.completions[cursel]) self.hide_window() - return + return None elif keysym in ("Home", "End", "Prior", "Next", "Up", "Down") and \ not state: @@ -349,12 +350,12 @@ class AutoCompleteWindow: # first tab; let AutoComplete handle the completion self.userwantswindow = True self.lastkey_was_tab = True - return + return None elif any(s in keysym for s in ("Shift", "Control", "Alt", "Meta", "Command", "Option")): # A modifier key, so ignore - return + return None elif event.char and event.char >= ' ': # Regular character with a non-length-1 keycode @@ -368,7 +369,7 @@ class AutoCompleteWindow: else: # Unknown event, close the window and let it through. self.hide_window() - return + return None def keyrelease_event(self, event): if not self.is_active(): diff --git a/Lib/idlelib/AutoExpand.py b/Lib/idlelib/autoexpand.py index 7059054..6b46bee 100644 --- a/Lib/idlelib/AutoExpand.py +++ b/Lib/idlelib/autoexpand.py @@ -12,8 +12,8 @@ its state. This is an extension file and there is only one instance of AutoExpand. ''' -import string import re +import string ###$ event <<expand-word>> ###$ win <Alt-slash> @@ -31,6 +31,7 @@ class AutoExpand: def __init__(self, editwin): self.text = editwin.text + self.bell = self.text.bell self.state = None def expand_word_event(self, event): @@ -46,14 +47,14 @@ class AutoExpand: words = self.getwords() index = 0 if not words: - self.text.bell() + self.bell() return "break" word = self.getprevword() self.text.delete("insert - %d chars" % len(word), "insert") newword = words[index] index = (index + 1) % len(words) if index == 0: - self.text.bell() # Warn we cycled around + self.bell() # Warn we cycled around self.text.insert("insert", newword) curinsert = self.text.index("insert") curline = self.text.get("insert linestart", "insert lineend") diff --git a/Lib/idlelib/ClassBrowser.py b/Lib/idlelib/browser.py index d09c52f..ea05638 100644 --- a/Lib/idlelib/ClassBrowser.py +++ b/Lib/idlelib/browser.py @@ -11,16 +11,16 @@ XXX TO DO: """ import os -import sys import pyclbr +import sys -from idlelib import PyShell -from idlelib.WindowList import ListedToplevel -from idlelib.TreeWidget import TreeNode, TreeItem, ScrolledCanvas -from idlelib.configHandler import idleConf +from idlelib.config import idleConf +from idlelib import pyshell +from idlelib.tree import TreeNode, TreeItem, ScrolledCanvas +from idlelib.windows import ListedToplevel file_open = None # Method...Item and Class...Item use this. -# Normally PyShell.flist.open, but there is no PyShell.flist for htest. +# Normally pyshell.flist.open, but there is no pyshell.flist for htest. class ClassBrowser: @@ -32,7 +32,7 @@ class ClassBrowser: """ global file_open if not _htest: - file_open = PyShell.flist.open + file_open = pyshell.flist.open self.name = name self.file = os.path.join(path[0], self.name + ".py") self._htest = _htest @@ -95,7 +95,7 @@ class ModuleBrowserTreeItem(TreeItem): return if not os.path.exists(self.file): return - PyShell.flist.open(self.file) + pyshell.flist.open(self.file) def IsExpandable(self): return os.path.normcase(self.file[-3:]) == ".py" @@ -226,7 +226,7 @@ def _class_browser(parent): #Wrapper for htest file = sys.argv[0] dir, file = os.path.split(file) name = os.path.splitext(file)[0] - flist = PyShell.PyShellFileList(parent) + flist = pyshell.PyShellFileList(parent) global file_open file_open = flist.open ClassBrowser(flist, name, [dir], _htest=True) diff --git a/Lib/idlelib/CallTipWindow.py b/Lib/idlelib/calltip_w.py index 9eec175..c7361d1 100644 --- a/Lib/idlelib/CallTipWindow.py +++ b/Lib/idlelib/calltip_w.py @@ -1,7 +1,7 @@ """A CallTip window class for Tkinter/IDLE. -After ToolTip.py, which uses ideas gleaned from PySol -Used by the CallTips IDLE extension. +After tooltip.py, which uses ideas gleaned from PySol +Used by the calltips IDLE extension. """ from tkinter import Toplevel, Label, LEFT, SOLID, TclError @@ -138,8 +138,8 @@ def _calltip_window(parent): # htest # top = Toplevel(parent) top.title("Test calltips") - top.geometry("200x100+%d+%d" % (parent.winfo_rootx() + 200, - parent.winfo_rooty() + 150)) + x, y = map(int, parent.geometry().split('+')[1:]) + top.geometry("200x100+%d+%d" % (x + 250, y + 175)) text = Text(top) text.pack(side=LEFT, fill=BOTH, expand=1) text.insert("insert", "string.split") diff --git a/Lib/idlelib/CallTips.py b/Lib/idlelib/calltips.py index 81bd5f1..4c5aea2 100644 --- a/Lib/idlelib/CallTips.py +++ b/Lib/idlelib/calltips.py @@ -1,19 +1,19 @@ -"""CallTips.py - An IDLE Extension to Jog Your Memory +"""calltips.py - An IDLE Extension to Jog Your Memory Call Tips are floating windows which display function, class, and method parameter and docstring information when you type an opening parenthesis, and which disappear when you type a closing parenthesis. """ -import __main__ import inspect import re import sys import textwrap import types -from idlelib import CallTipWindow -from idlelib.HyperParser import HyperParser +from idlelib import calltip_w +from idlelib.hyperparser import HyperParser +import __main__ class CallTips: @@ -37,7 +37,7 @@ class CallTips: def _make_tk_calltip_window(self): # See __init__ for usage - return CallTipWindow.CallTip(self.text) + return calltip_w.CallTip(self.text) def _remove_calltip_window(self, event=None): if self.active_calltip: @@ -120,7 +120,7 @@ def get_entity(expression): _MAX_COLS = 85 _MAX_LINES = 5 # enough for bytes _INDENT = ' '*4 # for wrapped signatures -_first_param = re.compile('(?<=\()\w*\,?\s*') +_first_param = re.compile(r'(?<=\()\w*\,?\s*') _default_callable_argspec = "See source or doc" diff --git a/Lib/idlelib/CodeContext.py b/Lib/idlelib/codecontext.py index 7d25ada..f25e1b3 100644 --- a/Lib/idlelib/CodeContext.py +++ b/Lib/idlelib/codecontext.py @@ -1,19 +1,21 @@ -"""CodeContext - Extension to display the block context above the edit window +"""codecontext - Extension to display the block context above the edit window Once code has scrolled off the top of a window, it can be difficult to determine which block you are in. This extension implements a pane at the top of each IDLE edit window which provides block structure hints. These hints are the lines which contain the block opening keywords, e.g. 'if', for the enclosing block. The number of hint lines is determined by the numlines -variable in the CodeContext section of config-extensions.def. Lines which do +variable in the codecontext section of config-extensions.def. Lines which do not open blocks are not shown in the context hints pane. """ -import tkinter -from tkinter.constants import TOP, LEFT, X, W, SUNKEN import re from sys import maxsize as INFINITY -from idlelib.configHandler import idleConf + +import tkinter +from tkinter.constants import TOP, LEFT, X, W, SUNKEN + +from idlelib.config import idleConf BLOCKOPENERS = {"class", "def", "elif", "else", "except", "finally", "for", "if", "try", "while", "with"} diff --git a/Lib/idlelib/ColorDelegator.py b/Lib/idlelib/colorizer.py index 02eac47..7310bb2 100644 --- a/Lib/idlelib/ColorDelegator.py +++ b/Lib/idlelib/colorizer.py @@ -1,10 +1,10 @@ -import time -import re -import keyword import builtins -from tkinter import TkVersion -from idlelib.Delegator import Delegator -from idlelib.configHandler import idleConf +import keyword +import re +import time + +from idlelib.config import idleConf +from idlelib.delegator import Delegator DEBUG = False @@ -49,11 +49,8 @@ def color_config(text): # Called from htest, Editor, and Turtle Demo. insertbackground=cursor_color, selectforeground=select_colors['foreground'], selectbackground=select_colors['background'], - ) - if TkVersion >= 8.5: - text.config( - inactiveselectbackground=select_colors['background']) - + inactiveselectbackground=select_colors['background'], # new in 8.5 + ) class ColorDelegator(Delegator): @@ -259,12 +256,12 @@ class ColorDelegator(Delegator): def _color_delegator(parent): # htest # from tkinter import Toplevel, Text - from idlelib.Percolator import Percolator + from idlelib.percolator import Percolator top = Toplevel(parent) top.title("Test ColorDelegator") - top.geometry("200x100+%d+%d" % (parent.winfo_rootx() + 200, - parent.winfo_rooty() + 150)) + x, y = map(int, parent.geometry().split('+')[1:]) + top.geometry("200x100+%d+%d" % (x + 250, y + 175)) source = "if somename: x = 'abc' # comment\nprint\n" text = Text(top, background="white") text.pack(expand=1, fill="both") @@ -277,5 +274,9 @@ def _color_delegator(parent): # htest # p.insertfilter(d) if __name__ == "__main__": + import unittest + unittest.main('idlelib.idle_test.test_colorizer', + verbosity=2, exit=False) + from idlelib.idle_test.htest import run run(_color_delegator) diff --git a/Lib/idlelib/config-keys.def b/Lib/idlelib/config-keys.def index 3bfcb69..64788f9 100644 --- a/Lib/idlelib/config-keys.def +++ b/Lib/idlelib/config-keys.def @@ -109,6 +109,57 @@ change-indentwidth=<Alt-Key-u> del-word-left=<Alt-Key-BackSpace> del-word-right=<Alt-Key-d> +[IDLE Modern Unix] +copy = <Control-Shift-Key-C> <Control-Key-Insert> +cut = <Control-Key-x> <Shift-Key-Delete> +paste = <Control-Key-v> <Shift-Key-Insert> +beginning-of-line = <Key-Home> +center-insert = <Control-Key-l> +close-all-windows = <Control-Key-q> +close-window = <Control-Key-w> <Control-Shift-Key-W> +do-nothing = <Control-Key-F12> +end-of-file = <Control-Key-d> +history-next = <Alt-Key-n> <Meta-Key-n> +history-previous = <Alt-Key-p> <Meta-Key-p> +interrupt-execution = <Control-Key-c> +view-restart = <Key-F6> +restart-shell = <Control-Key-F6> +open-class-browser = <Control-Key-b> +open-module = <Control-Key-m> +open-new-window = <Control-Key-n> +open-window-from-file = <Control-Key-o> +plain-newline-and-indent = <Control-Key-j> +print-window = <Control-Key-p> +python-context-help = <Shift-Key-F1> +python-docs = <Key-F1> +redo = <Control-Shift-Key-Z> +remove-selection = <Key-Escape> +save-copy-of-window-as-file = <Alt-Shift-Key-S> +save-window-as-file = <Control-Shift-Key-S> +save-window = <Control-Key-s> +select-all = <Control-Key-a> +toggle-auto-coloring = <Control-Key-slash> +undo = <Control-Key-z> +find = <Control-Key-f> +find-again = <Key-F3> +find-in-files = <Control-Shift-Key-f> +find-selection = <Control-Key-h> +replace = <Control-Key-r> +goto-line = <Control-Key-g> +smart-backspace = <Key-BackSpace> +newline-and-indent = <Key-Return> <Key-KP_Enter> +smart-indent = <Key-Tab> +indent-region = <Control-Key-bracketright> +dedent-region = <Control-Key-bracketleft> +comment-region = <Control-Key-d> +uncomment-region = <Control-Shift-Key-D> +tabify-region = <Alt-Key-5> +untabify-region = <Alt-Key-6> +toggle-tabs = <Control-Key-T> +change-indentwidth = <Alt-Key-u> +del-word-left = <Control-Key-BackSpace> +del-word-right = <Control-Key-Delete> + [IDLE Classic Mac] copy=<Command-Key-c> cut=<Command-Key-x> diff --git a/Lib/idlelib/config-main.def b/Lib/idlelib/config-main.def index 8ebbc1b..a61bba7 100644 --- a/Lib/idlelib/config-main.def +++ b/Lib/idlelib/config-main.def @@ -70,7 +70,9 @@ name2= [Keys] default= 1 -name= IDLE Classic Windows +name= +name2= +# name2 set in user config-main.cfg for keys added after 2016 July 1 [History] cyclic=1 diff --git a/Lib/idlelib/configHandler.py b/Lib/idlelib/config.py index 8954488..10fe3ba 100644 --- a/Lib/idlelib/configHandler.py +++ b/Lib/idlelib/config.py @@ -7,7 +7,7 @@ duplicate the defaults will be removed from the user's configuration files, and if a file becomes empty, it will be deleted. The contents of the user files may be altered using the Options/Configure IDLE -menu to access the configuration GUI (configDialog.py), or manually. +menu to access the configuration GUI (configdialog.py), or manually. Throughout this module there is an emphasis on returning useable defaults when a problem occurs in returning a requested configuration value back to @@ -18,11 +18,10 @@ configuration problem notification and resolution. """ # TODOs added Oct 2014, tjr +from configparser import ConfigParser import os import sys -from configparser import ConfigParser -from tkinter import TkVersion from tkinter.font import Font, nametofont class InvalidConfigType(Exception): pass @@ -230,15 +229,12 @@ class IdleConf: return self.userCfg[configType].Get(section, option, type=type, raw=raw) except ValueError: - warning = ('\n Warning: configHandler.py - IdleConf.GetOption -\n' + warning = ('\n Warning: config.py - IdleConf.GetOption -\n' ' invalid %r value for configuration option %r\n' ' from section %r: %r' % (type, option, section, self.userCfg[configType].Get(section, option, raw=raw))) - try: - print(warning, file=sys.stderr) - except OSError: - pass + _warn(warning, configType, section, option) try: if self.defaultCfg[configType].has_option(section,option): return self.defaultCfg[configType].Get( @@ -247,15 +243,12 @@ class IdleConf: pass #returning default, print warning if warn_on_default: - warning = ('\n Warning: configHandler.py - IdleConf.GetOption -\n' + warning = ('\n Warning: config.py - IdleConf.GetOption -\n' ' problem retrieving configuration option %r\n' ' from section %r.\n' ' returning default value: %r' % (option, section, default)) - try: - print(warning, file=sys.stderr) - except OSError: - pass + _warn(warning, configType, section, option) return default def SetOption(self, configType, section, option, value): @@ -358,52 +351,73 @@ class IdleConf: for element in theme: if not cfgParser.has_option(themeName, element): # Print warning that will return a default color - warning = ('\n Warning: configHandler.IdleConf.GetThemeDict' + warning = ('\n Warning: config.IdleConf.GetThemeDict' ' -\n problem retrieving theme element %r' '\n from theme %r.\n' ' returning default color: %r' % (element, themeName, theme[element])) - try: - print(warning, file=sys.stderr) - except OSError: - pass + _warn(warning, 'highlight', themeName, element) theme[element] = cfgParser.Get( themeName, element, default=theme[element]) return theme def CurrentTheme(self): - """Return the name of the currently active text color theme. + "Return the name of the currently active text color theme." + return self.current_colors_and_keys('Theme') + + def CurrentKeys(self): + """Return the name of the currently active key set.""" + return self.current_colors_and_keys('Keys') + + def current_colors_and_keys(self, section): + """Return the currently active name for Theme or Keys section. + + idlelib.config-main.def ('default') includes these sections - idlelib.config-main.def includes this section [Theme] default= 1 name= IDLE Classic name2= - # name2 set in user config-main.cfg for themes added after 2015 Oct 1 - Item name2 is needed because setting name to a new builtin - causes older IDLEs to display multiple error messages or quit. + [Keys] + default= 1 + name= + name2= + + Item 'name2', is used for built-in ('default') themes and keys + added after 2015 Oct 1 and 2016 July 1. This kludge is needed + because setting 'name' to a builtin not defined in older IDLEs + to display multiple error messages or quit. See https://bugs.python.org/issue25313. - When default = True, name2 takes precedence over name, - while older IDLEs will just use name. + When default = True, 'name2' takes precedence over 'name', + while older IDLEs will just use name. When default = False, + 'name2' may still be set, but it is ignored. """ - default = self.GetOption('main', 'Theme', 'default', + cfgname = 'highlight' if section == 'Theme' else 'keys' + default = self.GetOption('main', section, 'default', type='bool', default=True) + name = '' if default: - theme = self.GetOption('main', 'Theme', 'name2', default='') - if default and not theme or not default: - theme = self.GetOption('main', 'Theme', 'name', default='') - source = self.defaultCfg if default else self.userCfg - if source['highlight'].has_section(theme): - return theme + name = self.GetOption('main', section, 'name2', default='') + if not name: + name = self.GetOption('main', section, 'name', default='') + if name: + source = self.defaultCfg if default else self.userCfg + if source[cfgname].has_section(name): + return name + return "IDLE Classic" if section == 'Theme' else self.default_keys() + + @staticmethod + def default_keys(): + if sys.platform[:3] == 'win': + return 'IDLE Classic Windows' + elif sys.platform == 'darwin': + return 'IDLE Classic OSX' else: - return "IDLE Classic" + return 'IDLE Modern Unix' - def CurrentKeys(self): - "Return the name of the currently active key set." - return self.GetOption('main', 'Keys', 'name', default='') - - def GetExtensions(self, active_only=True, editor_only=False, shell_only=False): + def GetExtensions(self, active_only=True, + editor_only=False, shell_only=False): """Return extensions in default and user config-extensions files. If active_only True, only return active (enabled) extensions @@ -423,7 +437,7 @@ class IdleConf: if self.GetOption('extensions', extn, 'enable', default=True, type='bool'): #the extension is enabled - if editor_only or shell_only: # TODO if both, contradictory + if editor_only or shell_only: # TODO both True contradict if editor_only: option = "enable_editor" else: @@ -528,7 +542,8 @@ class IdleConf: eventStr - virtual event, including brackets, as in '<<event>>'. """ eventName = eventStr[2:-2] #trim off the angle brackets - binding = self.GetOption('keys', keySetName, eventName, default='').split() + binding = self.GetOption('keys', keySetName, eventName, default='', + warn_on_default=False).split() return binding def GetCurrentKeySet(self): @@ -639,20 +654,28 @@ class IdleConf: '<<del-word-right>>': ['<Control-Key-Delete>'] } if keySetName: - for event in keyBindings: - binding = self.GetKeyBinding(keySetName, event) - if binding: - keyBindings[event] = binding - else: #we are going to return a default, print warning - warning=('\n Warning: configHandler.py - IdleConf.GetCoreKeys' - ' -\n problem retrieving key binding for event %r' - '\n from key set %r.\n' - ' returning default value: %r' % - (event, keySetName, keyBindings[event])) - try: - print(warning, file=sys.stderr) - except OSError: - pass + if not (self.userCfg['keys'].has_section(keySetName) or + self.defaultCfg['keys'].has_section(keySetName)): + warning = ( + '\n Warning: config.py - IdleConf.GetCoreKeys -\n' + ' key set %r is not defined, using default bindings.' % + (keySetName,) + ) + _warn(warning, 'keys', keySetName) + else: + for event in keyBindings: + binding = self.GetKeyBinding(keySetName, event) + if binding: + keyBindings[event] = binding + else: #we are going to return a default, print warning + warning = ( + '\n Warning: config.py - IdleConf.GetCoreKeys -\n' + ' problem retrieving key binding for event %r\n' + ' from key set %r.\n' + ' returning default value: %r' % + (event, keySetName, keyBindings[event]) + ) + _warn(warning, 'keys', keySetName, event) return keyBindings def GetExtraHelpSourceList(self, configSet): @@ -713,16 +736,13 @@ class IdleConf: bold = self.GetOption(configType, section, 'font-bold', default=0, type='bool') if (family == 'TkFixedFont'): - if TkVersion < 8.5: - family = 'Courier' - else: - f = Font(name='TkFixedFont', exists=True, root=root) - actualFont = Font.actual(f) - family = actualFont['family'] - size = actualFont['size'] - if size <= 0: - size = 10 # if font in pixels, ignore actual size - bold = actualFont['weight']=='bold' + f = Font(name='TkFixedFont', exists=True, root=root) + actualFont = Font.actual(f) + family = actualFont['family'] + size = actualFont['size'] + if size <= 0: + size = 10 # if font in pixels, ignore actual size + bold = actualFont['weight'] == 'bold' return (family, size, 'bold' if bold else 'normal') def LoadCfgFiles(self): @@ -739,6 +759,18 @@ class IdleConf: idleConf = IdleConf() + +_warned = set() +def _warn(msg, *key): + key = (msg,) + key + if key not in _warned: + try: + print(msg, file=sys.stderr) + except OSError: + pass + _warned.add(key) + + # TODO Revise test output, write expanded unittest # if __name__ == '__main__': diff --git a/Lib/idlelib/configHelpSourceEdit.py b/Lib/idlelib/configHelpSourceEdit.py deleted file mode 100644 index cde8118..0000000 --- a/Lib/idlelib/configHelpSourceEdit.py +++ /dev/null @@ -1,170 +0,0 @@ -"Dialog to specify or edit the parameters for a user configured help source." - -import os -import sys - -from tkinter import * -import tkinter.messagebox as tkMessageBox -import tkinter.filedialog as tkFileDialog - -class GetHelpSourceDialog(Toplevel): - def __init__(self, parent, title, menuItem='', filePath='', _htest=False): - """Get menu entry and url/ local file location for Additional Help - - User selects a name for the Help resource and provides a web url - or a local file as its source. The user can enter a url or browse - for the file. - - _htest - bool, change box location when running htest - """ - Toplevel.__init__(self, parent) - self.configure(borderwidth=5) - self.resizable(height=FALSE, width=FALSE) - self.title(title) - self.transient(parent) - self.grab_set() - self.protocol("WM_DELETE_WINDOW", self.cancel) - self.parent = parent - self.result = None - self.create_widgets() - self.menu.set(menuItem) - self.path.set(filePath) - self.withdraw() #hide while setting geometry - #needs to be done here so that the winfo_reqwidth is valid - self.update_idletasks() - #centre dialog over parent. below parent if running htest. - self.geometry( - "+%d+%d" % ( - parent.winfo_rootx() + - (parent.winfo_width()/2 - self.winfo_reqwidth()/2), - parent.winfo_rooty() + - ((parent.winfo_height()/2 - self.winfo_reqheight()/2) - if not _htest else 150))) - self.deiconify() #geometry set, unhide - self.bind('<Return>', self.ok) - self.wait_window() - - def create_widgets(self): - self.menu = StringVar(self) - self.path = StringVar(self) - self.fontSize = StringVar(self) - self.frameMain = Frame(self, borderwidth=2, relief=GROOVE) - self.frameMain.pack(side=TOP, expand=TRUE, fill=BOTH) - labelMenu = Label(self.frameMain, anchor=W, justify=LEFT, - text='Menu Item:') - self.entryMenu = Entry(self.frameMain, textvariable=self.menu, - width=30) - self.entryMenu.focus_set() - labelPath = Label(self.frameMain, anchor=W, justify=LEFT, - text='Help File Path: Enter URL or browse for file') - self.entryPath = Entry(self.frameMain, textvariable=self.path, - width=40) - self.entryMenu.focus_set() - labelMenu.pack(anchor=W, padx=5, pady=3) - self.entryMenu.pack(anchor=W, padx=5, pady=3) - 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.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) - self.buttonOk.grid(row=0, column=0, padx=5,pady=5) - self.buttonCancel = Button(frameButtons, text='Cancel', - width=8, command=self.cancel) - self.buttonCancel.grid(row=0, column=1, padx=5, pady=5) - - def browse_file(self): - filetypes = [ - ("HTML Files", "*.htm *.html", "TEXT"), - ("PDF Files", "*.pdf", "TEXT"), - ("Windows Help Files", "*.chm"), - ("Text Files", "*.txt", "TEXT"), - ("All Files", "*")] - path = self.path.get() - if path: - dir, base = os.path.split(path) - else: - base = None - if sys.platform[:3] == 'win': - dir = os.path.join(os.path.dirname(sys.executable), 'Doc') - if not os.path.isdir(dir): - dir = os.getcwd() - else: - dir = os.getcwd() - opendialog = tkFileDialog.Open(parent=self, filetypes=filetypes) - file = opendialog.show(initialdir=dir, initialfile=base) - if file: - self.path.set(file) - - def menu_ok(self): - "Simple validity check for a sensible menu item name" - menu_ok = True - menu = self.menu.get() - menu.strip() - if not menu: - tkMessageBox.showerror(title='Menu Item Error', - message='No menu item specified', - parent=self) - self.entryMenu.focus_set() - 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() - menu_ok = False - return menu_ok - - def path_ok(self): - "Simple validity check for menu file path" - path_ok = True - path = self.path.get() - path.strip() - if not path: #no path specified - tkMessageBox.showerror(title='File Path Error', - message='No help file path specified.', - parent=self) - self.entryPath.focus_set() - path_ok = False - elif path.startswith(('www.', 'http')): - pass - else: - if path[:5] == 'file:': - path = path[5:] - if not os.path.exists(path): - tkMessageBox.showerror(title='File Path Error', - message='Help file path does not exist.', - parent=self) - self.entryPath.focus_set() - path_ok = False - return path_ok - - 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:', 'https:')): - pass - else: - # Mac Safari insists on using the URI form for local files - self.result = list(self.result) - self.result[1] = "file://" + path - self.destroy() - - 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/configSectionNameDialog.py b/Lib/idlelib/configSectionNameDialog.py deleted file mode 100644 index 5137836..0000000 --- a/Lib/idlelib/configSectionNameDialog.py +++ /dev/null @@ -1,98 +0,0 @@ -""" -Dialog that allows user to specify a new config file section name. -Used to get new highlight theme and keybinding set names. -The 'return value' for the dialog, used two placed in configDialog.py, -is the .result attribute set in the Ok and Cancel methods. -""" -from tkinter import * -import tkinter.messagebox as tkMessageBox - -class GetCfgSectionNameDialog(Toplevel): - def __init__(self, parent, title, message, used_names, _htest=False): - """ - message - string, informational message to display - used_names - string collection, names already in use for validity check - _htest - bool, change box location when running htest - """ - Toplevel.__init__(self, parent) - self.configure(borderwidth=5) - self.resizable(height=FALSE, width=FALSE) - self.title(title) - self.transient(parent) - self.grab_set() - self.protocol("WM_DELETE_WINDOW", self.Cancel) - self.parent = parent - self.message = message - self.used_names = used_names - self.create_widgets() - self.withdraw() #hide while setting geometry - self.update_idletasks() - #needs to be done here so that the winfo_reqwidth is valid - self.messageInfo.config(width=self.frameMain.winfo_reqwidth()) - self.geometry( - "+%d+%d" % ( - parent.winfo_rootx() + - (parent.winfo_width()/2 - self.winfo_reqwidth()/2), - parent.winfo_rooty() + - ((parent.winfo_height()/2 - self.winfo_reqheight()/2) - if not _htest else 100) - ) ) #centre dialog over parent (or below htest box) - self.deiconify() #geometry set, unhide - self.wait_window() - - def create_widgets(self): - self.name = StringVar(self.parent) - self.fontSize = StringVar(self.parent) - self.frameMain = Frame(self, borderwidth=2, relief=SUNKEN) - self.frameMain.pack(side=TOP, expand=TRUE, fill=BOTH) - self.messageInfo = Message(self.frameMain, anchor=W, justify=LEFT, - padx=5, pady=5, text=self.message) #,aspect=200) - entryName = Entry(self.frameMain, textvariable=self.name, width=30) - entryName.focus_set() - self.messageInfo.pack(padx=5, pady=5) #, expand=TRUE, fill=BOTH) - entryName.pack(padx=5, pady=5) - - frameButtons = Frame(self, pady=2) - frameButtons.pack(side=BOTTOM) - self.buttonOk = Button(frameButtons, text='Ok', - width=8, command=self.Ok) - self.buttonOk.pack(side=LEFT, padx=5) - self.buttonCancel = Button(frameButtons, text='Cancel', - width=8, command=self.Cancel) - self.buttonCancel.pack(side=RIGHT, padx=5) - - def name_ok(self): - ''' After stripping entered name, check that it is a sensible - ConfigParser file section name. Return it if it is, '' if not. - ''' - name = self.name.get().strip() - if not name: #no name specified - tkMessageBox.showerror(title='Name Error', - message='No name specified.', parent=self) - elif len(name)>30: #name too long - tkMessageBox.showerror(title='Name Error', - message='Name too long. It should be no more than '+ - '30 characters.', parent=self) - name = '' - elif name in self.used_names: - tkMessageBox.showerror(title='Name Error', - message='This name is already in use.', parent=self) - name = '' - return name - - def Ok(self, event=None): - name = self.name_ok() - if name: - self.result = name - self.destroy() - - def Cancel(self, event=None): - self.result = '' - self.destroy() - -if __name__ == '__main__': - import unittest - unittest.main('idlelib.idle_test.test_config_name', verbosity=2, exit=False) - - from idlelib.idle_test.htest import run - run(GetCfgSectionNameDialog) diff --git a/Lib/idlelib/keybindingDialog.py b/Lib/idlelib/config_key.py index e6438bf..2602293 100644 --- a/Lib/idlelib/keybindingDialog.py +++ b/Lib/idlelib/config_key.py @@ -2,31 +2,35 @@ Dialog for building Tkinter accelerator key bindings """ from tkinter import * +from tkinter.ttk import Scrollbar import tkinter.messagebox as tkMessageBox import string import sys class GetKeysDialog(Toplevel): - def __init__(self,parent,title,action,currentKeySequences,_htest=False): + def __init__(self, parent, title, action, currentKeySequences, + _htest=False, _utest=False): """ action - string, the name of the virtual event these keys will be mapped to currentKeys - list, a list of all key sequence lists currently mapped to virtual events, for overlap checking + _utest - bool, do not wait when running unittest _htest - bool, change box location when running htest """ Toplevel.__init__(self, parent) + self.withdraw() #hide while setting geometry self.configure(borderwidth=5) - self.resizable(height=FALSE,width=FALSE) + self.resizable(height=FALSE, width=FALSE) self.title(title) self.transient(parent) self.grab_set() self.protocol("WM_DELETE_WINDOW", self.Cancel) self.parent = parent self.action=action - self.currentKeySequences=currentKeySequences - self.result='' - self.keyString=StringVar(self) + self.currentKeySequences = currentKeySequences + self.result = '' + self.keyString = StringVar(self) self.keyString.set('') self.SetModifiersForPlatform() # set self.modifiers, self.modifier_label self.modifier_vars = [] @@ -37,7 +41,6 @@ class GetKeysDialog(Toplevel): self.advanced = False self.CreateWidgets() self.LoadFinalKeyList() - self.withdraw() #hide while setting geometry self.update_idletasks() self.geometry( "+%d+%d" % ( @@ -47,8 +50,9 @@ class GetKeysDialog(Toplevel): ((parent.winfo_height()/2 - self.winfo_reqheight()/2) if not _htest else 150) ) ) #centre dialog over parent (or below htest box) - self.deiconify() #geometry set, unhide - self.wait_window() + if not _utest: + self.deiconify() #geometry set, unhide + self.wait_window() def CreateWidgets(self): frameMain = Frame(self,borderwidth=2,relief=SUNKEN) @@ -261,6 +265,7 @@ class GetKeysDialog(Toplevel): keysOK = True return keysOK + if __name__ == '__main__': from idlelib.idle_test.htest import run run(GetKeysDialog) diff --git a/Lib/idlelib/configDialog.py b/Lib/idlelib/configdialog.py index 5f5bd36..fa47aaa 100644 --- a/Lib/idlelib/configDialog.py +++ b/Lib/idlelib/configdialog.py @@ -10,18 +10,18 @@ Refer to comments in EditorWindow autoindent code for details. """ from tkinter import * -import tkinter.messagebox as tkMessageBox +from tkinter.ttk import Scrollbar import tkinter.colorchooser as tkColorChooser import tkinter.font as tkFont +import tkinter.messagebox as tkMessageBox -from idlelib.configHandler import idleConf -from idlelib.dynOptionMenuWidget import DynOptionMenu -from idlelib.keybindingDialog import GetKeysDialog -from idlelib.configSectionNameDialog import GetCfgSectionNameDialog -from idlelib.configHelpSourceEdit import GetHelpSourceDialog +from idlelib.config import idleConf +from idlelib.config_key import GetKeysDialog +from idlelib.dynoption import DynOptionMenu +from idlelib import macosx +from idlelib.query import SectionName, HelpSource from idlelib.tabbedpages import TabbedPageSet -from idlelib.textView import view_text -from idlelib import macosxSupport +from idlelib.textview import view_text class ConfigDialog(Toplevel): @@ -91,7 +91,7 @@ class ConfigDialog(Toplevel): self.create_action_buttons().pack(side=BOTTOM) def create_action_buttons(self): - if macosxSupport.isAquaTk(): + if macosx.isAquaTk(): # Changing the default padding on OSX results in unreadable # text in the buttons paddingArgs = {} @@ -341,6 +341,7 @@ class ConfigDialog(Toplevel): buttonSaveCustomKeys = Button( frames[1], text='Save as New Custom Key Set', command=self.SaveAsNewKeySet) + self.new_custom_keys = Label(frames[0], bd=2) ##widget packing #body @@ -361,6 +362,7 @@ class ConfigDialog(Toplevel): self.radioKeysCustom.grid(row=1, column=0, sticky=W+NS) self.optMenuKeysBuiltin.grid(row=0, column=1, sticky=NSEW) self.optMenuKeysCustom.grid(row=1, column=1, sticky=NSEW) + self.new_custom_keys.grid(row=0, column=2, sticky=NSEW, padx=5, pady=5) self.buttonDeleteCustomKeys.pack(side=LEFT, fill=X, expand=True, padx=2) buttonSaveCustomKeys.pack(side=LEFT, fill=X, expand=True, padx=2) frames[0].pack(side=TOP, fill=BOTH, expand=True) @@ -464,24 +466,24 @@ class ConfigDialog(Toplevel): return frame def AttachVarCallbacks(self): - self.fontSize.trace_variable('w', self.VarChanged_font) - self.fontName.trace_variable('w', self.VarChanged_font) - self.fontBold.trace_variable('w', self.VarChanged_font) - self.spaceNum.trace_variable('w', self.VarChanged_spaceNum) - self.colour.trace_variable('w', self.VarChanged_colour) - self.builtinTheme.trace_variable('w', self.VarChanged_builtinTheme) - self.customTheme.trace_variable('w', self.VarChanged_customTheme) - self.themeIsBuiltin.trace_variable('w', self.VarChanged_themeIsBuiltin) - self.highlightTarget.trace_variable('w', self.VarChanged_highlightTarget) - self.keyBinding.trace_variable('w', self.VarChanged_keyBinding) - self.builtinKeys.trace_variable('w', self.VarChanged_builtinKeys) - self.customKeys.trace_variable('w', self.VarChanged_customKeys) - self.keysAreBuiltin.trace_variable('w', self.VarChanged_keysAreBuiltin) - self.winWidth.trace_variable('w', self.VarChanged_winWidth) - self.winHeight.trace_variable('w', self.VarChanged_winHeight) - self.startupEdit.trace_variable('w', self.VarChanged_startupEdit) - self.autoSave.trace_variable('w', self.VarChanged_autoSave) - self.encoding.trace_variable('w', self.VarChanged_encoding) + self.fontSize.trace_add('write', self.VarChanged_font) + self.fontName.trace_add('write', self.VarChanged_font) + self.fontBold.trace_add('write', self.VarChanged_font) + self.spaceNum.trace_add('write', self.VarChanged_spaceNum) + self.colour.trace_add('write', self.VarChanged_colour) + self.builtinTheme.trace_add('write', self.VarChanged_builtinTheme) + self.customTheme.trace_add('write', self.VarChanged_customTheme) + self.themeIsBuiltin.trace_add('write', self.VarChanged_themeIsBuiltin) + self.highlightTarget.trace_add('write', self.VarChanged_highlightTarget) + self.keyBinding.trace_add('write', self.VarChanged_keyBinding) + self.builtinKeys.trace_add('write', self.VarChanged_builtinKeys) + self.customKeys.trace_add('write', self.VarChanged_customKeys) + self.keysAreBuiltin.trace_add('write', self.VarChanged_keysAreBuiltin) + self.winWidth.trace_add('write', self.VarChanged_winWidth) + self.winHeight.trace_add('write', self.VarChanged_winHeight) + self.startupEdit.trace_add('write', self.VarChanged_startupEdit) + self.autoSave.trace_add('write', self.VarChanged_autoSave) + self.encoding.trace_add('write', self.VarChanged_encoding) def remove_var_callbacks(self): "Remove callbacks to prevent memory leaks." @@ -492,7 +494,7 @@ class ConfigDialog(Toplevel): 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]) + var.trace_remove('write', var.trace_info()[0][1]) def VarChanged_font(self, *params): '''When one font attribute changes, save them all, as they are @@ -514,10 +516,11 @@ class ConfigDialog(Toplevel): self.OnNewColourSet() def VarChanged_builtinTheme(self, *params): + oldthemes = ('IDLE Classic', 'IDLE New') value = self.builtinTheme.get() - if value == 'IDLE Dark': - if idleConf.GetOption('main', 'Theme', 'name') != 'IDLE New': - self.AddChangedItem('main', 'Theme', 'name', 'IDLE Classic') + if value not in oldthemes: + if idleConf.GetOption('main', 'Theme', 'name') not in oldthemes: + self.AddChangedItem('main', 'Theme', 'name', oldthemes[0]) self.AddChangedItem('main', 'Theme', 'name2', value) self.new_custom_theme.config(text='New theme, see Help', fg='#500000') @@ -557,8 +560,23 @@ class ConfigDialog(Toplevel): self.AddChangedItem('extensions', extKeybindSection, event, value) def VarChanged_builtinKeys(self, *params): + oldkeys = ( + 'IDLE Classic Windows', + 'IDLE Classic Unix', + 'IDLE Classic Mac', + 'IDLE Classic OSX', + ) value = self.builtinKeys.get() - self.AddChangedItem('main', 'Keys', 'name', value) + if value not in oldkeys: + if idleConf.GetOption('main', 'Keys', 'name') not in oldkeys: + self.AddChangedItem('main', 'Keys', 'name', oldkeys[0]) + self.AddChangedItem('main', 'Keys', 'name2', value) + self.new_custom_keys.config(text='New key set, see Help', + fg='#500000') + else: + self.AddChangedItem('main', 'Keys', 'name', value) + self.AddChangedItem('main', 'Keys', 'name2', '') + self.new_custom_keys.config(text='', fg='black') self.LoadKeysList(value) def VarChanged_customKeys(self, *params): @@ -683,7 +701,7 @@ class ConfigDialog(Toplevel): def GetNewKeysName(self, message): usedNames = (idleConf.GetSectionList('user', 'keys') + idleConf.GetSectionList('default', 'keys')) - newKeySet = GetCfgSectionNameDialog( + newKeySet = SectionName( self, 'New Custom Key Set', message, usedNames).result return newKeySet @@ -767,8 +785,10 @@ class ConfigDialog(Toplevel): else: self.optMenuKeysCustom.SetMenu(itemList, itemList[0]) #revert to default key set - self.keysAreBuiltin.set(idleConf.defaultCfg['main'].Get('Keys', 'default')) - self.builtinKeys.set(idleConf.defaultCfg['main'].Get('Keys', 'name')) + self.keysAreBuiltin.set(idleConf.defaultCfg['main'] + .Get('Keys', 'default')) + self.builtinKeys.set(idleConf.defaultCfg['main'].Get('Keys', 'name') + or idleConf.default_keys()) #user can't back out of these changes, they must be applied now self.SaveAllChangedConfigs() self.ActivateConfigChanges() @@ -836,7 +856,7 @@ class ConfigDialog(Toplevel): def GetNewThemeName(self, message): usedNames = (idleConf.GetSectionList('user', 'highlight') + idleConf.GetSectionList('default', 'highlight')) - newTheme = GetCfgSectionNameDialog( + newTheme = SectionName( self, 'New Custom Theme', message, usedNames).result return newTheme @@ -939,7 +959,8 @@ class ConfigDialog(Toplevel): self.buttonHelpListRemove.config(state=DISABLED) def HelpListItemAdd(self): - helpSource = GetHelpSourceDialog(self, 'New Help Source').result + helpSource = HelpSource(self, 'New Help Source', + ).result if helpSource: self.userHelpList.append((helpSource[0], helpSource[1])) self.listHelp.insert(END, helpSource[0]) @@ -949,16 +970,17 @@ class ConfigDialog(Toplevel): def HelpListItemEdit(self): itemIndex = self.listHelp.index(ANCHOR) helpSource = self.userHelpList[itemIndex] - newHelpSource = GetHelpSourceDialog( - self, 'Edit Help Source', menuItem=helpSource[0], - filePath=helpSource[1]).result - if (not newHelpSource) or (newHelpSource == helpSource): - return #no changes - self.userHelpList[itemIndex] = newHelpSource - self.listHelp.delete(itemIndex) - self.listHelp.insert(itemIndex, newHelpSource[0]) - self.UpdateUserHelpChangedItems() - self.SetHelpListButtonStates() + newHelpSource = HelpSource( + self, 'Edit Help Source', + menuitem=helpSource[0], + filepath=helpSource[1], + ).result + if newHelpSource and newHelpSource != helpSource: + self.userHelpList[itemIndex] = newHelpSource + self.listHelp.delete(itemIndex) + self.listHelp.insert(itemIndex, newHelpSource[0]) + self.UpdateUserHelpChangedItems() + self.SetHelpListButtonStates() def HelpListItemRemove(self): itemIndex = self.listHelp.index(ANCHOR) @@ -996,7 +1018,8 @@ class ConfigDialog(Toplevel): pass ##font size dropdown self.optMenuFontSize.SetMenu(('7', '8', '9', '10', '11', '12', '13', - '14', '16', '18', '20', '22'), fontSize ) + '14', '16', '18', '20', '22', + '25', '29', '34', '40'), fontSize ) ##fontWeight self.fontBold.set(fontBold) ##font sample @@ -1065,7 +1088,7 @@ class ConfigDialog(Toplevel): self.optMenuKeysCustom.SetMenu(itemList, currentOption) itemList = idleConf.GetSectionList('default', 'keys') itemList.sort() - self.optMenuKeysBuiltin.SetMenu(itemList, itemList[0]) + self.optMenuKeysBuiltin.SetMenu(itemList, idleConf.default_keys()) self.SetKeysType() ##load keyset element list keySetName = idleConf.CurrentKeys() @@ -1367,12 +1390,18 @@ machine. Some do not take affect until IDLE is restarted. [Cancel] only cancels changes made since the last save. ''' help_pages = { - 'Highlighting':''' + 'Highlighting': ''' Highlighting: The IDLE Dark color theme is new in October 2015. It can only be used with older IDLE releases if it is saved as a custom theme, with a different name. -''' +''', + 'Keys': ''' +Keys: +The IDLE Modern Unix key set is new in June 2016. It can only +be used with older IDLE releases if it is saved as a custom +key set, with a different name. +''', } diff --git a/Lib/idlelib/Debugger.py b/Lib/idlelib/debugger.py index d5e217d..114d0d1 100644 --- a/Lib/idlelib/Debugger.py +++ b/Lib/idlelib/debugger.py @@ -1,9 +1,12 @@ -import os import bdb +import os + from tkinter import * -from idlelib.WindowList import ListedToplevel -from idlelib.ScrolledList import ScrolledList -from idlelib import macosxSupport +from tkinter.ttk import Scrollbar + +from idlelib import macosx +from idlelib.scrolledlist import ScrolledList +from idlelib.windows import ListedToplevel class Idb(bdb.Bdb): @@ -34,8 +37,10 @@ class Idb(bdb.Bdb): return True else: prev_frame = frame.f_back - if prev_frame.f_code.co_filename.count('Debugger.py'): - # (that test will catch both Debugger.py and RemoteDebugger.py) + prev_name = prev_frame.f_code.co_filename + if 'idlelib' in prev_name and 'debugger' in prev_name: + # catch both idlelib/debugger.py and idlelib/debugger_r.py + # on both posix and windows return False return self.in_rpc_code(prev_frame) @@ -370,7 +375,7 @@ class Debugger: class StackViewer(ScrolledList): def __init__(self, master, flist, gui): - if macosxSupport.isAquaTk(): + if macosx.isAquaTk(): # At least on with the stock AquaTk version on OSX 10.4 you'll # get a shaking GUI that eventually kills IDLE if the width # argument is specified. @@ -502,7 +507,7 @@ class NamespaceViewer: # # There is also an obscure bug in sorted(dict) where the # interpreter gets into a loop requesting non-existing dict[0], - # dict[1], dict[2], etc from the RemoteDebugger.DictProxy. + # dict[1], dict[2], etc from the debugger_r.DictProxy. ### keys_list = dict.keys() names = sorted(keys_list) diff --git a/Lib/idlelib/RemoteDebugger.py b/Lib/idlelib/debugger_r.py index be2262f..bc97127 100644 --- a/Lib/idlelib/RemoteDebugger.py +++ b/Lib/idlelib/debugger_r.py @@ -21,7 +21,7 @@ barrier, in particular frame and traceback objects. """ import types -from idlelib import Debugger +from idlelib import debugger debugging = 0 @@ -187,7 +187,7 @@ def start_debugger(rpchandler, gui_adap_oid): """ gui_proxy = GUIProxy(rpchandler, gui_adap_oid) - idb = Debugger.Idb(gui_proxy) + idb = debugger.Idb(gui_proxy) idb_adap = IdbAdapter(idb) rpchandler.register(idb_adap_oid, idb_adap) return idb_adap_oid @@ -362,7 +362,7 @@ def start_remote_debugger(rpcclt, pyshell): idb_adap_oid = rpcclt.remotecall("exec", "start_the_debugger",\ (gui_adap_oid,), {}) idb_proxy = IdbProxy(rpcclt, pyshell, idb_adap_oid) - gui = Debugger.Debugger(pyshell, idb_proxy) + gui = debugger.Debugger(pyshell, idb_proxy) gui_adap = GUIAdapter(rpcclt, gui) rpcclt.register(gui_adap_oid, gui_adap) return gui @@ -373,7 +373,7 @@ def close_remote_debugger(rpcclt): Request that the RPCServer shut down the subprocess debugger and link. Unregister the GUIAdapter, which will cause a GC on the Idle process debugger and RPC link objects. (The second reference to the debugger GUI - is deleted in PyShell.close_remote_debugger().) + is deleted in pyshell.close_remote_debugger().) """ close_subprocess_debugger(rpcclt) diff --git a/Lib/idlelib/ObjectBrowser.py b/Lib/idlelib/debugobj.py index 7b57aa4..b70b13c 100644 --- a/Lib/idlelib/ObjectBrowser.py +++ b/Lib/idlelib/debugobj.py @@ -8,13 +8,10 @@ # XXX TO DO: # - for classes/modules, add "open source" to object browser - -import re - -from idlelib.TreeWidget import TreeItem, TreeNode, ScrolledCanvas - from reprlib import Repr +from idlelib.tree import TreeItem, TreeNode, ScrolledCanvas + myrepr = Repr() myrepr.maxstring = 100 myrepr.maxother = 100 @@ -122,21 +119,20 @@ def make_objecttreeitem(labeltext, object, setfunction=None): return c(labeltext, object, setfunction) -def _object_browser(parent): +def _object_browser(parent): # htest # import sys - from tkinter import Tk - root = Tk() - root.title("Test ObjectBrowser") - width, height, x, y = list(map(int, re.split('[x+]', parent.geometry()))) - root.geometry("+%d+%d"%(x, y + 150)) - root.configure(bd=0, bg="yellow") - root.focus_set() - sc = ScrolledCanvas(root, bg="white", highlightthickness=0, takefocus=1) + from tkinter import Toplevel + top = Toplevel(parent) + top.title("Test debug object browser") + x, y = map(int, parent.geometry().split('+')[1:]) + top.geometry("+%d+%d" % (x + 100, y + 175)) + top.configure(bd=0, bg="yellow") + top.focus_set() + sc = ScrolledCanvas(top, bg="white", highlightthickness=0, takefocus=1) sc.frame.pack(expand=1, fill="both") item = make_objecttreeitem("sys", sys) node = TreeNode(sc.canvas, None, item) node.update() - root.mainloop() if __name__ == '__main__': from idlelib.idle_test.htest import run diff --git a/Lib/idlelib/RemoteObjectBrowser.py b/Lib/idlelib/debugobj_r.py index 8031aae..8031aae 100644 --- a/Lib/idlelib/RemoteObjectBrowser.py +++ b/Lib/idlelib/debugobj_r.py diff --git a/Lib/idlelib/Delegator.py b/Lib/idlelib/delegator.py index dc2a1aa..dc2a1aa 100644 --- a/Lib/idlelib/Delegator.py +++ b/Lib/idlelib/delegator.py diff --git a/Lib/idlelib/dynOptionMenuWidget.py b/Lib/idlelib/dynoption.py index 515b4ba..9c6ffa4 100644 --- a/Lib/idlelib/dynOptionMenuWidget.py +++ b/Lib/idlelib/dynoption.py @@ -3,6 +3,7 @@ OptionMenu widget modified to allow dynamic menu reconfiguration and setting of highlightthickness """ import copy + from tkinter import OptionMenu, _setit, StringVar, Button class DynOptionMenu(OptionMenu): @@ -34,12 +35,12 @@ class DynOptionMenu(OptionMenu): self.variable.set(value) def _dyn_option_menu(parent): # htest # - from tkinter import Toplevel + from tkinter import Toplevel # + StringVar, Button - top = Toplevel() + top = Toplevel(parent) top.title("Tets dynamic option menu") - top.geometry("200x100+%d+%d" % (parent.winfo_rootx() + 200, - parent.winfo_rooty() + 150)) + x, y = map(int, parent.geometry().split('+')[1:]) + top.geometry("200x100+%d+%d" % (x + 250, y + 175)) top.focus_set() var = StringVar(top) diff --git a/Lib/idlelib/EditorWindow.py b/Lib/idlelib/editor.py index 9944da3..ae475cb 100644 --- a/Lib/idlelib/EditorWindow.py +++ b/Lib/idlelib/editor.py @@ -6,28 +6,34 @@ import platform import re import string import sys +import tokenize +import traceback +import webbrowser + from tkinter import * +from tkinter.ttk import Scrollbar import tkinter.simpledialog as tkSimpleDialog import tkinter.messagebox as tkMessageBox -import traceback -import webbrowser -from idlelib.MultiCall import MultiCallCreator -from idlelib import WindowList -from idlelib import SearchDialog -from idlelib import GrepDialog -from idlelib import ReplaceDialog -from idlelib import PyParse -from idlelib.configHandler import idleConf -from idlelib import aboutDialog, textView, configDialog -from idlelib import macosxSupport +from idlelib.config import idleConf +from idlelib import configdialog +from idlelib import grep from idlelib import help +from idlelib import help_about +from idlelib import macosx +from idlelib.multicall import MultiCallCreator +from idlelib import pyparse +from idlelib import query +from idlelib import replace +from idlelib import search +from idlelib import textview +from idlelib import windows # The default tab setting for a Text widget, in average-width characters. TK_TABWIDTH_DEFAULT = 8 - _py_version = ' (%s)' % platform.python_version() + def _sphinx_version(): "Format sys.version_info to produce the Sphinx version string used to install the chm docs" major, minor, micro, level, serial = sys.version_info @@ -40,63 +46,16 @@ def _sphinx_version(): return release -class HelpDialog(object): - - def __init__(self): - self.parent = None # parent of help window - self.dlg = None # the help window iteself - - def display(self, parent, near=None): - """ Display the help dialog. - - parent - parent widget for the help window - - near - a Toplevel widget (e.g. EditorWindow or PyShell) - to use as a reference for placing the help window - """ - import warnings as w - w.warn("EditorWindow.HelpDialog is no longer used by Idle.\n" - "It will be removed in 3.6 or later.\n" - "It has been replaced by private help.HelpWindow\n", - DeprecationWarning, stacklevel=2) - if self.dlg is None: - self.show_dialog(parent) - if near: - self.nearwindow(near) - - def show_dialog(self, parent): - self.parent = parent - fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt') - self.dlg = dlg = textView.view_file(parent,'Help',fn, modal=False) - dlg.bind('<Destroy>', self.destroy, '+') - - def nearwindow(self, near): - # Place the help dialog near the window specified by parent. - # Note - this may not reposition the window in Metacity - # if "/apps/metacity/general/disable_workarounds" is enabled - dlg = self.dlg - geom = (near.winfo_rootx() + 10, near.winfo_rooty() + 10) - dlg.withdraw() - dlg.geometry("=+%d+%d" % geom) - dlg.deiconify() - dlg.lift() - - def destroy(self, ev=None): - self.dlg = None - self.parent = None - -helpDialog = HelpDialog() # singleton instance, no longer used - - class EditorWindow(object): - from idlelib.Percolator import Percolator - from idlelib.ColorDelegator import ColorDelegator, color_config - from idlelib.UndoDelegator import UndoDelegator - from idlelib.IOBinding import IOBinding, filesystemencoding, encoding - from idlelib import Bindings + from idlelib.percolator import Percolator + from idlelib.colorizer import ColorDelegator, color_config + from idlelib.undo import UndoDelegator + from idlelib.iomenu import IOBinding, encoding + from idlelib import mainmenu from tkinter import Toplevel - from idlelib.MultiStatusBar import MultiStatusBar + from idlelib.statusbar import MultiStatusBar + filesystemencoding = sys.getfilesystemencoding() # for file names help_url = None def __init__(self, flist=None, filename=None, key=None, root=None): @@ -136,11 +95,11 @@ class EditorWindow(object): except AttributeError: sys.ps1 = '>>> ' self.menubar = Menu(root) - self.top = top = WindowList.ListedToplevel(root, menu=self.menubar) + self.top = top = windows.ListedToplevel(root, menu=self.menubar) if flist: self.tkinter_vars = flist.vars #self.top.instance_dict makes flist.inversedict available to - #configDialog.py so it can access all EditorWindow instances + #configdialog.py so it can access all EditorWindow instances self.top.instance_dict = flist.inversedict else: self.tkinter_vars = {} # keys: Tkinter event names @@ -158,13 +117,10 @@ class EditorWindow(object): 'wrap': 'none', 'highlightthickness': 0, 'width': self.width, - 'height': idleConf.GetOption('main', 'EditorWindow', - 'height', type='int')} - if TkVersion >= 8.5: - # Starting with tk 8.5 we have to set the new tabstyle option - # to 'wordprocessor' to achieve the same display of tabs as in - # older tk versions. - text_options['tabstyle'] = 'wordprocessor' + 'tabstyle': 'wordprocessor', # new in 8.5 + 'height': idleConf.GetOption( + 'main', 'EditorWindow', 'height', type='int'), + } self.text = text = MultiCallCreator(Text)(text_frame, **text_options) self.top.focused_widget = self.text @@ -173,7 +129,7 @@ class EditorWindow(object): self.top.protocol("WM_DELETE_WINDOW", self.close) self.top.bind("<<close-window>>", self.close_event) - if macosxSupport.isAquaTk(): + if macosx.isAquaTk(): # Command-W on editorwindows doesn't work without this. text.bind('<<close-window>>', self.close_event) # Some OS X systems have only one mouse button, so use @@ -309,7 +265,7 @@ class EditorWindow(object): menu.add_separator() end = end + 1 self.wmenu_end = end - WindowList.register_callback(self.postwindowsmenu) + windows.register_callback(self.postwindowsmenu) # Some abstractions so IDLE extensions are cross-IDE self.askyesno = tkMessageBox.askyesno @@ -418,7 +374,7 @@ class EditorWindow(object): underline, label = prepstr(label) menudict[name] = menu = Menu(mbar, name=name, tearoff=0) mbar.add_cascade(label=label, menu=menu, underline=underline) - if macosxSupport.isCarbonTk(): + if macosx.isCarbonTk(): # Insert the application menu menudict['application'] = menu = Menu(mbar, name='apple', tearoff=0) @@ -439,7 +395,7 @@ class EditorWindow(object): end = -1 if end > self.wmenu_end: menu.delete(self.wmenu_end+1, end) - WindowList.add_windows_to_menu(menu) + windows.add_windows_to_menu(menu) rmenu = None @@ -507,17 +463,17 @@ class EditorWindow(object): def about_dialog(self, event=None): "Handle Help 'About IDLE' event." - # Synchronize with macosxSupport.overrideRootMenu.about_dialog. - aboutDialog.AboutDialog(self.top,'About IDLE') + # Synchronize with macosx.overrideRootMenu.about_dialog. + help_about.AboutDialog(self.top,'About IDLE') def config_dialog(self, event=None): "Handle Options 'Configure IDLE' event." - # Synchronize with macosxSupport.overrideRootMenu.config_dialog. - configDialog.ConfigDialog(self.top,'Settings') + # Synchronize with macosx.overrideRootMenu.config_dialog. + configdialog.ConfigDialog(self.top,'Settings') def help_dialog(self, event=None): "Handle Help 'IDLE Help' event." - # Synchronize with macosxSupport.overrideRootMenu.help_dialog. + # Synchronize with macosx.overrideRootMenu.help_dialog. if self.root: parent = self.root else: @@ -590,23 +546,23 @@ class EditorWindow(object): return "break" def find_event(self, event): - SearchDialog.find(self.text) + search.find(self.text) return "break" def find_again_event(self, event): - SearchDialog.find_again(self.text) + search.find_again(self.text) return "break" def find_selection_event(self, event): - SearchDialog.find_selection(self.text) + search.find_selection(self.text) return "break" def find_in_files_event(self, event): - GrepDialog.grep(self.text, self.io, self.flist) + grep.grep(self.text, self.io, self.flist) return "break" def replace_event(self, event): - ReplaceDialog.replace(self.text) + replace.replace(self.text) return "break" def goto_line_event(self, event): @@ -622,46 +578,27 @@ class EditorWindow(object): text.see("insert") def open_module(self, event=None): - # XXX Shouldn't this be in IOBinding? + """Get module name from user and open it. + + Return module path or None for calls by open_class_browser + when latter is not invoked in named editor window. + """ + # XXX This, open_class_browser, and open_path_browser + # would fit better in iomenu.IOBinding. try: - name = self.text.get("sel.first", "sel.last") + name = self.text.get("sel.first", "sel.last").strip() except TclError: - name = "" - else: - name = name.strip() - name = tkSimpleDialog.askstring("Module", - "Enter the name of a Python module\n" - "to search on sys.path and open:", - parent=self.text, initialvalue=name) - if name: - name = name.strip() - if not name: - return - # XXX Ought to insert current file's directory in front of path - try: - spec = importlib.util.find_spec(name) - except (ValueError, ImportError) as msg: - tkMessageBox.showerror("Import error", str(msg), parent=self.text) - return - if spec is None: - tkMessageBox.showerror("Import error", "module not found", - parent=self.text) - return - if not isinstance(spec.loader, importlib.abc.SourceLoader): - tkMessageBox.showerror("Import error", "not a source-based module", - parent=self.text) - return - try: - file_path = spec.loader.get_filename(name) - except AttributeError: - tkMessageBox.showerror("Import error", - "loader does not support get_filename", - parent=self.text) - return - if self.flist: - self.flist.open(file_path) - else: - self.io.loadfile(file_path) + name = '' + file_path = query.ModuleName( + self.text, "Open Module", + "Enter the name of a Python module\n" + "to search on sys.path and open:", + name).result + if file_path is not None: + if self.flist: + self.flist.open(file_path) + else: + self.io.loadfile(file_path) return file_path def open_class_browser(self, event=None): @@ -673,12 +610,12 @@ class EditorWindow(object): return head, tail = os.path.split(filename) base, ext = os.path.splitext(tail) - from idlelib import ClassBrowser - ClassBrowser.ClassBrowser(self.flist, base, [head]) + from idlelib import browser + browser.ClassBrowser(self.flist, base, [head]) def open_path_browser(self, event=None): - from idlelib import PathBrowser - PathBrowser.PathBrowser(self.flist) + from idlelib import pathbrowser + pathbrowser.PathBrowser(self.flist) def open_turtle_demo(self, event = None): import subprocess @@ -739,7 +676,7 @@ class EditorWindow(object): def ResetColorizer(self): "Update the color theme" - # Called from self.filename_change_hook and from configDialog.py + # Called from self.filename_change_hook and from configdialog.py self._rmcolorizer() self._addcolorizer() EditorWindow.color_config(self.text) @@ -759,14 +696,14 @@ class EditorWindow(object): def ResetFont(self): "Update the text widgets' font if it is changed" - # Called from configDialog.py + # Called from configdialog.py self.text['font'] = idleConf.GetFont(self.root, 'main','EditorWindow') def RemoveKeybindings(self): "Remove the keybindings before they are changed." - # Called from configDialog.py - self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet() + # Called from configdialog.py + self.mainmenu.default_keydefs = keydefs = idleConf.GetCurrentKeySet() for event, keylist in keydefs.items(): self.text.event_delete(event, *keylist) for extensionName in self.get_standard_extension_names(): @@ -777,8 +714,8 @@ class EditorWindow(object): def ApplyKeybindings(self): "Update the keybindings after they are changed" - # Called from configDialog.py - self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet() + # Called from configdialog.py + self.mainmenu.default_keydefs = keydefs = idleConf.GetCurrentKeySet() self.apply_bindings() for extensionName in self.get_standard_extension_names(): xkeydefs = idleConf.GetExtensionBindings(extensionName) @@ -786,7 +723,7 @@ class EditorWindow(object): self.apply_bindings(xkeydefs) #update menu accelerators menuEventDict = {} - for menu in self.Bindings.menudefs: + for menu in self.mainmenu.menudefs: menuEventDict[menu[0]] = {} for item in menu[1]: if item: @@ -813,7 +750,7 @@ class EditorWindow(object): def set_notabs_indentwidth(self): "Update the indentwidth if changed and not using tabs in this window" - # Called from configDialog.py + # Called from configdialog.py if not self.usetabs: self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces', type='int') @@ -993,7 +930,7 @@ class EditorWindow(object): def _close(self): if self.io.filename: self.update_recent_files_list(new_file=self.io.filename) - WindowList.unregister_callback(self.postwindowsmenu) + windows.unregister_callback(self.postwindowsmenu) self.unload_extensions() self.io.close() self.io = None @@ -1031,12 +968,25 @@ class EditorWindow(object): def get_standard_extension_names(self): return idleConf.GetExtensions(editor_only=True) + extfiles = { # map config-extension section names to new file names + 'AutoComplete': 'autocomplete', + 'AutoExpand': 'autoexpand', + 'CallTips': 'calltips', + 'CodeContext': 'codecontext', + 'FormatParagraph': 'paragraph', + 'ParenMatch': 'parenmatch', + 'RstripExtension': 'rstrip', + 'ScriptBinding': 'runscript', + 'ZoomHeight': 'zoomheight', + } + def load_extension(self, name): + fname = self.extfiles.get(name, name) try: try: - mod = importlib.import_module('.' + name, package=__package__) + mod = importlib.import_module('.' + fname, package=__package__) except (ImportError, TypeError): - mod = importlib.import_module(name) + mod = importlib.import_module(fname) except ImportError: print("\nFailed to import extension: ", name) raise @@ -1060,7 +1010,7 @@ class EditorWindow(object): def apply_bindings(self, keydefs=None): if keydefs is None: - keydefs = self.Bindings.default_keydefs + keydefs = self.mainmenu.default_keydefs text = self.text text.keydefs = keydefs for event, keylist in keydefs.items(): @@ -1073,9 +1023,9 @@ class EditorWindow(object): Menus that are absent or None in self.menudict are ignored. """ if menudefs is None: - menudefs = self.Bindings.menudefs + menudefs = self.mainmenu.menudefs if keydefs is None: - keydefs = self.Bindings.default_keydefs + keydefs = self.mainmenu.default_keydefs menudict = self.menudict text = self.text for mname, entrylist in menudefs: @@ -1302,7 +1252,7 @@ class EditorWindow(object): # adjust indentation for continuations and block # open/close first need to find the last stmt lno = index2line(text.index('insert')) - y = PyParse.Parser(self.indentwidth, self.tabwidth) + y = pyparse.Parser(self.indentwidth, self.tabwidth) if not self.context_use_ps1: for context in self.num_context_lines: startat = max(lno - context, 1) @@ -1326,22 +1276,22 @@ class EditorWindow(object): y.set_lo(0) c = y.get_continuation_type() - if c != PyParse.C_NONE: + if c != pyparse.C_NONE: # The current stmt hasn't ended yet. - if c == PyParse.C_STRING_FIRST_LINE: + if c == pyparse.C_STRING_FIRST_LINE: # after the first line of a string; do not indent at all pass - elif c == PyParse.C_STRING_NEXT_LINES: + elif c == pyparse.C_STRING_NEXT_LINES: # inside a string which started before this line; # just mimic the current indent text.insert("insert", indent) - elif c == PyParse.C_BRACKET: + elif c == pyparse.C_BRACKET: # line up with the first (if any) element of the # last open bracket structure; else indent one # level beyond the indent of the line with the # last open bracket self.reindent_to(y.compute_bracket_indent()) - elif c == PyParse.C_BACKSLASH: + elif c == pyparse.C_BACKSLASH: # if more than one line in this stmt already, just # mimic the current indent; else if initial line # has a start on an assignment stmt, indent to @@ -1569,9 +1519,6 @@ def classifyws(s, tabwidth): break return raw, effective -import tokenize -_tokenize = tokenize -del tokenize class IndentSearcher(object): @@ -1596,8 +1543,8 @@ class IndentSearcher(object): return self.text.get(mark, mark + " lineend+1c") def tokeneater(self, type, token, start, end, line, - INDENT=_tokenize.INDENT, - NAME=_tokenize.NAME, + INDENT=tokenize.INDENT, + NAME=tokenize.NAME, OPENERS=('class', 'def', 'for', 'if', 'try', 'while')): if self.finished: pass @@ -1608,19 +1555,19 @@ class IndentSearcher(object): self.finished = 1 def run(self): - save_tabsize = _tokenize.tabsize - _tokenize.tabsize = self.tabwidth + save_tabsize = tokenize.tabsize + tokenize.tabsize = self.tabwidth try: try: - tokens = _tokenize.generate_tokens(self.readline) + tokens = tokenize.generate_tokens(self.readline) for token in tokens: self.tokeneater(*token) - except (_tokenize.TokenError, SyntaxError): + except (tokenize.TokenError, SyntaxError): # since we cut off the tokenizer early, we can trigger # spurious errors pass finally: - _tokenize.tabsize = save_tabsize + tokenize.tabsize = save_tabsize return self.blkopenline, self.indentedline ### end autoindent code ### @@ -1644,7 +1591,7 @@ def get_accelerator(keydefs, eventname): keylist = keydefs.get(eventname) # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5 # if not keylist: - if (not keylist) or (macosxSupport.isCocoaTk() and eventname in { + if (not keylist) or (macosx.isCocoaTk() and eventname in { "<<open-module>>", "<<goto-line>>", "<<change-indentwidth>>"}): @@ -1679,12 +1626,15 @@ def _editor_window(parent): # htest # filename = sys.argv[1] else: filename = None - macosxSupport.setupApp(root, None) + macosx.setupApp(root, None) edit = EditorWindow(root=root, filename=filename) edit.text.bind("<<close-all-windows>>", edit.close_event) # Does not stop error, neither does following # edit.text.bind("<<close-window>>", edit.close_event) if __name__ == '__main__': + import unittest + unittest.main('idlelib.idle_test.test_editor', verbosity=2, exit=False) + from idlelib.idle_test.htest import run run(_editor_window) diff --git a/Lib/idlelib/FileList.py b/Lib/idlelib/filelist.py index a9989a8..f46ad7c 100644 --- a/Lib/idlelib/FileList.py +++ b/Lib/idlelib/filelist.py @@ -1,4 +1,5 @@ import os + from tkinter import * import tkinter.messagebox as tkMessageBox @@ -6,7 +7,7 @@ import tkinter.messagebox as tkMessageBox class FileList: # N.B. this import overridden in PyShellFileList. - from idlelib.EditorWindow import EditorWindow + from idlelib.editor import EditorWindow def __init__(self, root): self.root = root @@ -111,7 +112,7 @@ class FileList: def _test(): - from idlelib.EditorWindow import fixwordbreaks + from idlelib.editor import fixwordbreaks import sys root = Tk() fixwordbreaks(root) diff --git a/Lib/idlelib/GrepDialog.py b/Lib/idlelib/grep.py index 721b231..64ba28d 100644 --- a/Lib/idlelib/GrepDialog.py +++ b/Lib/idlelib/grep.py @@ -1,17 +1,19 @@ -import os import fnmatch -import re # for htest +import os import sys -from tkinter import StringVar, BooleanVar, Checkbutton # for GrepDialog -from tkinter import Tk, Text, Button, SEL, END # for htest -from idlelib import SearchEngine -from idlelib.SearchDialogBase import SearchDialogBase -# Importing OutputWindow fails due to import loop + +from tkinter import StringVar, BooleanVar +from tkinter.ttk import Checkbutton + +from idlelib.searchbase import SearchDialogBase +from idlelib import searchengine + +# Importing OutputWindow here fails due to import loop # EditorWindow -> GrepDialop -> OutputWindow -> EditorWindow def grep(text, io=None, flist=None): root = text._root() - engine = SearchEngine.get(root) + engine = searchengine.get(root) if not hasattr(engine, "_grepdialog"): engine._grepdialog = GrepDialog(root, engine, flist) dialog = engine._grepdialog @@ -47,13 +49,10 @@ class GrepDialog(SearchDialogBase): self.globent = self.make_entry("In files:", self.globvar)[0] def create_other_buttons(self): - f = self.make_frame()[0] - - btn = Checkbutton(f, anchor="w", - variable=self.recvar, + btn = Checkbutton( + self.make_frame()[0], variable=self.recvar, text="Recurse down subdirectories") btn.pack(side="top", fill="both") - btn.select() def create_command_buttons(self): SearchDialogBase.create_command_buttons(self) @@ -67,7 +66,7 @@ class GrepDialog(SearchDialogBase): if not path: self.top.bell() return - from idlelib.OutputWindow import OutputWindow # leave here! + from idlelib.outwin import OutputWindow # leave here! save = sys.stdout try: sys.stdout = OutputWindow(self.flist) @@ -131,14 +130,16 @@ class GrepDialog(SearchDialogBase): def _grep_dialog(parent): # htest # - from idlelib.PyShell import PyShellFileList - root = Tk() - root.title("Test GrepDialog") - width, height, x, y = list(map(int, re.split('[x+]', parent.geometry()))) - root.geometry("+%d+%d"%(x, y + 150)) - - flist = PyShellFileList(root) - text = Text(root, height=5) + from tkinter import Toplevel, Text, SEL, END + from tkinter.ttk import Button + from idlelib.pyshell import PyShellFileList + top = Toplevel(parent) + top.title("Test GrepDialog") + x, y = map(int, parent.geometry().split('+')[1:]) + top.geometry("+%d+%d" % (x, y + 175)) + + flist = PyShellFileList(top) + text = Text(top, height=5) text.pack() def show_grep_dialog(): @@ -146,9 +147,8 @@ def _grep_dialog(parent): # htest # grep(text, flist=flist) text.tag_remove(SEL, "1.0", END) - button = Button(root, text="Show GrepDialog", command=show_grep_dialog) + button = Button(top, text="Show GrepDialog", command=show_grep_dialog) button.pack() - root.mainloop() if __name__ == "__main__": import unittest diff --git a/Lib/idlelib/help.py b/Lib/idlelib/help.py index a7008e9..77e01a3 100644 --- a/Lib/idlelib/help.py +++ b/Lib/idlelib/help.py @@ -4,7 +4,7 @@ Contents are subject to revision at any time, without notice. Help => About IDLE: diplay About Idle dialog -<to be moved here from aboutDialog.py> +<to be moved here from help_about.py> Help => IDLE Help: Display help.html with proper formatting. @@ -25,15 +25,14 @@ copy_strip - Copy idle.html to help.html, rstripping each line. show_idlehelp - Create HelpWindow. Called in EditorWindow.help_dialog. """ from html.parser import HTMLParser -from os.path import abspath, dirname, isdir, isfile, join +from os.path import abspath, dirname, isfile, join from platform import python_version -from tkinter import Tk, Toplevel, Frame, Text, Scrollbar, Menu, Menubutton + +from tkinter import Toplevel, Frame, Text, Menu +from tkinter.ttk import Menubutton, Scrollbar from tkinter import font as tkfont -from idlelib.configHandler import idleConf -use_ttk = False # until available to import -if use_ttk: - from tkinter.ttk import Menubutton +from idlelib.config import idleConf ## About IDLE ## @@ -197,15 +196,18 @@ class HelpFrame(Frame): "Display html text, scrollbar, and toc." def __init__(self, parent, filename): Frame.__init__(self, parent) - text = HelpText(self, filename) + # keep references to widgets for test access. + self.text = text = HelpText(self, filename) self['background'] = text['background'] - scroll = Scrollbar(self, command=text.yview) + self.toc = toc = self.toc_menu(text) + self.scroll = scroll = Scrollbar(self, command=text.yview) text['yscrollcommand'] = scroll.set + self.rowconfigure(0, weight=1) self.columnconfigure(1, weight=1) # text - self.toc_menu(text).grid(column=0, row=0, sticky='nw') - text.grid(column=1, row=0, sticky='nsew') - scroll.grid(column=2, row=0, sticky='ns') + toc.grid(row=0, column=0, sticky='nw') + text.grid(row=0, column=1, sticky='nsew') + scroll.grid(row=0, column=2, sticky='ns') def toc_menu(self, text): "Create table of contents as drop-down menu." diff --git a/Lib/idlelib/help.txt b/Lib/idlelib/help.txt deleted file mode 100644 index 89fbe0b..0000000 --- a/Lib/idlelib/help.txt +++ /dev/null @@ -1,372 +0,0 @@ -This file, idlelib/help.txt is out-of-date and no longer used by Idle. -It is deprecated and will be removed in the future, possibly in 3.6 ----------------------------------------------------------------------- - -[See the end of this file for ** TIPS ** on using IDLE !!] - -IDLE is the Python IDE built with the tkinter GUI toolkit. - -IDLE has the following features: --coded in 100% pure Python, using the tkinter GUI toolkit --cross-platform: works on Windows, Unix, and OS X --multi-window text editor with multiple undo, Python colorizing, smart indent, -call tips, and many other features --Python shell window (a.k.a interactive interpreter) --debugger (not complete, but you can set breakpoints, view and step) - -Menus: - -IDLE has two window types the Shell window and the Editor window. It is -possible to have multiple editor windows simultaneously. IDLE's -menus dynamically change based on which window is currently selected. Each menu -documented below indicates which window type it is associated with. - -File Menu (Shell and Editor): - - New File -- Create a new file editing window - Open... -- Open an existing file - Open Module... -- Open an existing module (searches sys.path) - Recent Files... -- Open a list of recent files - Class Browser -- Show classes and methods in current file - Path Browser -- Show sys.path directories, modules, classes, - and methods - --- - Save -- Save current window to the associated file (unsaved - windows have a * before and after the window title) - - Save As... -- Save current window to new file, which becomes - the associated file - Save Copy As... -- Save current window to different file - without changing the associated file - --- - Print Window -- Print the current window - --- - Close -- Close current window (asks to save if unsaved) - Exit -- Close all windows, quit (asks to save if unsaved) - -Edit Menu (Shell and Editor): - - Undo -- Undo last change to current window - (a maximum of 1000 changes may be undone) - Redo -- Redo last undone change to current window - --- - Cut -- Copy a selection into system-wide clipboard, - then delete the selection - Copy -- Copy selection into system-wide clipboard - Paste -- Insert system-wide clipboard into window - Select All -- Select the entire contents of the edit buffer - --- - Find... -- Open a search dialog box with many options - Find Again -- Repeat last search - Find Selection -- Search for the string in the selection - Find in Files... -- Open a search dialog box for searching files - Replace... -- Open a search-and-replace dialog box - Go to Line -- Ask for a line number and show that line - Expand Word -- Expand the word you have typed to match another - word in the same buffer; repeat to get a - different expansion - Show Calltip -- After an unclosed parenthesis for a function, open - a small window with function parameter hints - Show Parens -- Highlight the surrounding parenthesis - Show Completions -- Open a scroll window allowing selection keywords - and attributes. (see '*TIPS*', below) - -Format Menu (Editor window only): - - Indent Region -- Shift selected lines right by the indent width - (default 4 spaces) - Dedent Region -- Shift selected lines left by the indent width - (default 4 spaces) - Comment Out Region -- Insert ## in front of selected lines - Uncomment Region -- Remove leading # or ## from selected lines - Tabify Region -- Turns *leading* stretches of spaces into tabs. - (Note: We recommend using 4 space blocks to indent Python code.) - Untabify Region -- Turn *all* tabs into the corrent number of spaces - Toggle tabs -- Open a dialog to switch between indenting with - spaces and tabs. - New Indent Width... -- Open a dialog to change indent width. The - accepted default by the Python community is 4 - spaces. - Format Paragraph -- Reformat the current blank-line-separated - paragraph. All lines in the paragraph will be - formatted to less than 80 columns. - --- - Strip trailing whitespace -- Removed any space characters after the end - of the last non-space character - -Run Menu (Editor window only): - - Python Shell -- Open or wake up the Python shell window - --- - Check Module -- Check the syntax of the module currently open in the - Editor window. If the module has not been saved IDLE - will prompt the user to save the code. - Run Module -- Restart the shell to clean the environment, then - execute the currently open module. If the module has - not been saved IDLE will prompt the user to save the - code. - -Shell Menu (Shell window only): - - View Last Restart -- Scroll the shell window to the last Shell restart - Restart Shell -- Restart the shell to clean the environment - -Debug Menu (Shell window only): - - Go to File/Line -- Look around the insert point for a filename - and line number, open the file, and show the line. - Useful to view the source lines referenced in an - exception traceback. Available in the context - menu of the Shell window. - Debugger (toggle) -- This feature is not complete and considered - experimental. Run commands in the shell under the - debugger. - Stack Viewer -- Show the stack traceback of the last exception - Auto-open Stack Viewer (toggle) -- Toggle automatically opening the - stack viewer on unhandled - exception - -Options Menu (Shell and Editor): - - Configure IDLE -- Open a configuration dialog. Fonts, indentation, - keybindings, and color themes may be altered. - Startup Preferences may be set, and additional Help - sources can be specified. On OS X, open the - configuration dialog by selecting Preferences - in the application menu. - - --- - Code Context (toggle) -- Open a pane at the top of the edit window - which shows the block context of the section - of code which is scrolling off the top or the - window. This is not present in the Shell - window only the Editor window. - -Window Menu (Shell and Editor): - - Zoom Height -- Toggles the window between normal size (40x80 initial - setting) and maximum height. The initial size is in the Configure - IDLE dialog under the general tab. - --- - The rest of this menu lists the names of all open windows; - select one to bring it to the foreground (deiconifying it if - necessary). - -Help Menu: - - About IDLE -- Version, copyright, license, credits - --- - IDLE Help -- Display this file which is a help file for IDLE - detailing the menu options, basic editing and navigation, - and other tips. - Python Docs -- Access local Python documentation, if - installed. Or will start a web browser and open - docs.python.org showing the latest Python documentation. - --- - Additional help sources may be added here with the Configure IDLE - dialog under the General tab. - -Editor context menu (Right-click / Control-click on OS X in Edit window): - - Cut -- Copy a selection into system-wide clipboard, - then delete the selection - Copy -- Copy selection into system-wide clipboard - Paste -- Insert system-wide clipboard into window - Set Breakpoint -- Sets a breakpoint. Breakpoints are only enabled - when the debugger is open. - Clear Breakpoint -- Clears the breakpoint on that line - -Shell context menu (Right-click / Control-click on OS X in Shell window): - - Cut -- Copy a selection into system-wide clipboard, - then delete the selection - Copy -- Copy selection into system-wide clipboard - Paste -- Insert system-wide clipboard into window - --- - Go to file/line -- Same as in Debug menu - - -** TIPS ** -========== - -Additional Help Sources: - - Windows users can Google on zopeshelf.chm to access Zope help files in - the Windows help format. The Additional Help Sources feature of the - configuration GUI supports .chm, along with any other filetypes - supported by your browser. Supply a Menu Item title, and enter the - location in the Help File Path slot of the New Help Source dialog. Use - http:// and/or www. to identify external URLs, or download the file and - browse for its path on your machine using the Browse button. - - All users can access the extensive sources of help, including - tutorials, available at docs.python.org. Selected URLs can be added - or removed from the Help menu at any time using Configure IDLE. - -Basic editing and navigation: - - Backspace deletes char to the left; DEL deletes char to the right. - Control-backspace deletes word left, Control-DEL deletes word right. - Arrow keys and Page Up/Down move around. - Control-left/right Arrow moves by words in a strange but useful way. - Home/End go to begin/end of line. - Control-Home/End go to begin/end of file. - Some useful Emacs bindings are inherited from Tcl/Tk: - Control-a beginning of line - Control-e end of line - Control-k kill line (but doesn't put it in clipboard) - Control-l center window around the insertion point - Standard keybindings (like Control-c to copy and Control-v to - paste) may work. Keybindings are selected in the Configure IDLE - dialog. - -Automatic indentation: - - 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, Backspace deletes up to 4 spaces if they are there. Tab - 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. - - See also the indent/dedent region commands in the edit menu. - -Completions: - - Completions are supplied for functions, classes, and attributes of - classes, both built-in and user-defined. Completions are also provided - for filenames. - - The AutoCompleteWindow (ACW) will open after a predefined delay - (default is two seconds) after a '.' or (in a string) an os.sep is - typed. If after one of those characters (plus zero or more other - characters) a tab is typed the ACW will open immediately if a possible - continuation is found. - - If there is only one possible completion for the characters entered, a - tab will supply that completion without opening the ACW. - - 'Show Completions' will force open a completions window, by default the - Control-space keys 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. - - If string of characters is typed, the ACW selection will jump to the - entry most closely matching those characters. Entering a tab will cause - the longest non-ambiguous match to be entered in the Edit window or - Shell. Two tabs 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. - - "Hidden" attributes can be accessed by typing the beginning of hidden - name after a '.', e.g. '_'. This allows access to modules with - '__all__' set, or to class-private attributes. - - Completions and the 'Expand Word' facility can save a lot of typing! - - Completions are currently limited to those in the namespaces. Names in - an Editor window which are not via __main__ or sys.modules 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. - - If you don't like the ACW popping up unbidden, simply make the delay - longer or disable the extension. Or another option is the delay could - be set to zero. Another alternative to preventing ACW popups is to - disable the call tips extension. - -Python Shell window: - - Control-c interrupts executing command. - Control-d sends end-of-file; closes window if typed at >>> prompt. - Alt-/ expand word is also useful to reduce typing. - - Command history: - - Alt-p retrieves previous command matching what you have typed. On OS X - use Control-p. - Alt-n retrieves next. On OS X use Control-n. - Return while cursor is on a previous command retrieves that command. - - Syntax colors: - - The coloring is applied in a background "thread", so you may - occasionally see uncolorized text. To change the color - scheme, use the Configure IDLE / Highlighting dialog. - - Python default syntax colors: - - Keywords orange - Builtins royal purple - Strings green - Comments red - Definitions blue - - Shell default colors: - - Console output brown - stdout blue - stderr red - stdin black - -Other preferences: - - The font preferences, highlighting, keys, and general preferences can - be changed via the Configure IDLE menu option. Be sure to note that - keys can be user defined, IDLE ships with four built in key sets. In - addition a user can create a custom key set in the Configure IDLE - dialog under the keys tab. - -Command line usage: - - Enter idle -h at the command prompt to get a usage message. - - idle.py [-c command] [-d] [-e] [-s] [-t title] [arg] ... - - -c command run this command - -d enable debugger - -e edit mode; arguments are files to be edited - -s run $IDLESTARTUP or $PYTHONSTARTUP first - -t title set title of shell window - - If there are arguments: - 1. If -e is used, arguments are files opened for editing and sys.argv - reflects the arguments passed to IDLE itself. - 2. Otherwise, if -c is used, all arguments are placed in - sys.argv[1:...], with sys.argv[0] set to -c. - 3. Otherwise, if neither -e nor -c is used, the first argument is a - script which is executed with the remaining arguments in - sys.argv[1:...] and sys.argv[0] set to the script name. If the - script name is -, no script is executed but an interactive Python - session is started; the arguments are still available in sys.argv. - -Running without a subprocess: (DEPRECATED in Python 3.4 see Issue 16123) - - If IDLE is started with the -n command line switch it will run in a - single process and will not create the subprocess which runs the RPC - Python execution server. This can be useful if Python cannot create - the subprocess or the RPC socket interface on your platform. However, - in this mode user code is not isolated from IDLE itself. Also, the - environment is not restarted when Run/Run Module (F5) is selected. If - your code has been modified, you must reload() the affected modules and - re-import any specific items (e.g. from foo import baz) if the changes - are to take effect. For these reasons, it is preferable to run IDLE - with the default subprocess if at all possible. - -Extensions: - - IDLE contains an extension facility. See the beginning of - config-extensions.def in the idlelib directory for further information. - The default extensions are currently: - - FormatParagraph - AutoExpand - ZoomHeight - ScriptBinding - CallTips - ParenMatch - AutoComplete - CodeContext diff --git a/Lib/idlelib/aboutDialog.py b/Lib/idlelib/help_about.py index a8f75d2..071bd3e 100644 --- a/Lib/idlelib/aboutDialog.py +++ b/Lib/idlelib/help_about.py @@ -1,11 +1,13 @@ """About Dialog for IDLE """ - import os from sys import version + from tkinter import * -from idlelib import textView + +from idlelib import textview + class AboutDialog(Toplevel): """Modal about dialog for idle @@ -135,17 +137,18 @@ class AboutDialog(Toplevel): def display_printer_text(self, title, printer): printer._Printer__setup() text = '\n'.join(printer._Printer__lines) - textView.view_text(self, title, text) + textview.view_text(self, title, text) def display_file_text(self, title, filename, encoding=None): fn = os.path.join(os.path.abspath(os.path.dirname(__file__)), filename) - textView.view_file(self, title, fn, encoding) + textview.view_file(self, title, fn, encoding) def Ok(self, event=None): self.destroy() + if __name__ == '__main__': import unittest - unittest.main('idlelib.idle_test.test_helpabout', verbosity=2, exit=False) + unittest.main('idlelib.idle_test.test_help_about', verbosity=2, exit=False) from idlelib.idle_test.htest import run run(AboutDialog) diff --git a/Lib/idlelib/IdleHistory.py b/Lib/idlelib/history.py index 078af29..56f53a0 100644 --- a/Lib/idlelib/IdleHistory.py +++ b/Lib/idlelib/history.py @@ -1,11 +1,12 @@ "Implement Idle Shell history mechanism with History class" -from idlelib.configHandler import idleConf +from idlelib.config import idleConf + class History: ''' Implement Idle Shell history mechanism. - store - Store source statement (called from PyShell.resetoutput). + store - Store source statement (called from pyshell.resetoutput). fetch - Fetch stored statement matching prefix already entered. history_next - Bound to <<history-next>> event (default Alt-N). history_prev - Bound to <<history-prev>> event (default Alt-P). @@ -99,6 +100,7 @@ class History: self.pointer = None self.prefix = None + if __name__ == "__main__": from unittest import main - main('idlelib.idle_test.test_idlehistory', verbosity=2, exit=False) + main('idlelib.idle_test.test_history', verbosity=2, exit=False) diff --git a/Lib/idlelib/HyperParser.py b/Lib/idlelib/hyperparser.py index 77cb057..450a709 100644 --- a/Lib/idlelib/HyperParser.py +++ b/Lib/idlelib/hyperparser.py @@ -4,11 +4,10 @@ HyperParser uses PyParser. PyParser mostly gives information on the proper indentation of code. HyperParser gives additional information on the structure of code. """ - -import string from keyword import iskeyword -from idlelib import PyParse +import string +from idlelib import pyparse # all ASCII chars that may be in an identifier _ASCII_ID_CHARS = frozenset(string.ascii_letters + string.digits + "_") @@ -30,7 +29,7 @@ class HyperParser: self.editwin = editwin self.text = text = editwin.text - parser = PyParse.Parser(editwin.indentwidth, editwin.tabwidth) + parser = pyparse.Parser(editwin.indentwidth, editwin.tabwidth) def index2line(index): return int(float(index)) diff --git a/Lib/idlelib/idle.py b/Lib/idlelib/idle.py index 141534d..485d5a7 100644 --- a/Lib/idlelib/idle.py +++ b/Lib/idlelib/idle.py @@ -1,6 +1,7 @@ import os.path import sys + # Enable running IDLE with idlelib in a non-standard location. # This was once used to run development versions of IDLE. # Because PEP 434 declared idle.py a public interface, @@ -9,5 +10,5 @@ idlelib_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) if idlelib_dir not in sys.path: sys.path.insert(0, idlelib_dir) -from idlelib.PyShell import main # This is subject to change +from idlelib.pyshell import main # This is subject to change main() diff --git a/Lib/idlelib/idle.pyw b/Lib/idlelib/idle.pyw index 142cb32..e73c049 100644 --- a/Lib/idlelib/idle.pyw +++ b/Lib/idlelib/idle.pyw @@ -1,10 +1,10 @@ try: - import idlelib.PyShell + import idlelib.pyshell except ImportError: - # IDLE is not installed, but maybe PyShell is on sys.path: - from . import PyShell + # IDLE is not installed, but maybe pyshell is on sys.path: + from . import pyshell import os - idledir = os.path.dirname(os.path.abspath(PyShell.__file__)) + idledir = os.path.dirname(os.path.abspath(pyshell.__file__)) if idledir != os.getcwd(): # We're not in the IDLE directory, help the subprocess find run.py pypath = os.environ.get('PYTHONPATH', '') @@ -12,6 +12,6 @@ except ImportError: os.environ['PYTHONPATH'] = pypath + ':' + idledir else: os.environ['PYTHONPATH'] = idledir - PyShell.main() + pyshell.main() else: - idlelib.PyShell.main() + idlelib.pyshell.main() diff --git a/Lib/idlelib/idle_test/__init__.py b/Lib/idlelib/idle_test/__init__.py index 845c92d..ad067b4 100644 --- a/Lib/idlelib/idle_test/__init__.py +++ b/Lib/idlelib/idle_test/__init__.py @@ -1,6 +1,8 @@ '''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". +Starting with Python 3.6, IDLE requires tcl/tk 8.5 or later. + This package and its contained modules are subject to change and any direct use is at your own risk. ''' diff --git a/Lib/idlelib/idle_test/htest.py b/Lib/idlelib/idle_test/htest.py index 58e62cb..6f676ae 100644 --- a/Lib/idlelib/idle_test/htest.py +++ b/Lib/idlelib/idle_test/htest.py @@ -59,19 +59,20 @@ msg: master window hints about testing the widget. Modules and classes not being tested at the moment: -PyShell.PyShellEditorWindow -Debugger.Debugger -AutoCompleteWindow.AutoCompleteWindow -OutputWindow.OutputWindow (indirectly being tested with grep test) +pyshell.PyShellEditorWindow +debugger.Debugger +autocomplete_w.AutoCompleteWindow +outwin.OutputWindow (indirectly being tested with grep test) ''' from importlib import import_module -from idlelib.macosxSupport import _initializeTkVariantTests import tkinter as tk +from tkinter.ttk import Scrollbar +tk.NoDefaultRoot() AboutDialog_spec = { - 'file': 'aboutDialog', - 'kwds': {'title': 'aboutDialog test', + 'file': 'help_about', + 'kwds': {'title': 'help_about test', '_htest': True, }, 'msg': "Test every button. Ensure Python, TK and IDLE versions " @@ -79,14 +80,14 @@ AboutDialog_spec = { } _calltip_window_spec = { - 'file': 'CallTipWindow', + 'file': 'calltip_w', 'kwds': {}, 'msg': "Typing '(' should display a calltip.\n" "Typing ') should hide the calltip.\n" } _class_browser_spec = { - 'file': 'ClassBrowser', + 'file': 'browser', 'kwds': {}, 'msg': "Inspect names of module, class(with superclass if " "applicable), methods and functions.\nToggle nested items.\n" @@ -95,7 +96,7 @@ _class_browser_spec = { } _color_delegator_spec = { - 'file': 'ColorDelegator', + 'file': 'colorizer', 'kwds': {}, 'msg': "The text is sample Python code.\n" "Ensure components like comments, keywords, builtins,\n" @@ -104,7 +105,7 @@ _color_delegator_spec = { } ConfigDialog_spec = { - 'file': 'configDialog', + 'file': 'configdialog', 'kwds': {'title': 'ConfigDialogTest', '_htest': True,}, 'msg': "IDLE preferences dialog.\n" @@ -121,7 +122,7 @@ ConfigDialog_spec = { # TODO Improve message _dyn_option_menu_spec = { - 'file': 'dynOptionMenuWidget', + 'file': 'dynoption', 'kwds': {}, 'msg': "Select one of the many options in the 'old option set'.\n" "Click the button to change the option set.\n" @@ -130,39 +131,15 @@ _dyn_option_menu_spec = { # TODO edit wrapper _editor_window_spec = { - 'file': 'EditorWindow', + 'file': 'editor', 'kwds': {}, 'msg': "Test editor functions of interest.\n" "Best to close editor first." } -GetCfgSectionNameDialog_spec = { - 'file': 'configSectionNameDialog', - 'kwds': {'title':'Get Name', - 'message':'Enter something', - 'used_names': {'abc'}, - '_htest': True}, - 'msg': "After the text entered with [Ok] is stripped, <nothing>, " - "'abc', or more that 30 chars are errors.\n" - "Close 'Get Name' with a valid entry (printed to Shell), " - "[Cancel], or [X]", - } - -GetHelpSourceDialog_spec = { - 'file': 'configHelpSourceEdit', - 'kwds': {'title': 'Get helpsource', - '_htest': True}, - 'msg': "Enter menu item name and help file path\n " - "<nothing> and more than 30 chars are invalid menu item names.\n" - "<nothing>, file does not exist are invalid path items.\n" - "Test for incomplete web address for help file path.\n" - "A valid entry will be printed to shell with [0k].\n" - "[Cancel] will print None to shell", - } - # Update once issue21519 is resolved. GetKeysDialog_spec = { - 'file': 'keybindingDialog', + 'file': 'config_key', 'kwds': {'title': 'Test keybindings', 'action': 'find-again', 'currentKeySequences': [''] , @@ -177,7 +154,7 @@ GetKeysDialog_spec = { } _grep_dialog_spec = { - 'file': 'GrepDialog', + 'file': 'grep', 'kwds': {}, 'msg': "Click the 'Show GrepDialog' button.\n" "Test the various 'Find-in-files' functions.\n" @@ -186,8 +163,24 @@ _grep_dialog_spec = { "should open that file \nin a new EditorWindow." } +HelpSource_spec = { + 'file': 'query', + 'kwds': {'title': 'Help name and source', + 'menuitem': 'test', + 'filepath': __file__, + 'used_names': {'abc'}, + '_htest': True}, + 'msg': "Enter menu item name and help file path\n" + "'', > than 30 chars, and 'abc' are invalid menu item names.\n" + "'' and file does not exist are invalid path items.\n" + "Any url ('www...', 'http...') is accepted.\n" + "Test Browse with and without path, as cannot unittest.\n" + "[Ok] or <Return> prints valid entry to shell\n" + "[Cancel] or <Escape> prints None to shell" + } + _io_binding_spec = { - 'file': 'IOBinding', + 'file': 'iomenu', 'kwds': {}, 'msg': "Test the following bindings.\n" "<Control-o> to open file from dialog.\n" @@ -200,7 +193,7 @@ _io_binding_spec = { } _multi_call_spec = { - 'file': 'MultiCall', + 'file': 'multicall', 'kwds': {}, 'msg': "The following actions should trigger a print to console or IDLE" " Shell.\nEntering and leaving the text area, key entry, " @@ -210,14 +203,14 @@ _multi_call_spec = { } _multistatus_bar_spec = { - 'file': 'MultiStatusBar', + 'file': 'statusbar', 'kwds': {}, 'msg': "Ensure presence of multi-status bar below text area.\n" "Click 'Update Status' to change the multi-status text" } _object_browser_spec = { - 'file': 'ObjectBrowser', + 'file': 'debugobj', 'kwds': {}, 'msg': "Double click on items upto the lowest level.\n" "Attributes of the objects and related information " @@ -225,7 +218,7 @@ _object_browser_spec = { } _path_browser_spec = { - 'file': 'PathBrowser', + 'file': 'pathbrowser', 'kwds': {}, 'msg': "Test for correct display of all paths in sys.path.\n" "Toggle nested items upto the lowest level.\n" @@ -234,7 +227,7 @@ _path_browser_spec = { } _percolator_spec = { - 'file': 'Percolator', + 'file': 'percolator', 'kwds': {}, 'msg': "There are two tracers which can be toggled using a checkbox.\n" "Toggling a tracer 'on' by checking it should print tracer" @@ -244,8 +237,20 @@ _percolator_spec = { "Test for actions like text entry, and removal." } +Query_spec = { + 'file': 'query', + 'kwds': {'title': 'Query', + 'message': 'Enter something', + 'text0': 'Go', + '_htest': True}, + 'msg': "Enter with <Return> or [Ok]. Print valid entry to Shell\n" + "Blank line, after stripping, is ignored\n" + "Close dialog with valid entry, <Escape>, [Cancel], [X]" + } + + _replace_dialog_spec = { - 'file': 'ReplaceDialog', + 'file': 'replace', 'kwds': {}, 'msg': "Click the 'Replace' button.\n" "Test various replace options in the 'Replace dialog'.\n" @@ -253,15 +258,22 @@ _replace_dialog_spec = { } _search_dialog_spec = { - 'file': 'SearchDialog', + 'file': 'search', 'kwds': {}, 'msg': "Click the 'Search' button.\n" "Test various search options in the 'Search dialog'.\n" "Click [Close] or [X] to close the 'Search Dialog'." } +_searchbase_spec = { + 'file': 'searchbase', + 'kwds': {}, + 'msg': "Check the appearance of the base search dialog\n" + "Its only action is to close." + } + _scrolled_list_spec = { - 'file': 'ScrolledList', + 'file': 'scrolledlist', 'kwds': {}, 'msg': "You should see a scrollable list of items\n" "Selecting (clicking) or double clicking an item " @@ -277,7 +289,7 @@ show_idlehelp_spec = { } _stack_viewer_spec = { - 'file': 'StackViewer', + 'file': 'stackviewer', 'kwds': {}, 'msg': "A stacktrace for a NameError exception.\n" "Expand 'idlelib ...' and '<locals>'.\n" @@ -295,8 +307,8 @@ _tabbed_pages_spec = { } TextViewer_spec = { - 'file': 'textView', - 'kwds': {'title': 'Test textView', + 'file': 'textview', + 'kwds': {'title': 'Test textview', 'text':'The quick brown fox jumps over the lazy dog.\n'*35, '_htest': True}, 'msg': "Test for read-only property of text.\n" @@ -304,21 +316,21 @@ TextViewer_spec = { } _tooltip_spec = { - 'file': 'ToolTip', + 'file': 'tooltip', 'kwds': {}, 'msg': "Place mouse cursor over both the buttons\n" "A tooltip should appear with some text." } _tree_widget_spec = { - 'file': 'TreeWidget', + 'file': 'tree', 'kwds': {}, 'msg': "The canvas is scrollable.\n" "Click on folders upto to the lowest level." } _undo_delegator_spec = { - 'file': 'UndoDelegator', + 'file': 'undo', 'kwds': {}, 'msg': "Click [Undo] to undo any action.\n" "Click [Redo] to redo any action.\n" @@ -327,7 +339,7 @@ _undo_delegator_spec = { } _widget_redirector_spec = { - 'file': 'WidgetRedirector', + 'file': 'redirector', 'kwds': {}, 'msg': "Every text insert should be printed to the console." "or the IDLE shell." @@ -337,14 +349,13 @@ def run(*tests): root = tk.Tk() root.title('IDLE htest') root.resizable(0, 0) - _initializeTkVariantTests(root) # a scrollable Label like constant width text widget. frameLabel = tk.Frame(root, padx=10) frameLabel.pack() text = tk.Text(frameLabel, wrap='word') text.configure(bg=root.cget('bg'), relief='flat', height=4, width=70) - scrollbar = tk.Scrollbar(frameLabel, command=text.yview) + scrollbar = Scrollbar(frameLabel, command=text.yview) text.config(yscrollcommand=scrollbar.set) scrollbar.pack(side='right', fill='y', expand=False) text.pack(side='left', fill='both', expand=True) @@ -365,11 +376,11 @@ def run(*tests): test = getattr(mod, test_name) test_list.append((test_spec, test)) - test_name = tk.StringVar('') + test_name = tk.StringVar(root) callable_object = None test_kwds = None - def next(): + def next_test(): nonlocal test_name, callable_object, test_kwds if len(test_list) == 1: @@ -384,20 +395,26 @@ def run(*tests): text.insert("1.0",test_spec['msg']) text.configure(state='disabled') # preserve read-only property - def run_test(): + def run_test(_=None): widget = callable_object(**test_kwds) try: print(widget.result) except AttributeError: pass - button = tk.Button(root, textvariable=test_name, command=run_test) + def close(_=None): + root.destroy() + + button = tk.Button(root, textvariable=test_name, + default='active', command=run_test) + next_button = tk.Button(root, text="Next", command=next_test) button.pack() - next_button = tk.Button(root, text="Next", command=next) next_button.pack() + next_button.focus_set() + root.bind('<Key-Return>', run_test) + root.bind('<Key-Escape>', close) - next() - + next_test() root.mainloop() if __name__ == '__main__': diff --git a/Lib/idlelib/idle_test/mock_idle.py b/Lib/idlelib/idle_test/mock_idle.py index 1672a34..c7b49ef 100644 --- a/Lib/idlelib/idle_test/mock_idle.py +++ b/Lib/idlelib/idle_test/mock_idle.py @@ -33,7 +33,7 @@ class Func: class Editor: - '''Minimally imitate EditorWindow.EditorWindow class. + '''Minimally imitate editor.EditorWindow class. ''' def __init__(self, flist=None, filename=None, key=None, root=None): self.text = Text() @@ -46,7 +46,7 @@ class Editor: class UndoDelegator: - '''Minimally imitate UndoDelegator,UndoDelegator class. + '''Minimally imitate undo.UndoDelegator class. ''' # A real undo block is only needed for user interaction. def undo_block_start(*args): diff --git a/Lib/idlelib/idle_test/test_autocomplete.py b/Lib/idlelib/idle_test/test_autocomplete.py index 5fc899d..f3f2dea 100644 --- a/Lib/idlelib/idle_test/test_autocomplete.py +++ b/Lib/idlelib/idle_test/test_autocomplete.py @@ -1,9 +1,13 @@ +''' Test autocomplete and autocomple_w + +Coverage of autocomple: 56% +''' import unittest from test.support import requires from tkinter import Tk, Text -import idlelib.AutoComplete as ac -import idlelib.AutoCompleteWindow as acw +import idlelib.autocomplete as ac +import idlelib.autocomplete_w as acw from idlelib.idle_test.mock_idle import Func from idlelib.idle_test.mock_tk import Event @@ -91,6 +95,11 @@ class AutoCompleteTest(unittest.TestCase): self.assertIsNone(autocomplete.autocomplete_event(ev)) del ev.mc_state + # Test that tab after whitespace is ignored. + self.text.insert('1.0', ' """Docstring.\n ') + self.assertIsNone(autocomplete.autocomplete_event(ev)) + self.text.delete('1.0', 'end') + # If autocomplete window is open, complete() method is called self.text.insert('1.0', 're.') # This must call autocomplete._make_autocomplete_window() diff --git a/Lib/idlelib/idle_test/test_autoexpand.py b/Lib/idlelib/idle_test/test_autoexpand.py index d2a3156..ae8186c 100644 --- a/Lib/idlelib/idle_test/test_autoexpand.py +++ b/Lib/idlelib/idle_test/test_autoexpand.py @@ -1,9 +1,9 @@ -"""Unit tests for idlelib.AutoExpand""" +"""Unit tests for idlelib.autoexpand""" import unittest from test.support import requires from tkinter import Text, Tk #from idlelib.idle_test.mock_tk import Text -from idlelib.AutoExpand import AutoExpand +from idlelib.autoexpand import AutoExpand class Dummy_Editwin: @@ -22,6 +22,7 @@ class AutoExpandTest(unittest.TestCase): else: cls.text = Text() cls.auto_expand = AutoExpand(Dummy_Editwin(cls.text)) + cls.auto_expand.bell = lambda: None @classmethod def tearDownClass(cls): @@ -137,5 +138,6 @@ class AutoExpandTest(unittest.TestCase): new_state = self.auto_expand.state self.assertNotEqual(initial_state, new_state) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_calltips.py b/Lib/idlelib/idle_test/test_calltips.py index b2a733c..0b11602 100644 --- a/Lib/idlelib/idle_test/test_calltips.py +++ b/Lib/idlelib/idle_test/test_calltips.py @@ -1,5 +1,5 @@ import unittest -import idlelib.CallTips as ct +import idlelib.calltips as ct import textwrap import types diff --git a/Lib/idlelib/idle_test/test_colorizer.py b/Lib/idlelib/idle_test/test_colorizer.py new file mode 100644 index 0000000..238bc3e --- /dev/null +++ b/Lib/idlelib/idle_test/test_colorizer.py @@ -0,0 +1,56 @@ +'''Test idlelib/colorizer.py + +Perform minimal sanity checks that module imports and some things run. + +Coverage 22%. +''' +from idlelib import colorizer # always test import +from test.support import requires +from tkinter import Tk, Text +import unittest + + +class FunctionTest(unittest.TestCase): + + def test_any(self): + self.assertTrue(colorizer.any('test', ('a', 'b'))) + + def test_make_pat(self): + self.assertTrue(colorizer.make_pat()) + + +class ColorConfigTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + requires('gui') + cls.root = Tk() + cls.text = Text(cls.root) + + @classmethod + def tearDownClass(cls): + del cls.text + cls.root.destroy() + del cls.root + + def test_colorizer(self): + colorizer.color_config(self.text) + +class ColorDelegatorTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + requires('gui') + cls.root = Tk() + + @classmethod + def tearDownClass(cls): + cls.root.destroy() + del cls.root + + def test_colorizer(self): + colorizer.ColorDelegator() + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_config.py b/Lib/idlelib/idle_test/test_config.py new file mode 100644 index 0000000..a3fa1a3 --- /dev/null +++ b/Lib/idlelib/idle_test/test_config.py @@ -0,0 +1,160 @@ +'''Test idlelib.config. + +Much is tested by opening config dialog live or in test_configdialog. +Coverage: 27% +''' +from sys import modules +from test.support import captured_stderr +from tkinter import Tk +import unittest +from idlelib import config + +# Tests should not depend on fortuitous user configurations. +# They must not affect actual user .cfg files. +# Replace user parsers with empty parsers that cannot be saved. + +idleConf = config.idleConf +usercfg = idleConf.userCfg +testcfg = {} +usermain = testcfg['main'] = config.IdleUserConfParser('') # filename +userhigh = testcfg['highlight'] = config.IdleUserConfParser('') +userkeys = testcfg['keys'] = config.IdleUserConfParser('') + +def setUpModule(): + idleConf.userCfg = testcfg + +def tearDownModule(): + idleConf.userCfg = usercfg + + +class CurrentColorKeysTest(unittest.TestCase): + """ Test colorkeys function with user config [Theme] and [Keys] patterns. + + colorkeys = config.IdleConf.current_colors_and_keys + Test all patterns written by IDLE and some errors + Item 'default' should really be 'builtin' (versus 'custom). + """ + colorkeys = idleConf.current_colors_and_keys + default_theme = 'IDLE Classic' + default_keys = idleConf.default_keys() + + def test_old_builtin_theme(self): + # On initial installation, user main is blank. + self.assertEqual(self.colorkeys('Theme'), self.default_theme) + # For old default, name2 must be blank. + usermain.read_string(''' + [Theme] + default = True + ''') + # IDLE omits 'name' for default old builtin theme. + self.assertEqual(self.colorkeys('Theme'), self.default_theme) + # IDLE adds 'name' for non-default old builtin theme. + usermain['Theme']['name'] = 'IDLE New' + self.assertEqual(self.colorkeys('Theme'), 'IDLE New') + # Erroneous non-default old builtin reverts to default. + usermain['Theme']['name'] = 'non-existent' + self.assertEqual(self.colorkeys('Theme'), self.default_theme) + usermain.remove_section('Theme') + + def test_new_builtin_theme(self): + # IDLE writes name2 for new builtins. + usermain.read_string(''' + [Theme] + default = True + name2 = IDLE Dark + ''') + self.assertEqual(self.colorkeys('Theme'), 'IDLE Dark') + # Leftover 'name', not removed, is ignored. + usermain['Theme']['name'] = 'IDLE New' + self.assertEqual(self.colorkeys('Theme'), 'IDLE Dark') + # Erroneous non-default new builtin reverts to default. + usermain['Theme']['name2'] = 'non-existent' + self.assertEqual(self.colorkeys('Theme'), self.default_theme) + usermain.remove_section('Theme') + + def test_user_override_theme(self): + # Erroneous custom name (no definition) reverts to default. + usermain.read_string(''' + [Theme] + default = False + name = Custom Dark + ''') + self.assertEqual(self.colorkeys('Theme'), self.default_theme) + # Custom name is valid with matching Section name. + userhigh.read_string('[Custom Dark]\na=b') + self.assertEqual(self.colorkeys('Theme'), 'Custom Dark') + # Name2 is ignored. + usermain['Theme']['name2'] = 'non-existent' + self.assertEqual(self.colorkeys('Theme'), 'Custom Dark') + usermain.remove_section('Theme') + userhigh.remove_section('Custom Dark') + + def test_old_builtin_keys(self): + # On initial installation, user main is blank. + self.assertEqual(self.colorkeys('Keys'), self.default_keys) + # For old default, name2 must be blank, name is always used. + usermain.read_string(''' + [Keys] + default = True + name = IDLE Classic Unix + ''') + self.assertEqual(self.colorkeys('Keys'), 'IDLE Classic Unix') + # Erroneous non-default old builtin reverts to default. + usermain['Keys']['name'] = 'non-existent' + self.assertEqual(self.colorkeys('Keys'), self.default_keys) + usermain.remove_section('Keys') + + def test_new_builtin_keys(self): + # IDLE writes name2 for new builtins. + usermain.read_string(''' + [Keys] + default = True + name2 = IDLE Modern Unix + ''') + self.assertEqual(self.colorkeys('Keys'), 'IDLE Modern Unix') + # Leftover 'name', not removed, is ignored. + usermain['Keys']['name'] = 'IDLE Classic Unix' + self.assertEqual(self.colorkeys('Keys'), 'IDLE Modern Unix') + # Erroneous non-default new builtin reverts to default. + usermain['Keys']['name2'] = 'non-existent' + self.assertEqual(self.colorkeys('Keys'), self.default_keys) + usermain.remove_section('Keys') + + def test_user_override_keys(self): + # Erroneous custom name (no definition) reverts to default. + usermain.read_string(''' + [Keys] + default = False + name = Custom Keys + ''') + self.assertEqual(self.colorkeys('Keys'), self.default_keys) + # Custom name is valid with matching Section name. + userkeys.read_string('[Custom Keys]\na=b') + self.assertEqual(self.colorkeys('Keys'), 'Custom Keys') + # Name2 is ignored. + usermain['Keys']['name2'] = 'non-existent' + self.assertEqual(self.colorkeys('Keys'), 'Custom Keys') + usermain.remove_section('Keys') + userkeys.remove_section('Custom Keys') + + +class WarningTest(unittest.TestCase): + + def test_warn(self): + Equal = self.assertEqual + config._warned = set() + with captured_stderr() as stderr: + config._warn('warning', 'key') + Equal(config._warned, {('warning','key')}) + Equal(stderr.getvalue(), 'warning'+'\n') + with captured_stderr() as stderr: + config._warn('warning', 'key') + Equal(stderr.getvalue(), '') + with captured_stderr() as stderr: + config._warn('warn2', 'yek') + Equal(config._warned, {('warning','key'), ('warn2','yek')}) + Equal(stderr.getvalue(), 'warn2'+'\n') + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_config_help.py b/Lib/idlelib/idle_test/test_config_help.py deleted file mode 100644 index 664f8ed..0000000 --- a/Lib/idlelib/idle_test/test_config_help.py +++ /dev/null @@ -1,106 +0,0 @@ -"""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_config_key.py b/Lib/idlelib/idle_test/test_config_key.py new file mode 100644 index 0000000..ee3f2c8 --- /dev/null +++ b/Lib/idlelib/idle_test/test_config_key.py @@ -0,0 +1,33 @@ +''' Test idlelib.config_key. + +Coverage: 56% from creating and closing dialog. +''' +from idlelib import config_key +from test.support import requires +requires('gui') +import unittest +from tkinter import Tk, Text + + +class GetKeysTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.root = Tk() + cls.root.withdraw() + + @classmethod + def tearDownClass(cls): + cls.root.update() # Stop "can't run event command" warning. + cls.root.destroy() + del cls.root + + + def test_init(self): + dia = config_key.GetKeysDialog( + self.root, 'test', '<<Test>>', ['<Key-F12>'], _utest=True) + dia.Cancel() + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_config_name.py b/Lib/idlelib/idle_test/test_config_name.py deleted file mode 100644 index 40e72b9..0000000 --- a/Lib/idlelib/idle_test/test_config_name.py +++ /dev/null @@ -1,75 +0,0 @@ -"""Unit tests for idlelib.configSectionNameDialog""" -import unittest -from idlelib.idle_test.mock_tk import Var, Mbox -from idlelib import configSectionNameDialog as name_dialog_module - -name_dialog = name_dialog_module.GetCfgSectionNameDialog - -class Dummy_name_dialog: - # Mock for testing the following methods of name_dialog - name_ok = name_dialog.name_ok - Ok = name_dialog.Ok - Cancel = name_dialog.Cancel - # Attributes, constant or variable, needed for tests - used_names = ['used'] - name = Var() - result = None - destroyed = False - def destroy(self): - self.destroyed = True - -# name_ok calls Mbox.showerror if name is not ok -orig_mbox = name_dialog_module.tkMessageBox -showerror = Mbox.showerror - -class ConfigNameTest(unittest.TestCase): - dialog = Dummy_name_dialog() - - @classmethod - def setUpClass(cls): - name_dialog_module.tkMessageBox = Mbox - - @classmethod - def tearDownClass(cls): - name_dialog_module.tkMessageBox = orig_mbox - - def test_blank_name(self): - self.dialog.name.set(' ') - self.assertEqual(self.dialog.name_ok(), '') - self.assertEqual(showerror.title, 'Name Error') - self.assertIn('No', showerror.message) - - def test_used_name(self): - self.dialog.name.set('used') - self.assertEqual(self.dialog.name_ok(), '') - self.assertEqual(showerror.title, 'Name Error') - self.assertIn('use', showerror.message) - - def test_long_name(self): - self.dialog.name.set('good'*8) - self.assertEqual(self.dialog.name_ok(), '') - self.assertEqual(showerror.title, 'Name Error') - self.assertIn('too long', showerror.message) - - def test_good_name(self): - self.dialog.name.set(' good ') - showerror.title = 'No Error' # should not be called - self.assertEqual(self.dialog.name_ok(), 'good') - self.assertEqual(showerror.title, 'No Error') - - def test_ok(self): - self.dialog.destroyed = False - self.dialog.name.set('good') - self.dialog.Ok() - self.assertEqual(self.dialog.result, 'good') - self.assertTrue(self.dialog.destroyed) - - def test_cancel(self): - self.dialog.destroyed = False - self.dialog.Cancel() - self.assertEqual(self.dialog.result, '') - 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 5c09790..70bd14e 100644 --- a/Lib/idlelib/idle_test/test_configdialog.py +++ b/Lib/idlelib/idle_test/test_configdialog.py @@ -1,14 +1,13 @@ -'''Test idlelib.configDialog. +'''Test idlelib.configdialog. Coverage: 46% just by creating dialog. The other half is code for working with user customizations. ''' -from idlelib.configDialog import ConfigDialog # always test import +from idlelib.configdialog import ConfigDialog # always test import from test.support import requires requires('gui') from tkinter import Tk import unittest -from idlelib import macosxSupport as macosx class ConfigDialogTest(unittest.TestCase): @@ -16,7 +15,6 @@ class ConfigDialogTest(unittest.TestCase): def setUpClass(cls): cls.root = Tk() cls.root.withdraw() - macosx._initializeTkVariantTests(cls.root) @classmethod def tearDownClass(cls): @@ -24,7 +22,7 @@ class ConfigDialogTest(unittest.TestCase): cls.root.destroy() del cls.root - def test_dialog(self): + def test_configdialog(self): d = ConfigDialog(self.root, 'Test', _utest=True) d.remove_var_callbacks() diff --git a/Lib/idlelib/idle_test/test_debugger.py b/Lib/idlelib/idle_test/test_debugger.py new file mode 100644 index 0000000..bcba9a4 --- /dev/null +++ b/Lib/idlelib/idle_test/test_debugger.py @@ -0,0 +1,29 @@ +''' Test idlelib.debugger. + +Coverage: 19% +''' +from idlelib import debugger +from test.support import requires +requires('gui') +import unittest +from tkinter import Tk + + +class NameSpaceTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.root = Tk() + cls.root.withdraw() + + @classmethod + def tearDownClass(cls): + cls.root.destroy() + del cls.root + + def test_init(self): + debugger.NamespaceViewer(self.root, 'Test') + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_delegator.py b/Lib/idlelib/idle_test/test_delegator.py index 1f0baa9..85624fb 100644 --- a/Lib/idlelib/idle_test/test_delegator.py +++ b/Lib/idlelib/idle_test/test_delegator.py @@ -1,5 +1,5 @@ import unittest -from idlelib.Delegator import Delegator +from idlelib.delegator import Delegator class DelegatorTest(unittest.TestCase): diff --git a/Lib/idlelib/idle_test/test_editmenu.py b/Lib/idlelib/idle_test/test_editmenu.py index a258e29..17eb25c 100644 --- a/Lib/idlelib/idle_test/test_editmenu.py +++ b/Lib/idlelib/idle_test/test_editmenu.py @@ -5,8 +5,9 @@ Edit modules have their own test files files from test.support import requires requires('gui') import tkinter as tk +from tkinter import ttk import unittest -from idlelib import PyShell +from idlelib import pyshell class PasteTest(unittest.TestCase): '''Test pasting into widgets that allow pasting. @@ -16,17 +17,18 @@ class PasteTest(unittest.TestCase): @classmethod def setUpClass(cls): cls.root = root = tk.Tk() - root.withdraw() - PyShell.fix_x11_paste(root) + cls.root.withdraw() + pyshell.fix_x11_paste(root) cls.text = tk.Text(root) cls.entry = tk.Entry(root) + cls.tentry = ttk.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 + del cls.text, cls.entry, cls.tentry cls.root.clipboard_clear() cls.root.update_idletasks() cls.root.destroy() @@ -44,16 +46,16 @@ class PasteTest(unittest.TestCase): 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) + # Generated <<Paste>> fails for tk entry without empty select + # range for 'no selection'. Live widget works fine. + for entry in self.entry, self.tentry: + 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) + entry.event_generate('<<Paste>>') + self.assertEqual(entry.get(), ans) def test_paste_spin(self): "Test pasting into a spinbox with and without a selection." diff --git a/Lib/idlelib/idle_test/test_editor.py b/Lib/idlelib/idle_test/test_editor.py index a31d26d..e9d29d4 100644 --- a/Lib/idlelib/idle_test/test_editor.py +++ b/Lib/idlelib/idle_test/test_editor.py @@ -1,6 +1,6 @@ import unittest from tkinter import Tk, Text -from idlelib.EditorWindow import EditorWindow +from idlelib.editor import EditorWindow from test.support import requires class Editor_func_test(unittest.TestCase): diff --git a/Lib/idlelib/idle_test/test_grep.py b/Lib/idlelib/idle_test/test_grep.py index 0d8ff0d..6b54c13 100644 --- a/Lib/idlelib/idle_test/test_grep.py +++ b/Lib/idlelib/idle_test/test_grep.py @@ -1,5 +1,5 @@ """ !Changing this line will break Test_findfile.test_found! -Non-gui unit tests for idlelib.GrepDialog methods. +Non-gui unit tests for grep.GrepDialog methods. dummy_command calls grep_it calls findfiles. An exception raised in one method will fail callers. Otherwise, tests are mostly independent. @@ -8,7 +8,7 @@ Otherwise, tests are mostly independent. import unittest from test.support import captured_stdout from idlelib.idle_test.mock_tk import Var -from idlelib.GrepDialog import GrepDialog +from idlelib.grep import GrepDialog import re class Dummy_searchengine: @@ -72,7 +72,7 @@ class Grep_itTest(unittest.TestCase): self.assertTrue(lines[4].startswith('(Hint:')) class Default_commandTest(unittest.TestCase): - # To write this, mode OutputWindow import to top of GrepDialog + # To write this, move outwin import to top of GrepDialog # so it can be replaced by captured_stdout in class setup/teardown. pass diff --git a/Lib/idlelib/idle_test/test_help.py b/Lib/idlelib/idle_test/test_help.py new file mode 100644 index 0000000..2c68e23 --- /dev/null +++ b/Lib/idlelib/idle_test/test_help.py @@ -0,0 +1,34 @@ +'''Test idlelib.help. + +Coverage: 87% +''' +from idlelib import help +from test.support import requires +requires('gui') +from os.path import abspath, dirname, join +from tkinter import Tk +import unittest + +class HelpFrameTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + "By itself, this tests that file parsed without exception." + cls.root = root = Tk() + root.withdraw() + helpfile = join(dirname(dirname(abspath(__file__))), 'help.html') + cls.frame = help.HelpFrame(root, helpfile) + + @classmethod + def tearDownClass(cls): + del cls.frame + cls.root.update_idletasks() + cls.root.destroy() + del cls.root + + def test_line1(self): + text = self.frame.text + self.assertEqual(text.get('1.0', '1.end'), ' IDLE ') + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_help_about.py b/Lib/idlelib/idle_test/test_help_about.py index d0a0127..843efb9 100644 --- a/Lib/idlelib/idle_test/test_help_about.py +++ b/Lib/idlelib/idle_test/test_help_about.py @@ -2,10 +2,10 @@ Coverage: ''' -from idlelib import aboutDialog as help_about -from idlelib import textView as textview +from idlelib import help_about +from idlelib import textview from idlelib.idle_test.mock_idle import Func -from idlelib.idle_test.mock_tk import Mbox +from idlelib.idle_test.mock_tk import Mbox_func import unittest About = help_about.AboutDialog @@ -19,33 +19,33 @@ class Dummy_about_dialog(): 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_error = textview.showerror cls.orig_view = textview.view_text - cls.mbox = Mbox() + cls.error = Mbox_func() cls.view = Func() - textview.tkMessageBox = cls.mbox + textview.showerror = cls.error textview.view_text = cls.view cls.About = Dummy_about_dialog() @classmethod def tearDownClass(cls): - textview.tkMessageBox = cls.orig_mbox + textview.showerror = cls.orig_error 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.error.message = '' self.view.called = False - handler() - self.assertEqual(self.mbox.showerror.message, '') - self.assertEqual(self.view.called, True) + with self.subTest(handler=handler): + handler() + self.assertEqual(self.error.message, '') + self.assertEqual(self.view.called, True) if __name__ == '__main__': diff --git a/Lib/idlelib/idle_test/test_idlehistory.py b/Lib/idlelib/idle_test/test_history.py index 6e7c6c3..b278010 100644 --- a/Lib/idlelib/idle_test/test_idlehistory.py +++ b/Lib/idlelib/idle_test/test_history.py @@ -4,8 +4,8 @@ from test.support import requires import tkinter as tk from tkinter import Text as tkText from idlelib.idle_test.mock_tk import Text as mkText -from idlelib.IdleHistory import History -from idlelib.configHandler import idleConf +from idlelib.history import History +from idlelib.config import idleConf line1 = 'a = 7' line2 = 'b = a' diff --git a/Lib/idlelib/idle_test/test_hyperparser.py b/Lib/idlelib/idle_test/test_hyperparser.py index 9ce3f2c..73c8281 100644 --- a/Lib/idlelib/idle_test/test_hyperparser.py +++ b/Lib/idlelib/idle_test/test_hyperparser.py @@ -1,9 +1,9 @@ -"""Unittest for idlelib.HyperParser""" +"""Unittest for idlelib.hyperparser.py.""" import unittest from test.support import requires from tkinter import Tk, Text -from idlelib.EditorWindow import EditorWindow -from idlelib.HyperParser import HyperParser +from idlelib.editor import EditorWindow +from idlelib.hyperparser import HyperParser class DummyEditwin: def __init__(self, text): diff --git a/Lib/idlelib/idle_test/test_io.py b/Lib/idlelib/idle_test/test_iomenu.py index e0e3b98..65bf593 100644 --- a/Lib/idlelib/idle_test/test_io.py +++ b/Lib/idlelib/idle_test/test_iomenu.py @@ -1,6 +1,7 @@ import unittest import io -from idlelib.PyShell import PseudoInputFile, PseudoOutputFile + +from idlelib.run import PseudoInputFile, PseudoOutputFile class S(str): @@ -230,4 +231,4 @@ class PseudeInputFilesTest(unittest.TestCase): if __name__ == '__main__': - unittest.main() + unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_macosx.py b/Lib/idlelib/idle_test/test_macosx.py new file mode 100644 index 0000000..fae75d8 --- /dev/null +++ b/Lib/idlelib/idle_test/test_macosx.py @@ -0,0 +1,103 @@ +'''Test idlelib.macosx.py. + +Coverage: 71% on Windows. +''' +from idlelib import macosx +from test.support import requires +import sys +import tkinter as tk +import unittest +import unittest.mock as mock +from idlelib.filelist import FileList + +mactypes = {'carbon', 'cocoa', 'xquartz'} +nontypes = {'other'} +alltypes = mactypes | nontypes + + +class InitTktypeTest(unittest.TestCase): + "Test _init_tk_type." + + @classmethod + def setUpClass(cls): + requires('gui') + cls.root = tk.Tk() + cls.root.withdraw() + cls.orig_platform = macosx.platform + + @classmethod + def tearDownClass(cls): + cls.root.update_idletasks() + cls.root.destroy() + del cls.root + macosx.platform = cls.orig_platform + + def test_init_sets_tktype(self): + "Test that _init_tk_type sets _tk_type according to platform." + for platform, types in ('darwin', alltypes), ('other', nontypes): + with self.subTest(platform=platform): + macosx.platform = platform + macosx._tk_type == None + macosx._init_tk_type() + self.assertIn(macosx._tk_type, types) + + +class IsTypeTkTest(unittest.TestCase): + "Test each of the four isTypeTk predecates." + isfuncs = ((macosx.isAquaTk, ('carbon', 'cocoa')), + (macosx.isCarbonTk, ('carbon')), + (macosx.isCocoaTk, ('cocoa')), + (macosx.isXQuartz, ('xquartz')), + ) + + @mock.patch('idlelib.macosx._init_tk_type') + def test_is_calls_init(self, mockinit): + "Test that each isTypeTk calls _init_tk_type when _tk_type is None." + macosx._tk_type = None + for func, whentrue in self.isfuncs: + with self.subTest(func=func): + func() + self.assertTrue(mockinit.called) + mockinit.reset_mock() + + def test_isfuncs(self): + "Test that each isTypeTk return correct bool." + for func, whentrue in self.isfuncs: + for tktype in alltypes: + with self.subTest(func=func, whentrue=whentrue, tktype=tktype): + macosx._tk_type = tktype + (self.assertTrue if tktype in whentrue else self.assertFalse)\ + (func()) + + +class SetupTest(unittest.TestCase): + "Test setupApp." + + @classmethod + def setUpClass(cls): + requires('gui') + cls.root = tk.Tk() + cls.root.withdraw() + + @classmethod + def tearDownClass(cls): + cls.root.update_idletasks() + cls.root.destroy() + del cls.root + + @mock.patch('idlelib.macosx.overrideRootMenu') #27312 + def test_setupapp(self, overrideRootMenu): + "Call setupApp with each possible graphics type." + root = self.root + flist = FileList(root) + for tktype in alltypes: + with self.subTest(tktype=tktype): + macosx._tk_type = tktype + macosx.setupApp(root, flist) + if tktype in ('carbon', 'cocoa'): + self.assertTrue(overrideRootMenu.called) + overrideRootMenu.reset_mock() + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_formatparagraph.py b/Lib/idlelib/idle_test/test_paragraph.py index b6eb2f3..ba350c9 100644 --- a/Lib/idlelib/idle_test/test_formatparagraph.py +++ b/Lib/idlelib/idle_test/test_paragraph.py @@ -1,7 +1,7 @@ -# Test the functions and main class method of FormatParagraph.py +# Test the functions and main class method of paragraph.py import unittest -from idlelib import FormatParagraph as fp -from idlelib.EditorWindow import EditorWindow +from idlelib import paragraph as fp +from idlelib.editor import EditorWindow from tkinter import Tk, Text from test.support import requires @@ -38,7 +38,7 @@ class Is_Get_Test(unittest.TestCase): class FindTest(unittest.TestCase): - """Test the find_paragraph function in FormatParagraph. + """Test the find_paragraph function in paragraph module. Using the runcase() function, find_paragraph() is called with 'mark' set at multiple indexes before and inside the test paragraph. diff --git a/Lib/idlelib/idle_test/test_parenmatch.py b/Lib/idlelib/idle_test/test_parenmatch.py index 95cc22c..051f7ea 100644 --- a/Lib/idlelib/idle_test/test_parenmatch.py +++ b/Lib/idlelib/idle_test/test_parenmatch.py @@ -1,4 +1,4 @@ -'''Test idlelib.ParenMatch. +'''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. @@ -9,7 +9,7 @@ requires('gui') import unittest from unittest.mock import Mock from tkinter import Tk, Text -from idlelib.ParenMatch import ParenMatch +from idlelib.parenmatch import ParenMatch class DummyEditwin: def __init__(self, text): @@ -38,12 +38,17 @@ class ParenMatchTest(unittest.TestCase): def tearDown(self): self.text.delete('1.0', 'end') + def get_parenmatch(self): + pm = ParenMatch(self.editwin) + pm.bell = lambda: None + return pm + def test_paren_expression(self): """ Test ParenMatch with 'expression' style. """ text = self.text - pm = ParenMatch(self.editwin) + pm = self.get_parenmatch() pm.set_style('expression') text.insert('insert', 'def foobar(a, b') @@ -66,7 +71,7 @@ class ParenMatchTest(unittest.TestCase): Test ParenMatch with 'default' style. """ text = self.text - pm = ParenMatch(self.editwin) + pm = self.get_parenmatch() pm.set_style('default') text.insert('insert', 'def foobar(a, b') @@ -86,7 +91,7 @@ class ParenMatchTest(unittest.TestCase): These cases force conditional expression and alternate paths. """ text = self.text - pm = ParenMatch(self.editwin) + pm = self.get_parenmatch() text.insert('insert', '# this is a commen)') self.assertIsNone(pm.paren_closed_event('event')) @@ -99,7 +104,7 @@ class ParenMatchTest(unittest.TestCase): self.assertIsNone(pm.paren_closed_event('event')) def test_handle_restore_timer(self): - pm = ParenMatch(self.editwin) + pm = self.get_parenmatch() pm.restore_event = Mock() pm.handle_restore_timer(0) self.assertTrue(pm.restore_event.called) diff --git a/Lib/idlelib/idle_test/test_pathbrowser.py b/Lib/idlelib/idle_test/test_pathbrowser.py index afb886f..813cbcc 100644 --- a/Lib/idlelib/idle_test/test_pathbrowser.py +++ b/Lib/idlelib/idle_test/test_pathbrowser.py @@ -2,13 +2,13 @@ import unittest import os import sys import idlelib -from idlelib import PathBrowser +from idlelib import pathbrowser class PathBrowserTest(unittest.TestCase): def test_DirBrowserTreeItem(self): # Issue16226 - make sure that getting a sublist works - d = PathBrowser.DirBrowserTreeItem('') + d = pathbrowser.DirBrowserTreeItem('') d.GetSubList() self.assertEqual('', d.GetText()) @@ -17,11 +17,11 @@ class PathBrowserTest(unittest.TestCase): self.assertEqual(d.ispackagedir(dir + '/Icons'), False) def test_PathBrowserTreeItem(self): - p = PathBrowser.PathBrowserTreeItem() + p = pathbrowser.PathBrowserTreeItem() self.assertEqual(p.GetText(), 'sys.path') sub = p.GetSubList() self.assertEqual(len(sub), len(sys.path)) - self.assertEqual(type(sub[0]), PathBrowser.DirBrowserTreeItem) + self.assertEqual(type(sub[0]), pathbrowser.DirBrowserTreeItem) if __name__ == '__main__': unittest.main(verbosity=2, exit=False) diff --git a/Lib/idlelib/idle_test/test_percolator.py b/Lib/idlelib/idle_test/test_percolator.py index 4c0a7ad..573b9a1 100644 --- a/Lib/idlelib/idle_test/test_percolator.py +++ b/Lib/idlelib/idle_test/test_percolator.py @@ -1,10 +1,10 @@ -'''Test Percolator''' +'''Test percolator.py.''' from test.support import requires requires('gui') import unittest from tkinter import Text, Tk, END -from idlelib.Percolator import Percolator, Delegator +from idlelib.percolator import Percolator, Delegator class MyFilter(Delegator): diff --git a/Lib/idlelib/idle_test/test_query.py b/Lib/idlelib/idle_test/test_query.py new file mode 100644 index 0000000..66af8eb --- /dev/null +++ b/Lib/idlelib/idle_test/test_query.py @@ -0,0 +1,353 @@ +"""Test idlelib.query. + +Non-gui tests for Query, SectionName, ModuleName, and HelpSource use +dummy versions that extract the non-gui methods and add other needed +attributes. GUI tests create an instance of each class and simulate +entries and button clicks. Subclass tests only target the new code in +the subclass definition. + +The appearance of the widgets is checked by the Query and +HelpSource htests. These are run by running query.py. + +Coverage: 94% (100% for Query and SectionName). +6 of 8 missing are ModuleName exceptions I don't know how to trigger. +""" +from test.support import requires +import sys +from tkinter import Tk +import unittest +from unittest import mock +from idlelib.idle_test.mock_tk import Var +from idlelib import query + + +# NON-GUI TESTS + +class QueryTest(unittest.TestCase): + "Test Query base class." + + class Dummy_Query: + # Test the following Query methods. + entry_ok = query.Query.entry_ok + ok = query.Query.ok + cancel = query.Query.cancel + # Add attributes and initialization needed for tests. + entry = Var() + entry_error = {} + def __init__(self, dummy_entry): + self.entry.set(dummy_entry) + self.entry_error['text'] = '' + self.result = None + self.destroyed = False + def showerror(self, message): + self.entry_error['text'] = message + def destroy(self): + self.destroyed = True + + def test_entry_ok_blank(self): + dialog = self.Dummy_Query(' ') + self.assertEqual(dialog.entry_ok(), None) + self.assertEqual((dialog.result, dialog.destroyed), (None, False)) + self.assertIn('blank line', dialog.entry_error['text']) + + def test_entry_ok_good(self): + dialog = self.Dummy_Query(' good ') + Equal = self.assertEqual + Equal(dialog.entry_ok(), 'good') + Equal((dialog.result, dialog.destroyed), (None, False)) + Equal(dialog.entry_error['text'], '') + + def test_ok_blank(self): + dialog = self.Dummy_Query('') + dialog.entry.focus_set = mock.Mock() + self.assertEqual(dialog.ok(), None) + self.assertTrue(dialog.entry.focus_set.called) + del dialog.entry.focus_set + self.assertEqual((dialog.result, dialog.destroyed), (None, False)) + + def test_ok_good(self): + dialog = self.Dummy_Query('good') + self.assertEqual(dialog.ok(), None) + self.assertEqual((dialog.result, dialog.destroyed), ('good', True)) + + def test_cancel(self): + dialog = self.Dummy_Query('does not matter') + self.assertEqual(dialog.cancel(), None) + self.assertEqual((dialog.result, dialog.destroyed), (None, True)) + + +class SectionNameTest(unittest.TestCase): + "Test SectionName subclass of Query." + + class Dummy_SectionName: + entry_ok = query.SectionName.entry_ok # Function being tested. + used_names = ['used'] + entry = Var() + entry_error = {} + def __init__(self, dummy_entry): + self.entry.set(dummy_entry) + self.entry_error['text'] = '' + def showerror(self, message): + self.entry_error['text'] = message + + def test_blank_section_name(self): + dialog = self.Dummy_SectionName(' ') + self.assertEqual(dialog.entry_ok(), None) + self.assertIn('no name', dialog.entry_error['text']) + + def test_used_section_name(self): + dialog = self.Dummy_SectionName('used') + self.assertEqual(dialog.entry_ok(), None) + self.assertIn('use', dialog.entry_error['text']) + + def test_long_section_name(self): + dialog = self.Dummy_SectionName('good'*8) + self.assertEqual(dialog.entry_ok(), None) + self.assertIn('longer than 30', dialog.entry_error['text']) + + def test_good_section_name(self): + dialog = self.Dummy_SectionName(' good ') + self.assertEqual(dialog.entry_ok(), 'good') + self.assertEqual(dialog.entry_error['text'], '') + + +class ModuleNameTest(unittest.TestCase): + "Test ModuleName subclass of Query." + + class Dummy_ModuleName: + entry_ok = query.ModuleName.entry_ok # Function being tested. + text0 = '' + entry = Var() + entry_error = {} + def __init__(self, dummy_entry): + self.entry.set(dummy_entry) + self.entry_error['text'] = '' + def showerror(self, message): + self.entry_error['text'] = message + + def test_blank_module_name(self): + dialog = self.Dummy_ModuleName(' ') + self.assertEqual(dialog.entry_ok(), None) + self.assertIn('no name', dialog.entry_error['text']) + + def test_bogus_module_name(self): + dialog = self.Dummy_ModuleName('__name_xyz123_should_not_exist__') + self.assertEqual(dialog.entry_ok(), None) + self.assertIn('not found', dialog.entry_error['text']) + + def test_c_source_name(self): + dialog = self.Dummy_ModuleName('itertools') + self.assertEqual(dialog.entry_ok(), None) + self.assertIn('source-based', dialog.entry_error['text']) + + def test_good_module_name(self): + dialog = self.Dummy_ModuleName('idlelib') + self.assertTrue(dialog.entry_ok().endswith('__init__.py')) + self.assertEqual(dialog.entry_error['text'], '') + + +# 3 HelpSource test classes each test one function. + +orig_platform = query.platform + +class HelpsourceBrowsefileTest(unittest.TestCase): + "Test browse_file method of ModuleName subclass of Query." + + class Dummy_HelpSource: + browse_file = query.HelpSource.browse_file + pathvar = Var() + + def test_file_replaces_path(self): + dialog = self.Dummy_HelpSource() + # Path is widget entry, either '' or something. + # Func return is file dialog return, either '' or something. + # Func return should override widget entry. + # We need all 4 combination to test all (most) code paths. + for path, func, result in ( + ('', lambda a,b,c:'', ''), + ('', lambda a,b,c: __file__, __file__), + ('htest', lambda a,b,c:'', 'htest'), + ('htest', lambda a,b,c: __file__, __file__)): + with self.subTest(): + dialog.pathvar.set(path) + dialog.askfilename = func + dialog.browse_file() + self.assertEqual(dialog.pathvar.get(), result) + + +class HelpsourcePathokTest(unittest.TestCase): + "Test path_ok method of HelpSource subclass of Query." + + class Dummy_HelpSource: + path_ok = query.HelpSource.path_ok + path = Var() + path_error = {} + def __init__(self, dummy_path): + self.path.set(dummy_path) + self.path_error['text'] = '' + def showerror(self, message, widget=None): + self.path_error['text'] = message + + @classmethod + def tearDownClass(cls): + query.platform = orig_platform + + def test_path_ok_blank(self): + dialog = self.Dummy_HelpSource(' ') + self.assertEqual(dialog.path_ok(), None) + self.assertIn('no help file', dialog.path_error['text']) + + def test_path_ok_bad(self): + dialog = self.Dummy_HelpSource(__file__ + 'bad-bad-bad') + self.assertEqual(dialog.path_ok(), None) + self.assertIn('not exist', dialog.path_error['text']) + + def test_path_ok_web(self): + dialog = self.Dummy_HelpSource('') + Equal = self.assertEqual + for url in 'www.py.org', 'http://py.org': + with self.subTest(): + dialog.path.set(url) + self.assertEqual(dialog.path_ok(), url) + self.assertEqual(dialog.path_error['text'], '') + + def test_path_ok_file(self): + dialog = self.Dummy_HelpSource('') + for platform, prefix in ('darwin', 'file://'), ('other', ''): + with self.subTest(): + query.platform = platform + dialog.path.set(__file__) + self.assertEqual(dialog.path_ok(), prefix + __file__) + self.assertEqual(dialog.path_error['text'], '') + + +class HelpsourceEntryokTest(unittest.TestCase): + "Test entry_ok method of HelpSource subclass of Query." + + class Dummy_HelpSource: + entry_ok = query.HelpSource.entry_ok + entry_error = {} + path_error = {} + def item_ok(self): + return self.name + def path_ok(self): + return self.path + + def test_entry_ok_helpsource(self): + dialog = self.Dummy_HelpSource() + for name, path, result in ((None, None, None), + (None, 'doc.txt', None), + ('doc', None, None), + ('doc', 'doc.txt', ('doc', 'doc.txt'))): + with self.subTest(): + dialog.name, dialog.path = name, path + self.assertEqual(dialog.entry_ok(), result) + + +# GUI TESTS + +class QueryGuiTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + requires('gui') + cls.root = root = Tk() + cls.root.withdraw() + cls.dialog = query.Query(root, 'TEST', 'test', _utest=True) + cls.dialog.destroy = mock.Mock() + + @classmethod + def tearDownClass(cls): + del cls.dialog + cls.root.destroy() + del cls.root + + def setUp(self): + self.dialog.entry.delete(0, 'end') + self.dialog.result = None + self.dialog.destroy.reset_mock() + + def test_click_ok(self): + dialog = self.dialog + dialog.entry.insert(0, 'abc') + dialog.button_ok.invoke() + self.assertEqual(dialog.result, 'abc') + self.assertTrue(dialog.destroy.called) + + def test_click_blank(self): + dialog = self.dialog + dialog.button_ok.invoke() + self.assertEqual(dialog.result, None) + self.assertFalse(dialog.destroy.called) + + def test_click_cancel(self): + dialog = self.dialog + dialog.entry.insert(0, 'abc') + dialog.button_cancel.invoke() + self.assertEqual(dialog.result, None) + self.assertTrue(dialog.destroy.called) + + +class SectionnameGuiTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + requires('gui') + + def test_click_section_name(self): + root = Tk() + root.withdraw() + dialog = query.SectionName(root, 'T', 't', {'abc'}, _utest=True) + Equal = self.assertEqual + self.assertEqual(dialog.used_names, {'abc'}) + dialog.entry.insert(0, 'okay') + dialog.button_ok.invoke() + self.assertEqual(dialog.result, 'okay') + del dialog + root.destroy() + del root + + +class ModulenameGuiTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + requires('gui') + + def test_click_module_name(self): + root = Tk() + root.withdraw() + dialog = query.ModuleName(root, 'T', 't', 'idlelib', _utest=True) + self.assertEqual(dialog.text0, 'idlelib') + self.assertEqual(dialog.entry.get(), 'idlelib') + dialog.button_ok.invoke() + self.assertTrue(dialog.result.endswith('__init__.py')) + del dialog + root.destroy() + del root + + +class HelpsourceGuiTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + requires('gui') + + def test_click_help_source(self): + root = Tk() + root.withdraw() + dialog = query.HelpSource(root, 'T', menuitem='__test__', + filepath=__file__, _utest=True) + Equal = self.assertEqual + Equal(dialog.entry.get(), '__test__') + Equal(dialog.path.get(), __file__) + dialog.button_ok.invoke() + prefix = "file://" if sys.platform == 'darwin' else '' + Equal(dialog.result, ('__test__', prefix + __file__)) + del dialog + root.destroy() + del root + + +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_redirector.py index baa975d..b0385fa 100644 --- a/Lib/idlelib/idle_test/test_widgetredir.py +++ b/Lib/idlelib/idle_test/test_redirector.py @@ -1,4 +1,4 @@ -'''Test idlelib.WidgetRedirector. +'''Test idlelib.redirector. 100% coverage ''' @@ -6,7 +6,7 @@ from test.support import requires import unittest from idlelib.idle_test.mock_idle import Func from tkinter import Tk, Text, TclError -from idlelib.WidgetRedirector import WidgetRedirector +from idlelib.redirector import WidgetRedirector class InitCloseTest(unittest.TestCase): @@ -120,6 +120,5 @@ class WidgetRedirectorTest(unittest.TestCase): self.assertEqual(self.root.call(self.text._w, 'insert', 'boo'), '') - if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_replacedialog.py b/Lib/idlelib/idle_test/test_replace.py index ff44820..9913ed2 100644 --- a/Lib/idlelib/idle_test/test_replacedialog.py +++ b/Lib/idlelib/idle_test/test_replace.py @@ -1,4 +1,4 @@ -"""Unittest for idlelib.ReplaceDialog""" +"""Unittest for idlelib.replace.py""" from test.support import requires requires('gui') @@ -6,8 +6,8 @@ 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 +import idlelib.searchengine as se +from idlelib.replace import ReplaceDialog orig_mbox = se.tkMessageBox showerror = Mbox.showerror @@ -21,7 +21,8 @@ class ReplaceDialogTest(unittest.TestCase): cls.root.withdraw() se.tkMessageBox = Mbox cls.engine = se.SearchEngine(cls.root) - cls.dialog = rd.ReplaceDialog(cls.root, cls.engine) + cls.dialog = ReplaceDialog(cls.root, cls.engine) + cls.dialog.bell = lambda: None cls.dialog.ok = Mock() cls.text = Text(cls.root) cls.text.undo_block_start = Mock() @@ -70,7 +71,6 @@ class ReplaceDialogTest(unittest.TestCase): # text found and replaced pv.set('a') rv.set('asdf') - self.dialog.open(self.text) replace() equal(text.get('1.8', '1.12'), 'asdf') @@ -91,7 +91,7 @@ class ReplaceDialogTest(unittest.TestCase): text.mark_set('insert', 'end') text.insert('insert', '\nline42:') before_text = text.get('1.0', 'end') - pv.set('[a-z][\d]+') + pv.set(r'[a-z][\d]+') replace() after_text = text.get('1.0', 'end') equal(before_text, after_text) @@ -192,7 +192,7 @@ class ReplaceDialogTest(unittest.TestCase): self.engine.revar.set(True) before_text = text.get('1.0', 'end') - pv.set('[a-z][\d]+') + pv.set(r'[a-z][\d]+') rv.set('hello') replace() after_text = text.get('1.0', 'end') @@ -207,7 +207,7 @@ class ReplaceDialogTest(unittest.TestCase): self.assertIn('error', showerror.title) self.assertIn('Empty', showerror.message) - pv.set('[\d') + pv.set(r'[\d') replace() self.assertIn('error', showerror.title) self.assertIn('Pattern', showerror.message) diff --git a/Lib/idlelib/idle_test/test_rstrip.py b/Lib/idlelib/idle_test/test_rstrip.py index 1c90b93..130e6be 100644 --- a/Lib/idlelib/idle_test/test_rstrip.py +++ b/Lib/idlelib/idle_test/test_rstrip.py @@ -1,5 +1,5 @@ import unittest -import idlelib.RstripExtension as rs +import idlelib.rstrip as rs from idlelib.idle_test.mock_idle import Editor class rstripTest(unittest.TestCase): @@ -21,7 +21,7 @@ class rstripTest(unittest.TestCase): def test_rstrip_multiple(self): editor = Editor() # Uncomment following to verify that test passes with real widgets. -## from idlelib.EditorWindow import EditorWindow as Editor +## from idlelib.editor import EditorWindow as Editor ## from tkinter import Tk ## editor = Editor(root=Tk()) text = editor.text diff --git a/Lib/idlelib/idle_test/test_scrolledlist.py b/Lib/idlelib/idle_test/test_scrolledlist.py new file mode 100644 index 0000000..56aabfe --- /dev/null +++ b/Lib/idlelib/idle_test/test_scrolledlist.py @@ -0,0 +1,29 @@ +''' Test idlelib.scrolledlist. + +Coverage: 39% +''' +from idlelib import scrolledlist +from test.support import requires +requires('gui') +import unittest +from tkinter import Tk + + +class ScrolledListTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.root = Tk() + + @classmethod + def tearDownClass(cls): + cls.root.destroy() + del cls.root + + + def test_init(self): + scrolledlist.ScrolledList(self.root) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_searchdialog.py b/Lib/idlelib/idle_test/test_search.py index 190c866..80fa93a 100644 --- a/Lib/idlelib/idle_test/test_searchdialog.py +++ b/Lib/idlelib/idle_test/test_search.py @@ -1,4 +1,4 @@ -"""Test SearchDialog class in SearchDialogue.py""" +"""Test SearchDialog class in idlelib.search.py""" # Does not currently test the event handler wrappers. # A usage test should simulate clicks and check hilighting. @@ -11,8 +11,8 @@ requires('gui') import unittest import tkinter as tk from tkinter import BooleanVar -import idlelib.SearchEngine as se -import idlelib.SearchDialog as sd +import idlelib.searchengine as se +import idlelib.search as sd class SearchDialogTest(unittest.TestCase): @@ -29,6 +29,7 @@ class SearchDialogTest(unittest.TestCase): def setUp(self): self.engine = se.SearchEngine(self.root) self.dialog = sd.SearchDialog(self.root, self.engine) + self.dialog.bell = lambda: None self.text = tk.Text(self.root) self.text.insert('1.0', 'Hello World!') @@ -38,6 +39,7 @@ class SearchDialogTest(unittest.TestCase): self.engine.setpat('') self.assertFalse(self.dialog.find_again(text)) + self.dialog.bell = lambda: None self.engine.setpat('Hello') self.assertTrue(self.dialog.find_again(text)) diff --git a/Lib/idlelib/idle_test/test_searchdialogbase.py b/Lib/idlelib/idle_test/test_searchbase.py index 8036b91..d769fa2 100644 --- a/Lib/idlelib/idle_test/test_searchdialogbase.py +++ b/Lib/idlelib/idle_test/test_searchbase.py @@ -1,14 +1,13 @@ -'''Unittests for idlelib/SearchDialogBase.py +'''tests idlelib.searchbase. Coverage: 99%. The only thing not covered is inconsequential -- testing skipping of suite when self.needwrapbutton is false. - ''' import unittest from test.support import requires from tkinter import Tk, Toplevel, Frame ##, BooleanVar, StringVar -from idlelib import SearchEngine as se -from idlelib import SearchDialogBase as sdb +from idlelib import searchengine as se +from idlelib import searchbase as sdb from idlelib.idle_test.mock_idle import Func ## from idlelib.idle_test.mock_tk import Var @@ -74,7 +73,7 @@ class SearchDialogBaseTest(unittest.TestCase): def test_make_entry(self): equal = self.assertEqual self.dialog.row = 0 - self.dialog.top = Toplevel(self.root) + self.dialog.top = self.root entry, label = self.dialog.make_entry("Test:", 'hello') equal(label['text'], 'Test:') @@ -87,6 +86,7 @@ class SearchDialogBaseTest(unittest.TestCase): equal(self.dialog.row, 1) def test_create_entries(self): + self.dialog.top = self.root self.dialog.row = 0 self.engine.setpat('hello') self.dialog.create_entries() @@ -94,7 +94,7 @@ class SearchDialogBaseTest(unittest.TestCase): def test_make_frame(self): self.dialog.row = 0 - self.dialog.top = Toplevel(self.root) + self.dialog.top = self.root frame, label = self.dialog.make_frame() self.assertEqual(label, '') self.assertIsInstance(frame, Frame) @@ -104,7 +104,7 @@ class SearchDialogBaseTest(unittest.TestCase): self.assertIsInstance(frame, Frame) def btn_test_setup(self, meth): - self.dialog.top = Toplevel(self.root) + self.dialog.top = self.root self.dialog.row = 0 return meth() @@ -119,11 +119,6 @@ class SearchDialogBaseTest(unittest.TestCase): var, label = spec self.assertEqual(button['text'], label) self.assertEqual(var.get(), state) - if state == 1: - button.deselect() - else: - button.select() - self.assertEqual(var.get(), 1 - state) def test_create_other_buttons(self): for state in (False, True): @@ -139,18 +134,15 @@ class SearchDialogBaseTest(unittest.TestCase): # hit other button, then this one # indexes depend on button order self.assertEqual(var.get(), state) - buttons[val].select() - self.assertEqual(var.get(), 1 - state) - buttons[1-val].select() - self.assertEqual(var.get(), state) def test_make_button(self): - self.dialog.top = Toplevel(self.root) + self.dialog.top = self.root self.dialog.buttonframe = Frame(self.dialog.top) btn = self.dialog.make_button('Test', self.dialog.close) self.assertEqual(btn['text'], 'Test') def test_create_command_buttons(self): + self.dialog.top = self.root self.dialog.create_command_buttons() # Look for close button command in buttonframe closebuttoncommand = '' @@ -160,6 +152,5 @@ class SearchDialogBaseTest(unittest.TestCase): self.assertIn('close', closebuttoncommand) - 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 edbd558..b3aa8eb 100644 --- a/Lib/idlelib/idle_test/test_searchengine.py +++ b/Lib/idlelib/idle_test/test_searchengine.py @@ -1,4 +1,4 @@ -'''Test functions and SearchEngine class in SearchEngine.py.''' +'''Test functions and SearchEngine class in idlelib.searchengine.py.''' # With mock replacements, the module does not use any gui widgets. # The use of tk.Text is avoided (for now, until mock Text is improved) @@ -10,7 +10,7 @@ import unittest # from test.support import requires from tkinter import BooleanVar, StringVar, TclError # ,Tk, Text import tkinter.messagebox as tkMessageBox -from idlelib import SearchEngine as se +from idlelib import searchengine as se from idlelib.idle_test.mock_tk import Var, Mbox from idlelib.idle_test.mock_tk import Text as mockText @@ -139,10 +139,10 @@ class SearchEngineTest(unittest.TestCase): def test_setcookedpat(self): engine = self.engine - engine.setcookedpat('\s') - self.assertEqual(engine.getpat(), '\s') + engine.setcookedpat(r'\s') + self.assertEqual(engine.getpat(), r'\s') engine.revar.set(1) - engine.setcookedpat('\s') + engine.setcookedpat(r'\s') self.assertEqual(engine.getpat(), r'\\s') def test_getcookedpat(self): @@ -156,10 +156,10 @@ class SearchEngineTest(unittest.TestCase): Equal(engine.getcookedpat(), r'\bhello\b') engine.wordvar.set(False) - engine.setpat('\s') + engine.setpat(r'\s') Equal(engine.getcookedpat(), r'\\s') engine.revar.set(True) - Equal(engine.getcookedpat(), '\s') + Equal(engine.getcookedpat(), r'\s') def test_getprog(self): engine = self.engine @@ -282,7 +282,7 @@ class ForwardBackwardTest(unittest.TestCase): cls.pat = re.compile('target') cls.res = (2, (10, 16)) # line, slice indexes of 'target' cls.failpat = re.compile('xyz') # not in text - cls.emptypat = re.compile('\w*') # empty match possible + cls.emptypat = re.compile(r'\w*') # empty match possible def make_search(self, func): def search(pat, line, col, wrap, ok=0): diff --git a/Lib/idlelib/idle_test/test_text.py b/Lib/idlelib/idle_test/test_text.py index 7e823df..a5ba7bb 100644 --- a/Lib/idlelib/idle_test/test_text.py +++ b/Lib/idlelib/idle_test/test_text.py @@ -1,17 +1,19 @@ -# Test mock_tk.Text class against tkinter.Text class by running same tests with both. +''' Test mock_tk.Text class against tkinter.Text class + +Run same tests with both by creating a mixin class. +''' import unittest from test.support import requires - from _tkinter import TclError class TextTest(object): + "Define items common to both sets of tests." - hw = 'hello\nworld' # usual initial insert after initialization + hw = 'hello\nworld' # Several tests insert this after after initialization. hwn = hw+'\n' # \n present at initialization, before insert - Text = None - def setUp(self): - self.text = self.Text() + # setUpClass defines cls.Text and maybe cls.root. + # setUp defines self.text from Text and maybe root. def test_init(self): self.assertEqual(self.text.get('1.0'), '\n') @@ -196,6 +198,10 @@ class MockTextTest(TextTest, unittest.TestCase): from idlelib.idle_test.mock_tk import Text cls.Text = Text + def setUp(self): + self.text = self.Text() + + def test_decode(self): # test endflags (-1, 0) not tested by test_index (which uses +1) decode = self.text._decode @@ -222,6 +228,9 @@ class TkTextTest(TextTest, unittest.TestCase): cls.root.destroy() del cls.root + def setUp(self): + self.text = self.Text(self.root) + if __name__ == '__main__': unittest.main(verbosity=2, exit=False) diff --git a/Lib/idlelib/idle_test/test_textview.py b/Lib/idlelib/idle_test/test_textview.py index 5d2e600..f018f5e 100644 --- a/Lib/idlelib/idle_test/test_textview.py +++ b/Lib/idlelib/idle_test/test_textview.py @@ -1,21 +1,21 @@ -'''Test idlelib.textView. +'''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. Using mock Text would not change this. Other mocks are used to retrieve information about calls. -The coverage is essentially 100%. +Coverage: 94%. ''' +from idlelib import textview as tv from test.support import requires requires('gui') import unittest import os from tkinter import Tk -from idlelib import textView as tv from idlelib.idle_test.mock_idle import Func -from idlelib.idle_test.mock_tk import Mbox +from idlelib.idle_test.mock_tk import Mbox_func def setUpModule(): global root @@ -64,17 +64,17 @@ class TextViewTest(unittest.TestCase): view.destroy() -class textviewTest(unittest.TestCase): +class ViewFunctionTest(unittest.TestCase): @classmethod def setUpClass(cls): - cls.orig_mbox = tv.tkMessageBox - tv.tkMessageBox = Mbox + cls.orig_error = tv.showerror + tv.showerror = Mbox_func() @classmethod def tearDownClass(cls): - tv.tkMessageBox = cls.orig_mbox - del cls.orig_mbox + tv.showerror = cls.orig_error + del cls.orig_error def test_view_text(self): # If modal True, get tk error 'can't invoke "event" command'. @@ -90,7 +90,7 @@ class textviewTest(unittest.TestCase): self.assertIn('Test', view.textView.get('1.0', '1.end')) view.Ok() - # Mock messagebox will be used and view_file will not return anything + # Mock showerror will be used; view_file will return None. testfile = os.path.join(test_dir, '../notthere.py') view = tv.view_file(root, 'Title', testfile, modal=False) self.assertIsNone(view) diff --git a/Lib/idlelib/idle_test/test_tree.py b/Lib/idlelib/idle_test/test_tree.py new file mode 100644 index 0000000..09ba964 --- /dev/null +++ b/Lib/idlelib/idle_test/test_tree.py @@ -0,0 +1,36 @@ +''' Test idlelib.tree. + +Coverage: 56% +''' +from idlelib import tree +from test.support import requires +requires('gui') +import os +import unittest +from tkinter import Tk + + +class TreeTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.root = Tk() + cls.root.withdraw() + + @classmethod + def tearDownClass(cls): + cls.root.destroy() + del cls.root + + def test_init(self): + # Start with code slightly adapted from htest. + sc = tree.ScrolledCanvas( + self.root, bg="white", highlightthickness=0, takefocus=1) + sc.frame.pack(expand=1, fill="both", side='left') + item = tree.FileTreeItem(tree.ICONDIR) + node = tree.TreeNode(sc.canvas, None, item) + node.expand() + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_undodelegator.py b/Lib/idlelib/idle_test/test_undo.py index 2b83c99..e872927 100644 --- a/Lib/idlelib/idle_test/test_undodelegator.py +++ b/Lib/idlelib/idle_test/test_undo.py @@ -1,4 +1,4 @@ -"""Unittest for UndoDelegator in idlelib.UndoDelegator. +"""Unittest for UndoDelegator in idlelib.undo.py. Coverage about 80% (retest). """ @@ -8,8 +8,8 @@ requires('gui') import unittest from unittest.mock import Mock from tkinter import Text, Tk -from idlelib.UndoDelegator import UndoDelegator -from idlelib.Percolator import Percolator +from idlelib.undo import UndoDelegator +from idlelib.percolator import Percolator class UndoDelegatorTest(unittest.TestCase): @@ -29,8 +29,8 @@ class UndoDelegatorTest(unittest.TestCase): def setUp(self): self.delegator = UndoDelegator() + self.delegator.bell = Mock() self.percolator.insertfilter(self.delegator) - self.delegator.bell = Mock(wraps=self.delegator.bell) def tearDown(self): self.percolator.removefilter(self.delegator) diff --git a/Lib/idlelib/idle_test/test_warning.py b/Lib/idlelib/idle_test/test_warning.py index 18627dd..f3269f1 100644 --- a/Lib/idlelib/idle_test/test_warning.py +++ b/Lib/idlelib/idle_test/test_warning.py @@ -1,4 +1,4 @@ -'''Test warnings replacement in PyShell.py and run.py. +'''Test warnings replacement in pyshell.py and run.py. This file could be expanded to include traceback overrides (in same two modules). If so, change name. @@ -17,9 +17,9 @@ showwarning = warnings.showwarning running_in_idle = 'idle' in showwarning.__name__ from idlelib import run -from idlelib import PyShell as shell +from idlelib import pyshell as shell -# The following was generated from PyShell.idle_formatwarning +# The following was generated from pyshell.idle_formatwarning # and checked as matching expectation. idlemsg = ''' Warning (from warnings module): diff --git a/Lib/idlelib/idlever.py b/Lib/idlelib/idlever.py deleted file mode 100644 index 3e9f69a..0000000 --- a/Lib/idlelib/idlever.py +++ /dev/null @@ -1,12 +0,0 @@ -""" -The separate Idle version was eliminated years ago; -idlelib.idlever is no longer used by Idle -and will be removed in 3.6 or later. Use - from sys import version - IDLE_VERSION = version[:version.index(' ')] -""" -# Kept for now only for possible existing extension use -import warnings as w -w.warn(__doc__, DeprecationWarning, stacklevel=2) -from sys import version -IDLE_VERSION = version[:version.index(' ')] diff --git a/Lib/idlelib/IOBinding.py b/Lib/idlelib/iomenu.py index 84f39a2..3414c7b 100644 --- a/Lib/idlelib/IOBinding.py +++ b/Lib/idlelib/iomenu.py @@ -10,57 +10,60 @@ import tkinter.filedialog as tkFileDialog import tkinter.messagebox as tkMessageBox from tkinter.simpledialog import askstring -from idlelib.configHandler import idleConf +import idlelib +from idlelib.config import idleConf - -# Try setting the locale, so that we can find out -# what encoding to use -try: - import locale - locale.setlocale(locale.LC_CTYPE, "") -except (ImportError, locale.Error): - pass - -# Encoding for file names -filesystemencoding = sys.getfilesystemencoding() ### currently unused - -locale_encoding = 'ascii' -if sys.platform == 'win32': - # On Windows, we could use "mbcs". However, to give the user - # a portable encoding name, we need to find the code page - try: - locale_encoding = locale.getdefaultlocale()[1] - codecs.lookup(locale_encoding) - except LookupError: - pass +if idlelib.testing: # Set True by test.test_idle to avoid setlocale. + encoding = 'utf-8' else: + # Try setting the locale, so that we can find out + # what encoding to use try: - # Different things can fail here: the locale module may not be - # loaded, it may not offer nl_langinfo, or CODESET, or the - # resulting codeset may be unknown to Python. We ignore all - # these problems, falling back to ASCII - locale_encoding = locale.nl_langinfo(locale.CODESET) - if locale_encoding is None or locale_encoding is '': - # situation occurs on Mac OS X - locale_encoding = 'ascii' - codecs.lookup(locale_encoding) - except (NameError, AttributeError, LookupError): - # Try getdefaultlocale: it parses environment variables, - # which may give a clue. Unfortunately, getdefaultlocale has - # bugs that can cause ValueError. + import locale + locale.setlocale(locale.LC_CTYPE, "") + except (ImportError, locale.Error): + pass + + locale_decode = 'ascii' + if sys.platform == 'win32': + # On Windows, we could use "mbcs". However, to give the user + # a portable encoding name, we need to find the code page try: locale_encoding = locale.getdefaultlocale()[1] + codecs.lookup(locale_encoding) + except LookupError: + pass + else: + try: + # Different things can fail here: the locale module may not be + # loaded, it may not offer nl_langinfo, or CODESET, or the + # resulting codeset may be unknown to Python. We ignore all + # these problems, falling back to ASCII + locale_encoding = locale.nl_langinfo(locale.CODESET) if locale_encoding is None or locale_encoding is '': # situation occurs on Mac OS X locale_encoding = 'ascii' codecs.lookup(locale_encoding) - except (ValueError, LookupError): - pass + except (NameError, AttributeError, LookupError): + # Try getdefaultlocale: it parses environment variables, + # which may give a clue. Unfortunately, getdefaultlocale has + # bugs that can cause ValueError. + try: + locale_encoding = locale.getdefaultlocale()[1] + if locale_encoding is None or locale_encoding is '': + # situation occurs on Mac OS X + locale_encoding = 'ascii' + codecs.lookup(locale_encoding) + except (ValueError, LookupError): + pass -locale_encoding = locale_encoding.lower() + locale_encoding = locale_encoding.lower() -encoding = locale_encoding ### KBK 07Sep07 This is used all over IDLE, check! - ### 'encoding' is used below in encode(), check! + encoding = locale_encoding + # Encoding is used in multiple files; locale_encoding nowhere. + # The only use of 'encoding' below is in _decode as initial value + # of deprecated block asking user for encoding. + # Perhaps use elsewhere should be reviewed. coding_re = re.compile(r'^[ \t\f]*#.*?coding[:=][ \t]*([-\w.]+)', re.ASCII) blank_re = re.compile(r'^[ \t\f]*(?:[#\r\n]|$)', re.ASCII) @@ -107,6 +110,9 @@ def coding_spec(data): class IOBinding: +# One instance per editor Window so methods know which to save, close. +# Open returns focus to self.editwin if aborted. +# EditorWindow.open_module, others, belong here. def __init__(self, editwin): self.editwin = editwin @@ -301,7 +307,7 @@ class IOBinding: "The file's encoding is invalid for Python 3.x.\n" "IDLE will convert it to UTF-8.\n" "What is the current encoding of the file?", - initialvalue = locale_encoding, + initialvalue = encoding, parent = self.editwin.text) if enc: @@ -529,8 +535,8 @@ def _io_binding(parent): # htest # root = Toplevel(parent) root.title("Test IOBinding") - width, height, x, y = list(map(int, re.split('[x+]', parent.geometry()))) - root.geometry("+%d+%d"%(x, y + 150)) + x, y = map(int, parent.geometry().split('+')[1:]) + root.geometry("+%d+%d" % (x, y + 175)) class MyEditWin: def __init__(self, text): self.text = text @@ -561,5 +567,8 @@ def _io_binding(parent): # htest # IOBinding(editwin) if __name__ == "__main__": + import unittest + unittest.main('idlelib.idle_test.test_iomenu', verbosity=2, exit=False) + from idlelib.idle_test.htest import run run(_io_binding) diff --git a/Lib/idlelib/macosxSupport.py b/Lib/idlelib/macosx.py index b96bae1..c225dd9 100644 --- a/Lib/idlelib/macosxSupport.py +++ b/Lib/idlelib/macosx.py @@ -1,30 +1,25 @@ """ A number of functions that enhance IDLE on Mac OSX. """ -import sys -import tkinter -from os import path +from sys import platform # Used in _init_tk_type, changed by test. import warnings -def runningAsOSXApp(): - warnings.warn("runningAsOSXApp() is deprecated, use isAquaTk()", - DeprecationWarning, stacklevel=2) - return isAquaTk() +import tkinter + -def isCarbonAquaTk(root): - warnings.warn("isCarbonAquaTk(root) is deprecated, use isCarbonTk()", - DeprecationWarning, stacklevel=2) - return isCarbonTk() +## Define functions that query the Mac graphics type. +## _tk_type and its initializer are private to this section. _tk_type = None -def _initializeTkVariantTests(root): +def _init_tk_type(): """ Initializes OS X Tk variant values for isAquaTk(), isCarbonTk(), isCocoaTk(), and isXQuartz(). """ global _tk_type - if sys.platform == 'darwin': + if platform == 'darwin': + root = tkinter.Tk() ws = root.tk.call('tk', 'windowingsystem') if 'x11' in ws: _tk_type = "xquartz" @@ -34,6 +29,7 @@ def _initializeTkVariantTests(root): _tk_type = "cocoa" else: _tk_type = "carbon" + root.destroy() else: _tk_type = "other" @@ -41,7 +37,8 @@ def isAquaTk(): """ Returns True if IDLE is using a native OS X Tk (Cocoa or Carbon). """ - assert _tk_type is not None + if not _tk_type: + _init_tk_type() return _tk_type == "cocoa" or _tk_type == "carbon" def isCarbonTk(): @@ -49,23 +46,27 @@ def isCarbonTk(): Returns True if IDLE is using a Carbon Aqua Tk (instead of the newer Cocoa Aqua Tk). """ - assert _tk_type is not None + if not _tk_type: + _init_tk_type() return _tk_type == "carbon" def isCocoaTk(): """ Returns True if IDLE is using a Cocoa Aqua Tk. """ - assert _tk_type is not None + if not _tk_type: + _init_tk_type() return _tk_type == "cocoa" def isXQuartz(): """ Returns True if IDLE is using an OS X X11 Tk. """ - assert _tk_type is not None + if not _tk_type: + _init_tk_type() return _tk_type == "xquartz" + def tkVersionWarning(root): """ Returns a string warning message if the Tk version in use appears to @@ -86,6 +87,9 @@ def tkVersionWarning(root): else: return False + +## Fix the menu and related functions. + def addOpenEventSupport(root, flist): """ This ensures that the application will respond to open AppleEvents, which @@ -124,23 +128,23 @@ def overrideRootMenu(root, flist): # Due to a (mis-)feature of TkAqua the user will also see an empty Help # menu. from tkinter import Menu - from idlelib import Bindings - from idlelib import WindowList + from idlelib import mainmenu + from idlelib import windows - closeItem = Bindings.menudefs[0][1][-2] + closeItem = mainmenu.menudefs[0][1][-2] # Remove the last 3 items of the file menu: a separator, close window and # quit. Close window will be reinserted just above the save item, where # it should be according to the HIG. Quit is in the application menu. - del Bindings.menudefs[0][1][-3:] - Bindings.menudefs[0][1].insert(6, closeItem) + del mainmenu.menudefs[0][1][-3:] + mainmenu.menudefs[0][1].insert(6, closeItem) # Remove the 'About' entry from the help menu, it is in the application # menu - del Bindings.menudefs[-1][1][0:2] + del mainmenu.menudefs[-1][1][0:2] # Remove the 'Configure Idle' entry from the options menu, it is in the # application menu as 'Preferences' - del Bindings.menudefs[-2][1][0] + del mainmenu.menudefs[-2][1][0] menubar = Menu(root) root.configure(menu=menubar) menudict = {} @@ -155,30 +159,30 @@ def overrideRootMenu(root, flist): if end > 0: menu.delete(0, end) - WindowList.add_windows_to_menu(menu) - WindowList.register_callback(postwindowsmenu) + windows.add_windows_to_menu(menu) + windows.register_callback(postwindowsmenu) def about_dialog(event=None): "Handle Help 'About IDLE' event." - # Synchronize with EditorWindow.EditorWindow.about_dialog. - from idlelib import aboutDialog - aboutDialog.AboutDialog(root, 'About IDLE') + # Synchronize with editor.EditorWindow.about_dialog. + from idlelib import help_about + help_about.AboutDialog(root, 'About IDLE') def config_dialog(event=None): "Handle Options 'Configure IDLE' event." - # Synchronize with EditorWindow.EditorWindow.config_dialog. - from idlelib import configDialog + # Synchronize with editor.EditorWindow.config_dialog. + from idlelib import configdialog # Ensure that the root object has an instance_dict attribute, # mirrors code in EditorWindow (although that sets the attribute # on an EditorWindow instance that is then passed as the first # argument to ConfigDialog) root.instance_dict = flist.inversedict - configDialog.ConfigDialog(root, 'Settings') + configdialog.ConfigDialog(root, 'Settings') def help_dialog(event=None): "Handle Help 'IDLE Help' event." - # Synchronize with EditorWindow.EditorWindow.help_dialog. + # Synchronize with editor.EditorWindow.help_dialog. from idlelib import help help.show_idlehelp(root) @@ -198,29 +202,33 @@ def overrideRootMenu(root, flist): menudict['application'] = menu = Menu(menubar, name='apple', tearoff=0) menubar.add_cascade(label='IDLE', menu=menu) - Bindings.menudefs.insert(0, + mainmenu.menudefs.insert(0, ('application', [ ('About IDLE', '<<about-idle>>'), None, ])) - tkversion = root.tk.eval('info patchlevel') - if tuple(map(int, tkversion.split('.'))) < (8, 4, 14): - # for earlier AquaTk versions, supply a Preferences menu item - Bindings.menudefs[0][1].append( - ('_Preferences....', '<<open-config-dialog>>'), - ) if isCocoaTk(): # replace default About dialog with About IDLE one root.createcommand('tkAboutDialog', about_dialog) # replace default "Help" item in Help menu root.createcommand('::tk::mac::ShowHelp', help_dialog) # remove redundant "IDLE Help" from menu - del Bindings.menudefs[-1][1][0] + del mainmenu.menudefs[-1][1][0] + +def fixb2context(root): + '''Removed bad AquaTk Button-2 (right) and Paste bindings. + + They prevent context menu access and seem to be gone in AquaTk8.6. + See issue #24801. + ''' + root.unbind_class('Text', '<B2>') + root.unbind_class('Text', '<B2-Motion>') + root.unbind_class('Text', '<<PasteSelection>>') def setupApp(root, flist): """ Perform initial OS X customizations if needed. - Called from PyShell.main() after initial calls to Tk() + Called from pyshell.main() after initial calls to Tk() There are currently three major versions of Tk in use on OS X: 1. Aqua Cocoa Tk (native default since OS X 10.6) @@ -233,8 +241,13 @@ def setupApp(root, flist): isAquaTk(), isCarbonTk(), isCocoaTk(), isXQuartz() functions which are initialized here as well. """ - _initializeTkVariantTests(root) if isAquaTk(): hideTkConsole(root) overrideRootMenu(root, flist) addOpenEventSupport(root, flist) + fixb2context(root) + + +if __name__ == '__main__': + from unittest import main + main('idlelib.idle_test.test_macosx', verbosity=2) diff --git a/Lib/idlelib/Bindings.py b/Lib/idlelib/mainmenu.py index e19a279..65345cd 100644 --- a/Lib/idlelib/Bindings.py +++ b/Lib/idlelib/mainmenu.py @@ -10,9 +10,9 @@ windows. """ from importlib.util import find_spec -from idlelib.configHandler import idleConf +from idlelib.config import idleConf -# Warning: menudefs is altered in macosxSupport.overrideRootMenu() +# Warning: menudefs is altered in macosx.overrideRootMenu() # after it is determined that an OS X Aqua Tk is in use, # which cannot be done until after Tk() is first called. # Do not alter the 'file', 'options', or 'help' cascades here diff --git a/Lib/idlelib/MultiCall.py b/Lib/idlelib/multicall.py index 8462854..b74fed4 100644 --- a/Lib/idlelib/MultiCall.py +++ b/Lib/idlelib/multicall.py @@ -28,9 +28,9 @@ The order by which events are called is defined by these rules: unless this conflicts with the first rule. Each function will be called at most once for each event. """ - -import sys import re +import sys + import tkinter # the event type constants, which define the meaning of mc_type @@ -414,12 +414,12 @@ def MultiCallCreator(widget): return MultiCall -def _multi_call(parent): - root = tkinter.Tk() - root.title("Test MultiCall") - width, height, x, y = list(map(int, re.split('[x+]', parent.geometry()))) - root.geometry("+%d+%d"%(x, y + 150)) - text = MultiCallCreator(tkinter.Text)(root) +def _multi_call(parent): # htest # + top = tkinter.Toplevel(parent) + top.title("Test MultiCall") + x, y = map(int, parent.geometry().split('+')[1:]) + top.geometry("+%d+%d" % (x, y + 175)) + text = MultiCallCreator(tkinter.Text)(top) text.pack() def bindseq(seq, n=[0]): def handler(event): @@ -439,7 +439,6 @@ def _multi_call(parent): bindseq("<FocusOut>") bindseq("<Enter>") bindseq("<Leave>") - root.mainloop() if __name__ == "__main__": from idlelib.idle_test.htest import run diff --git a/Lib/idlelib/OutputWindow.py b/Lib/idlelib/outwin.py index e614f9b..f6d2915 100644 --- a/Lib/idlelib/OutputWindow.py +++ b/Lib/idlelib/outwin.py @@ -1,8 +1,11 @@ -from tkinter import * -from idlelib.EditorWindow import EditorWindow import re + +from tkinter import * import tkinter.messagebox as tkMessageBox -from idlelib import IOBinding + +from idlelib.editor import EditorWindow +from idlelib import iomenu + class OutputWindow(EditorWindow): @@ -36,7 +39,7 @@ class OutputWindow(EditorWindow): def write(self, s, tags=(), mark="insert"): if isinstance(s, (bytes, bytes)): - s = s.decode(IOBinding.encoding, "replace") + s = s.decode(iomenu.encoding, "replace") self.text.insert(mark, s, tags) self.text.see(mark) self.text.update() diff --git a/Lib/idlelib/FormatParagraph.py b/Lib/idlelib/paragraph.py index 7a9d185..f11bdae 100644 --- a/Lib/idlelib/FormatParagraph.py +++ b/Lib/idlelib/paragraph.py @@ -14,9 +14,10 @@ Known problems with comment reformatting: spaces, they will not be considered part of the same block. * Fancy comments, like this bulleted list, aren't handled :-) """ - import re -from idlelib.configHandler import idleConf + +from idlelib.config import idleConf + class FormatParagraph: @@ -129,7 +130,7 @@ def reformat_paragraph(data, limit): partial = indent1 while i < n and not is_all_white(lines[i]): # XXX Should take double space after period (etc.) into account - words = re.split("(\s+)", lines[i]) + words = re.split(r"(\s+)", lines[i]) for j in range(0, len(words), 2): word = words[j] if not word: @@ -189,7 +190,8 @@ def get_comment_header(line): if m is None: return "" return m.group(1) + if __name__ == "__main__": import unittest - unittest.main('idlelib.idle_test.test_formatparagraph', + unittest.main('idlelib.idle_test.test_paragraph', verbosity=2, exit=False) diff --git a/Lib/idlelib/ParenMatch.py b/Lib/idlelib/parenmatch.py index 47e10f3..ccec708 100644 --- a/Lib/idlelib/ParenMatch.py +++ b/Lib/idlelib/parenmatch.py @@ -4,9 +4,8 @@ When you hit a right paren, the cursor should move briefly to the left paren. Paren here is used generically; the matching applies to parentheses, square brackets, and curly braces. """ - -from idlelib.HyperParser import HyperParser -from idlelib.configHandler import idleConf +from idlelib.hyperparser import HyperParser +from idlelib.config import idleConf _openers = {')':'(',']':'[','}':'{'} CHECK_DELAY = 100 # milliseconds @@ -64,6 +63,7 @@ class ParenMatch: # and deactivate_restore (which calls event_delete). editwin.text.bind(self.RESTORE_VIRTUAL_EVENT_NAME, self.restore_event) + self.bell = self.text.bell if self.BELL else lambda: None self.counter = 0 self.is_restore_active = 0 self.set_style(self.STYLE) @@ -93,7 +93,7 @@ class ParenMatch: indices = (HyperParser(self.editwin, "insert") .get_surrounding_brackets()) if indices is None: - self.warn_mismatched() + self.bell() return self.activate_restore() self.create_tag(indices) @@ -109,7 +109,7 @@ class ParenMatch: return indices = hp.get_surrounding_brackets(_openers[closer], True) if indices is None: - self.warn_mismatched() + self.bell() return self.activate_restore() self.create_tag(indices) @@ -124,10 +124,6 @@ class ParenMatch: if timer_count == self.counter: self.restore_event() - def warn_mismatched(self): - if self.BELL: - self.text.bell() - # any one of the create_tag_XXX methods can be used depending on # the style diff --git a/Lib/idlelib/PathBrowser.py b/Lib/idlelib/pathbrowser.py index 9ab7632..6c19508 100644 --- a/Lib/idlelib/PathBrowser.py +++ b/Lib/idlelib/pathbrowser.py @@ -1,10 +1,10 @@ +import importlib.machinery import os import sys -import importlib.machinery -from idlelib.TreeWidget import TreeItem -from idlelib.ClassBrowser import ClassBrowser, ModuleBrowserTreeItem -from idlelib.PyShell import PyShellFileList +from idlelib.browser import ClassBrowser, ModuleBrowserTreeItem +from idlelib.pyshell import PyShellFileList +from idlelib.tree import TreeItem class PathBrowser(ClassBrowser): @@ -24,6 +24,7 @@ class PathBrowser(ClassBrowser): def rootnode(self): return PathBrowserTreeItem() + class PathBrowserTreeItem(TreeItem): def GetText(self): @@ -36,6 +37,7 @@ class PathBrowserTreeItem(TreeItem): sublist.append(item) return sublist + class DirBrowserTreeItem(TreeItem): def __init__(self, dir, packages=[]): @@ -95,6 +97,7 @@ class DirBrowserTreeItem(TreeItem): sorted.sort() return sorted + def _path_browser(parent): # htest # flist = PyShellFileList(parent) PathBrowser(flist, _htest=True) diff --git a/Lib/idlelib/Percolator.py b/Lib/idlelib/percolator.py index b8be2aa..d18daf0 100644 --- a/Lib/idlelib/Percolator.py +++ b/Lib/idlelib/percolator.py @@ -1,5 +1,5 @@ -from idlelib.WidgetRedirector import WidgetRedirector -from idlelib.Delegator import Delegator +from idlelib.delegator import Delegator +from idlelib.redirector import WidgetRedirector class Percolator: @@ -57,7 +57,6 @@ class Percolator: def _percolator(parent): # htest # import tkinter as tk - import re class Tracer(Delegator): def __init__(self, name): @@ -74,8 +73,8 @@ def _percolator(parent): # htest # box = tk.Toplevel(parent) box.title("Test Percolator") - width, height, x, y = list(map(int, re.split('[x+]', parent.geometry()))) - box.geometry("+%d+%d" % (x, y + 150)) + x, y = map(int, parent.geometry().split('+')[1:]) + box.geometry("+%d+%d" % (x, y + 175)) text = tk.Text(box) p = Percolator(text) pin = p.insertfilter @@ -89,10 +88,10 @@ def _percolator(parent): # htest # (pin if var2.get() else pout)(t2) text.pack() - var1 = tk.IntVar() + var1 = tk.IntVar(parent) cb1 = tk.Checkbutton(box, text="Tracer1", command=toggle1, variable=var1) cb1.pack() - var2 = tk.IntVar() + var2 = tk.IntVar(parent) cb2 = tk.Checkbutton(box, text="Tracer2", command=toggle2, variable=var2) cb2.pack() diff --git a/Lib/idlelib/PyParse.py b/Lib/idlelib/pyparse.py index 9ccbb25..6739dfd 100644 --- a/Lib/idlelib/PyParse.py +++ b/Lib/idlelib/pyparse.py @@ -1,6 +1,6 @@ +from collections import Mapping import re import sys -from collections import Mapping # Reason last stmt is continued (or C_NONE if it's not). (C_NONE, C_BACKSLASH, C_STRING_FIRST_LINE, diff --git a/Lib/idlelib/PyShell.py b/Lib/idlelib/pyshell.py index 5dec68e..e1eade1 100755 --- a/Lib/idlelib/PyShell.py +++ b/Lib/idlelib/pyshell.py @@ -1,8 +1,27 @@ #! /usr/bin/env python3 +try: + from tkinter import * +except ImportError: + print("** IDLE can't import Tkinter.\n" + "Your Python may not be configured for Tk. **", file=sys.__stderr__) + sys.exit(1) +import tkinter.messagebox as tkMessageBox +if TkVersion < 8.5: + root = Tk() # otherwise create root in main + root.withdraw() + tkMessageBox.showerror("Idle Cannot Start", + "Idle requires tcl/tk 8.5+, not $s." % TkVersion, + parent=root) + sys.exit(1) + +from code import InteractiveInterpreter import getopt +import io +import linecache import os import os.path +from platform import python_version, system import re import socket import subprocess @@ -10,30 +29,20 @@ import sys import threading import time import tokenize -import io - -import linecache -from code import InteractiveInterpreter -from platform import python_version, system - -try: - from tkinter import * -except ImportError: - print("** IDLE can't import Tkinter.\n" - "Your Python may not be configured for Tk. **", file=sys.__stderr__) - sys.exit(1) -import tkinter.messagebox as tkMessageBox +import warnings -from idlelib.EditorWindow import EditorWindow, fixwordbreaks -from idlelib.FileList import FileList -from idlelib.ColorDelegator import ColorDelegator -from idlelib.UndoDelegator import UndoDelegator -from idlelib.OutputWindow import OutputWindow -from idlelib.configHandler import idleConf +from idlelib import testing # bool value +from idlelib.colorizer import ColorDelegator +from idlelib.config import idleConf +from idlelib import debugger +from idlelib import debugger_r +from idlelib.editor import EditorWindow, fixwordbreaks +from idlelib.filelist import FileList +from idlelib import macosx +from idlelib.outwin import OutputWindow from idlelib import rpc -from idlelib import Debugger -from idlelib import RemoteDebugger -from idlelib import macosxSupport +from idlelib.run import idle_formatwarning, PseudoInputFile, PseudoOutputFile +from idlelib.undo import UndoDelegator HOST = '127.0.0.1' # python execution server on localhost loopback PORT = 0 # someday pass in host, port for remote debug capability @@ -43,20 +52,6 @@ PORT = 0 # someday pass in host, port for remote debug capability # temporarily redirect the stream to the shell window to display warnings when # checking user's code. warning_stream = sys.__stderr__ # None, at least on Windows, if no console. -import warnings - -def idle_formatwarning(message, category, filename, lineno, line=None): - """Format warnings the IDLE way.""" - - s = "\nWarning (from warnings module):\n" - s += ' File \"%s\", line %s\n' % (filename, lineno) - if line is None: - line = linecache.getline(filename, lineno) - line = line.strip() - if line: - s += " %s\n" % line - s += "%s: %s\n" % (category.__name__, message) - return s def idle_showwarning( message, category, filename, lineno, file=None, line=None): @@ -410,7 +405,7 @@ class ModifiedInterpreter(InteractiveInterpreter): # run from the IDLE source directory. del_exitf = idleConf.GetOption('main', 'General', 'delete-exitfunc', default=False, type='bool') - if __name__ == 'idlelib.PyShell': + if __name__ == 'idlelib.pyshell': command = "__import__('idlelib.run').run.main(%r)" % (del_exitf,) else: command = "__import__('run').main(%r)" % (del_exitf,) @@ -468,7 +463,7 @@ class ModifiedInterpreter(InteractiveInterpreter): if debug: try: # Only close subprocess debugger, don't unregister gui_adap! - RemoteDebugger.close_subprocess_debugger(self.rpcclt) + debugger_r.close_subprocess_debugger(self.rpcclt) except: pass # Kill subprocess, spawn a new one, accept connection. @@ -497,7 +492,7 @@ class ModifiedInterpreter(InteractiveInterpreter): # restart subprocess debugger if debug: # Restarted debugger connects to current instance of debug GUI - RemoteDebugger.restart_subprocess_debugger(self.rpcclt) + debugger_r.restart_subprocess_debugger(self.rpcclt) # reload remote debugger breakpoints for all PyShellEditWindows debug.load_breakpoints() self.compile.compiler.flags = self.original_compiler_flags @@ -578,7 +573,7 @@ class ModifiedInterpreter(InteractiveInterpreter): if self.tkconsole.getvar("<<toggle-jit-stack-viewer>>"): self.remote_stack_viewer() elif how == "ERROR": - errmsg = "PyShell.ModifiedInterpreter: Subprocess ERROR:\n" + errmsg = "pyshell.ModifiedInterpreter: Subprocess ERROR:\n" print(errmsg, what, file=sys.__stderr__) print(errmsg, what, file=console) # we received a response to the currently active seq number: @@ -613,13 +608,13 @@ class ModifiedInterpreter(InteractiveInterpreter): return def remote_stack_viewer(self): - from idlelib import RemoteObjectBrowser + from idlelib import debugobj_r oid = self.rpcclt.remotequeue("exec", "stackviewer", ("flist",), {}) if oid is None: self.tkconsole.root.bell() return - item = RemoteObjectBrowser.StubObjectTreeItem(self.rpcclt, oid) - from idlelib.TreeWidget import ScrolledCanvas, TreeNode + item = debugobj_r.StubObjectTreeItem(self.rpcclt, oid) + from idlelib.tree import ScrolledCanvas, TreeNode top = Toplevel(self.tkconsole.root) theme = idleConf.CurrentTheme() background = idleConf.GetHighlight(theme, 'normal')['background'] @@ -662,9 +657,9 @@ class ModifiedInterpreter(InteractiveInterpreter): # at the moment, InteractiveInterpreter expects str assert isinstance(source, str) #if isinstance(source, str): - # from idlelib import IOBinding + # from idlelib import iomenu # try: - # source = source.encode(IOBinding.encoding) + # source = source.encode(iomenu.encoding) # except UnicodeError: # self.tkconsole.resetoutput() # self.write("Unsupported characters in input\n") @@ -850,7 +845,7 @@ class PyShell(OutputWindow): # New classes - from idlelib.IdleHistory import History + from idlelib.history import History def __init__(self, flist=None): if use_subprocess: @@ -888,11 +883,11 @@ class PyShell(OutputWindow): self.save_stdout = sys.stdout self.save_stderr = sys.stderr self.save_stdin = sys.stdin - from idlelib import IOBinding - self.stdin = PseudoInputFile(self, "stdin", IOBinding.encoding) - self.stdout = PseudoOutputFile(self, "stdout", IOBinding.encoding) - self.stderr = PseudoOutputFile(self, "stderr", IOBinding.encoding) - self.console = PseudoOutputFile(self, "console", IOBinding.encoding) + from idlelib import iomenu + self.stdin = PseudoInputFile(self, "stdin", iomenu.encoding) + self.stdout = PseudoOutputFile(self, "stdout", iomenu.encoding) + self.stderr = PseudoOutputFile(self, "stderr", iomenu.encoding) + self.console = PseudoOutputFile(self, "console", iomenu.encoding) if not use_subprocess: sys.stdout = self.stdout sys.stderr = self.stderr @@ -900,7 +895,7 @@ class PyShell(OutputWindow): try: # page help() text to shell. import pydoc # import must be done here to capture i/o rebinding. - # XXX KBK 27Dec07 use a textView someday, but must work w/o subproc + # XXX KBK 27Dec07 use TextViewer someday, but must work w/o subproc pydoc.pager = pydoc.plainpager except: sys.stderr = sys.__stderr__ @@ -954,7 +949,7 @@ class PyShell(OutputWindow): self.interp.setdebugger(None) db.close() if self.interp.rpcclt: - RemoteDebugger.close_remote_debugger(self.interp.rpcclt) + debugger_r.close_remote_debugger(self.interp.rpcclt) self.resetoutput() self.console.write("[DEBUG OFF]\n") sys.ps1 = ">>> " @@ -963,10 +958,10 @@ class PyShell(OutputWindow): def open_debugger(self): if self.interp.rpcclt: - dbg_gui = RemoteDebugger.start_remote_debugger(self.interp.rpcclt, + dbg_gui = debugger_r.start_remote_debugger(self.interp.rpcclt, self) else: - dbg_gui = Debugger.Debugger(self) + dbg_gui = debugger.Debugger(self) self.interp.setdebugger(dbg_gui) dbg_gui.load_breakpoints() sys.ps1 = "[DEBUG ON]\n>>> " @@ -1241,7 +1236,7 @@ class PyShell(OutputWindow): "(sys.last_traceback is not defined)", parent=self.text) return - from idlelib.StackViewer import StackBrowser + from idlelib.stackviewer import StackBrowser StackBrowser(self.root, self.flist) def view_restart_mark(self, event=None): @@ -1309,92 +1304,6 @@ class PyShell(OutputWindow): return 'disabled' return super().rmenu_check_paste() -class PseudoFile(io.TextIOBase): - - def __init__(self, shell, tags, encoding=None): - self.shell = shell - self.tags = tags - self._encoding = encoding - - @property - def encoding(self): - return self._encoding - - @property - def name(self): - return '<%s>' % self.tags - - def isatty(self): - return True - - -class PseudoOutputFile(PseudoFile): - - def writable(self): - return True - - def write(self, s): - if self.closed: - raise ValueError("write to closed file") - if type(s) is not str: - if not isinstance(s, str): - raise TypeError('must be str, not ' + type(s).__name__) - # See issue #19481 - s = str.__str__(s) - return self.shell.write(s, self.tags) - - -class PseudoInputFile(PseudoFile): - - def __init__(self, shell, tags, encoding=None): - PseudoFile.__init__(self, shell, tags, encoding) - self._line_buffer = '' - - def readable(self): - return True - - def read(self, size=-1): - if self.closed: - raise ValueError("read from closed file") - if size is None: - size = -1 - elif not isinstance(size, int): - raise TypeError('must be int, not ' + type(size).__name__) - result = self._line_buffer - self._line_buffer = '' - if size < 0: - while True: - line = self.shell.readline() - if not line: break - result += line - else: - while len(result) < size: - line = self.shell.readline() - if not line: break - result += line - self._line_buffer = result[size:] - result = result[:size] - return result - - def readline(self, size=-1): - if self.closed: - raise ValueError("read from closed file") - if size is None: - size = -1 - elif not isinstance(size, int): - raise TypeError('must be int, not ' + type(size).__name__) - line = self._line_buffer or self.shell.readline() - if size < 0: - size = len(line) - eol = line.find('\n', 0, size) - if eol >= 0: - size = eol + 1 - self._line_buffer = line[size:] - return line[:size] - - def close(self): - self.shell.close() - def fix_x11_paste(root): "Make paste replace selection on x11. See issue #5124." @@ -1540,7 +1449,10 @@ def main(): enable_edit = enable_edit or edit_start enable_shell = enable_shell or not enable_edit - # start editor and/or shell windows: + # Setup root. Don't break user code run in IDLE process. + # Don't change environment when testing. + if use_subprocess and not testing: + NoDefaultRoot() root = Tk(className="Idle") root.withdraw() @@ -1549,25 +1461,19 @@ def main(): if system() == 'Windows': iconfile = os.path.join(icondir, 'idle.ico') root.wm_iconbitmap(default=iconfile) - elif TkVersion >= 8.5: + else: ext = '.png' if TkVersion >= 8.6 else '.gif' iconfiles = [os.path.join(icondir, 'idle_%d%s' % (size, ext)) for size in (16, 32, 48)] - icons = [PhotoImage(file=iconfile) for iconfile in iconfiles] + icons = [PhotoImage(master=root, file=iconfile) + for iconfile in iconfiles] root.wm_iconphoto(True, *icons) + # start editor and/or shell windows: fixwordbreaks(root) fix_x11_paste(root) flist = PyShellFileList(root) - macosxSupport.setupApp(root, flist) - - if macosxSupport.isAquaTk(): - # There are some screwed up <2> class bindings for text - # widgets defined in Tk which we need to do away with. - # See issue #24801. - root.unbind_class('Text', '<B2>') - root.unbind_class('Text', '<B2-Motion>') - root.unbind_class('Text', '<<PasteSelection>>') + macosx.setupApp(root, flist) if enable_edit: if not (cmd or script): @@ -1582,7 +1488,7 @@ def main(): shell = flist.open_shell() if not shell: return # couldn't open shell - if macosxSupport.isAquaTk() and flist.dict: + if macosx.isAquaTk() and flist.dict: # On OSX: when the user has double-clicked on a file that causes # IDLE to be launched the shell window will open just in front of # the file she wants to see. Lower the interpreter window when @@ -1616,7 +1522,7 @@ def main(): # check for problematic OS X Tk versions and print a warning # message in the IDLE shell window; this is less intrusive # than always opening a separate window. - tkversionwarning = macosxSupport.tkVersionWarning(root) + tkversionwarning = macosx.tkVersionWarning(root) if tkversionwarning: shell.interp.runcommand("print('%s')" % tkversionwarning) @@ -1626,7 +1532,7 @@ def main(): capture_warnings(False) if __name__ == "__main__": - sys.modules['PyShell'] = sys.modules['__main__'] + sys.modules['pyshell'] = sys.modules['__main__'] main() capture_warnings(False) # Make sure turned off; see issue 18081 diff --git a/Lib/idlelib/query.py b/Lib/idlelib/query.py new file mode 100644 index 0000000..3b1f1e2 --- /dev/null +++ b/Lib/idlelib/query.py @@ -0,0 +1,308 @@ +""" +Dialogs that query users and verify the answer before accepting. +Use ttk widgets, limiting use to tcl/tk 8.5+, as in IDLE 3.6+. + +Query is the generic base class for a popup dialog. +The user must either enter a valid answer or close the dialog. +Entries are validated when <Return> is entered or [Ok] is clicked. +Entries are ignored when [Cancel] or [X] are clicked. +The 'return value' is .result set to either a valid answer or None. + +Subclass SectionName gets a name for a new config file section. +Configdialog uses it for new highlight theme and keybinding set names. +Subclass ModuleName gets a name for File => Open Module. +Subclass HelpSource gets menu item and path for additions to Help menu. +""" +# Query and Section name result from splitting GetCfgSectionNameDialog +# of configSectionNameDialog.py (temporarily config_sec.py) into +# generic and specific parts. 3.6 only, July 2016. +# ModuleName.entry_ok came from editor.EditorWindow.load_module. +# HelpSource was extracted from configHelpSourceEdit.py (temporarily +# config_help.py), with darwin code moved from ok to path_ok. + +import importlib +import os +from sys import executable, platform # Platform is set for one test. + +from tkinter import Toplevel, StringVar, W, E, N, S +from tkinter.ttk import Frame, Button, Entry, Label +from tkinter import filedialog +from tkinter.font import Font + +class Query(Toplevel): + """Base class for getting verified answer from a user. + + For this base class, accept any non-blank string. + """ + def __init__(self, parent, title, message, *, text0='', used_names={}, + _htest=False, _utest=False): + """Create popup, do not return until tk widget destroyed. + + Additional subclass init must be done before calling this + unless _utest=True is passed to suppress wait_window(). + + title - string, title of popup dialog + message - string, informational message to display + text0 - initial value for entry + used_names - names already in use + _htest - bool, change box location when running htest + _utest - bool, leave window hidden and not modal + """ + Toplevel.__init__(self, parent) + self.withdraw() # Hide while configuring, especially geometry. + self.parent = parent + self.title(title) + self.message = message + self.text0 = text0 + self.used_names = used_names + self.transient(parent) + self.grab_set() + windowingsystem = self.tk.call('tk', 'windowingsystem') + if windowingsystem == 'aqua': + try: + self.tk.call('::tk::unsupported::MacWindowStyle', 'style', + self._w, 'moveableModal', '') + except: + pass + self.bind("<Command-.>", self.cancel) + self.bind('<Key-Escape>', self.cancel) + self.protocol("WM_DELETE_WINDOW", self.cancel) + self.bind('<Key-Return>', self.ok) + self.bind("<KP_Enter>", self.ok) + self.resizable(height=False, width=False) + self.create_widgets() + self.update_idletasks() # Needed here for winfo_reqwidth below. + self.geometry( # Center dialog over parent (or below htest box). + "+%d+%d" % ( + parent.winfo_rootx() + + (parent.winfo_width()/2 - self.winfo_reqwidth()/2), + parent.winfo_rooty() + + ((parent.winfo_height()/2 - self.winfo_reqheight()/2) + if not _htest else 150) + ) ) + if not _utest: + self.deiconify() # Unhide now that geometry set. + self.wait_window() + + def create_widgets(self): # Call from override, if any. + # Bind to self widgets needed for entry_ok or unittest. + self.frame = frame = Frame(self, padding=10) + frame.grid(column=0, row=0, sticky='news') + frame.grid_columnconfigure(0, weight=1) + + entrylabel = Label(frame, anchor='w', justify='left', + text=self.message) + self.entryvar = StringVar(self, self.text0) + self.entry = Entry(frame, width=30, textvariable=self.entryvar) + self.entry.focus_set() + self.error_font = Font(name='TkCaptionFont', + exists=True, root=self.parent) + self.entry_error = Label(frame, text=' ', foreground='red', + font=self.error_font) + self.button_ok = Button( + frame, text='OK', default='active', command=self.ok) + self.button_cancel = Button( + frame, text='Cancel', command=self.cancel) + + entrylabel.grid(column=0, row=0, columnspan=3, padx=5, sticky=W) + self.entry.grid(column=0, row=1, columnspan=3, padx=5, sticky=W+E, + pady=[10,0]) + self.entry_error.grid(column=0, row=2, columnspan=3, padx=5, + sticky=W+E) + self.button_ok.grid(column=1, row=99, padx=5) + self.button_cancel.grid(column=2, row=99, padx=5) + + def showerror(self, message, widget=None): + #self.bell(displayof=self) + (widget or self.entry_error)['text'] = 'ERROR: ' + message + + def entry_ok(self): # Example: usually replace. + "Return non-blank entry or None." + self.entry_error['text'] = '' + entry = self.entry.get().strip() + if not entry: + self.showerror('blank line.') + return None + return entry + + def ok(self, event=None): # Do not replace. + '''If entry is valid, bind it to 'result' and destroy tk widget. + + Otherwise leave dialog open for user to correct entry or cancel. + ''' + entry = self.entry_ok() + if entry is not None: + self.result = entry + self.destroy() + else: + # [Ok] moves focus. (<Return> does not.) Move it back. + self.entry.focus_set() + + def cancel(self, event=None): # Do not replace. + "Set dialog result to None and destroy tk widget." + self.result = None + self.destroy() + + +class SectionName(Query): + "Get a name for a config file section name." + # Used in ConfigDialog.GetNewKeysName, .GetNewThemeName (837) + + def __init__(self, parent, title, message, used_names, + *, _htest=False, _utest=False): + super().__init__(parent, title, message, used_names=used_names, + _htest=_htest, _utest=_utest) + + def entry_ok(self): + "Return sensible ConfigParser section name or None." + self.entry_error['text'] = '' + name = self.entry.get().strip() + if not name: + self.showerror('no name specified.') + return None + elif len(name)>30: + self.showerror('name is longer than 30 characters.') + return None + elif name in self.used_names: + self.showerror('name is already in use.') + return None + return name + + +class ModuleName(Query): + "Get a module name for Open Module menu entry." + # Used in open_module (editor.EditorWindow until move to iobinding). + + def __init__(self, parent, title, message, text0, + *, _htest=False, _utest=False): + super().__init__(parent, title, message, text0=text0, + _htest=_htest, _utest=_utest) + + def entry_ok(self): + "Return entered module name as file path or None." + self.entry_error['text'] = '' + name = self.entry.get().strip() + if not name: + self.showerror('no name specified.') + return None + # XXX Ought to insert current file's directory in front of path. + try: + spec = importlib.util.find_spec(name) + except (ValueError, ImportError) as msg: + self.showerror(str(msg)) + return None + if spec is None: + self.showerror("module not found") + return None + if not isinstance(spec.loader, importlib.abc.SourceLoader): + self.showerror("not a source-based module") + return None + try: + file_path = spec.loader.get_filename(name) + except AttributeError: + self.showerror("loader does not support get_filename", + parent=self) + return None + return file_path + + +class HelpSource(Query): + "Get menu name and help source for Help menu." + # Used in ConfigDialog.HelpListItemAdd/Edit, (941/9) + + def __init__(self, parent, title, *, menuitem='', filepath='', + used_names={}, _htest=False, _utest=False): + """Get menu entry and url/local file for Additional Help. + + User enters a name for the Help resource and a web url or file + name. The user can browse for the file. + """ + self.filepath = filepath + message = 'Name for item on Help menu:' + super().__init__( + parent, title, message, text0=menuitem, + used_names=used_names, _htest=_htest, _utest=_utest) + + def create_widgets(self): + super().create_widgets() + frame = self.frame + pathlabel = Label(frame, anchor='w', justify='left', + text='Help File Path: Enter URL or browse for file') + self.pathvar = StringVar(self, self.filepath) + self.path = Entry(frame, textvariable=self.pathvar, width=40) + browse = Button(frame, text='Browse', width=8, + command=self.browse_file) + self.path_error = Label(frame, text=' ', foreground='red', + font=self.error_font) + + pathlabel.grid(column=0, row=10, columnspan=3, padx=5, pady=[10,0], + sticky=W) + self.path.grid(column=0, row=11, columnspan=2, padx=5, sticky=W+E, + pady=[10,0]) + browse.grid(column=2, row=11, padx=5, sticky=W+S) + self.path_error.grid(column=0, row=12, columnspan=3, padx=5, + sticky=W+E) + + def askfilename(self, filetypes, initdir, initfile): # htest # + # Extracted from browse_file so can mock for unittests. + # Cannot unittest as cannot simulate button clicks. + # Test by running htest, such as by running this file. + return filedialog.Open(parent=self, filetypes=filetypes)\ + .show(initialdir=initdir, initialfile=initfile) + + def browse_file(self): + filetypes = [ + ("HTML Files", "*.htm *.html", "TEXT"), + ("PDF Files", "*.pdf", "TEXT"), + ("Windows Help Files", "*.chm"), + ("Text Files", "*.txt", "TEXT"), + ("All Files", "*")] + path = self.pathvar.get() + if path: + dir, base = os.path.split(path) + else: + base = None + if platform[:3] == 'win': + dir = os.path.join(os.path.dirname(executable), 'Doc') + if not os.path.isdir(dir): + dir = os.getcwd() + else: + dir = os.getcwd() + file = self.askfilename(filetypes, dir, base) + if file: + self.pathvar.set(file) + + item_ok = SectionName.entry_ok # localize for test override + + def path_ok(self): + "Simple validity check for menu file path" + path = self.path.get().strip() + if not path: #no path specified + self.showerror('no help file path specified.', self.path_error) + return None + elif not path.startswith(('www.', 'http')): + if path[:5] == 'file:': + path = path[5:] + if not os.path.exists(path): + self.showerror('help file path does not exist.', + self.path_error) + return None + if platform == 'darwin': # for Mac Safari + path = "file://" + path + return path + + def entry_ok(self): + "Return apparently valid (name, path) or None" + self.entry_error['text'] = '' + self.path_error['text'] = '' + name = self.item_ok() + path = self.path_ok() + return None if name is None or path is None else (name, path) + + +if __name__ == '__main__': + import unittest + unittest.main('idlelib.idle_test.test_query', verbosity=2, exit=False) + + from idlelib.idle_test.htest import run + run(Query, HelpSource) diff --git a/Lib/idlelib/WidgetRedirector.py b/Lib/idlelib/redirector.py index b66be9e..ec681de 100644 --- a/Lib/idlelib/WidgetRedirector.py +++ b/Lib/idlelib/redirector.py @@ -104,7 +104,7 @@ class WidgetRedirector: Note that if a registered function is called, the operation is not passed through to Tk. Apply the function returned by self.register() - to *args to accomplish that. For an example, see ColorDelegator.py. + to *args to accomplish that. For an example, see colorizer.py. ''' m = self._operations.get(operation) @@ -151,14 +151,13 @@ class OriginalCommand: def _widget_redirector(parent): # htest # - from tkinter import Tk, Text - import re - - root = Tk() - root.title("Test WidgetRedirector") - width, height, x, y = list(map(int, re.split('[x+]', parent.geometry()))) - root.geometry("+%d+%d"%(x, y + 150)) - text = Text(root) + from tkinter import Toplevel, Text + + top = Toplevel(parent) + top.title("Test WidgetRedirector") + x, y = map(int, parent.geometry().split('+')[1:]) + top.geometry("+%d+%d" % (x, y + 175)) + text = Text(top) text.pack() text.focus_set() redir = WidgetRedirector(text) @@ -166,11 +165,11 @@ def _widget_redirector(parent): # htest # print("insert", args) original_insert(*args) original_insert = redir.register("insert", my_insert) - root.mainloop() if __name__ == "__main__": import unittest - unittest.main('idlelib.idle_test.test_widgetredir', + unittest.main('idlelib.idle_test.test_redirector', verbosity=2, exit=False) + from idlelib.idle_test.htest import run run(_widget_redirector) diff --git a/Lib/idlelib/ReplaceDialog.py b/Lib/idlelib/replace.py index f2ea22e..abd9e59 100644 --- a/Lib/idlelib/ReplaceDialog.py +++ b/Lib/idlelib/replace.py @@ -3,18 +3,18 @@ Uses idlelib.SearchEngine for search capability. Defines various replace related functions like replace, replace all, replace+find. """ -from tkinter import * - -from idlelib import SearchEngine -from idlelib.SearchDialogBase import SearchDialogBase import re +from tkinter import StringVar, TclError + +from idlelib.searchbase import SearchDialogBase +from idlelib import searchengine 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) + engine = searchengine.get(root) if not hasattr(engine, "_replacedialog"): engine._replacedialog = ReplaceDialog(root, engine) dialog = engine._replacedialog @@ -95,7 +95,7 @@ class ReplaceDialog(SearchDialogBase): text = self.text res = self.engine.search_text(text, prog) if not res: - text.bell() + self.bell() return text.tag_remove("sel", "1.0", "end") text.tag_remove("hit", "1.0", "end") @@ -142,7 +142,7 @@ class ReplaceDialog(SearchDialogBase): text = self.text res = self.engine.search_text(text, None, ok) if not res: - text.bell() + self.bell() return False line, m = res i, j = m.span() @@ -164,7 +164,7 @@ class ReplaceDialog(SearchDialogBase): pos = None if not pos: first = last = pos = text.index("insert") - line, col = SearchEngine.get_line_col(pos) + line, col = searchengine.get_line_col(pos) chars = text.get("%d.0" % line, "%d.0" % (line+1)) m = prog.match(chars, col) if not prog: @@ -204,11 +204,13 @@ class ReplaceDialog(SearchDialogBase): def _replace_dialog(parent): # htest # - """htest wrapper function""" + from tkinter import Toplevel, Text, END, SEL + from tkinter.ttk import Button + box = Toplevel(parent) box.title("Test ReplaceDialog") - width, height, x, y = list(map(int, re.split('[x+]', parent.geometry()))) - box.geometry("+%d+%d"%(x, y + 150)) + x, y = map(int, parent.geometry().split('+')[1:]) + box.geometry("+%d+%d" % (x, y + 175)) # mock undo delegator methods def undo_block_start(): @@ -234,7 +236,7 @@ def _replace_dialog(parent): # htest # if __name__ == '__main__': import unittest - unittest.main('idlelib.idle_test.test_replacedialog', + unittest.main('idlelib.idle_test.test_replace', verbosity=2, exit=False) from idlelib.idle_test.htest import run diff --git a/Lib/idlelib/rpc.py b/Lib/idlelib/rpc.py index 48105f2..8f57edb8 100644 --- a/Lib/idlelib/rpc.py +++ b/Lib/idlelib/rpc.py @@ -26,23 +26,21 @@ See the Idle run.main() docstring for further information on how this was accomplished in Idle. """ - -import sys -import os +import builtins +import copyreg import io -import socket +import marshal +import os +import pickle +import queue import select +import socket import socketserver import struct -import pickle +import sys import threading -import queue import traceback -import copyreg import types -import marshal -import builtins - def unpickle_code(ms): co = marshal.loads(ms) @@ -60,10 +58,12 @@ def dumps(obj, protocol=None): p.dump(obj) return f.getvalue() + class CodePickler(pickle.Pickler): dispatch_table = {types.CodeType: pickle_code} dispatch_table.update(copyreg.dispatch_table) + BUFSIZE = 8*1024 LOCALHOST = '127.0.0.1' @@ -487,16 +487,19 @@ class RemoteObject(object): # Token mix-in class pass + def remoteref(obj): oid = id(obj) objecttable[oid] = obj return RemoteProxy(oid) + class RemoteProxy(object): def __init__(self, oid): self.oid = oid + class RPCHandler(socketserver.BaseRequestHandler, SocketIO): debugging = False @@ -514,6 +517,7 @@ class RPCHandler(socketserver.BaseRequestHandler, SocketIO): def get_remote_proxy(self, oid): return RPCProxy(self, oid) + class RPCClient(SocketIO): debugging = False @@ -539,6 +543,7 @@ class RPCClient(SocketIO): def get_remote_proxy(self, oid): return RPCProxy(self, oid) + class RPCProxy(object): __methods = None @@ -587,6 +592,7 @@ def _getattributes(obj, attributes): if not callable(attr): attributes[name] = 1 + class MethodProxy(object): def __init__(self, sockio, oid, name): diff --git a/Lib/idlelib/RstripExtension.py b/Lib/idlelib/rstrip.py index 2ce3c7e..2ce3c7e 100644 --- a/Lib/idlelib/RstripExtension.py +++ b/Lib/idlelib/rstrip.py diff --git a/Lib/idlelib/run.py b/Lib/idlelib/run.py index 28ce420..afa9744 100644 --- a/Lib/idlelib/run.py +++ b/Lib/idlelib/run.py @@ -1,33 +1,45 @@ -import sys +import io import linecache +import queue +import sys import time import traceback import _thread as thread import threading -import queue -import tkinter - -from idlelib import CallTips -from idlelib import AutoComplete +import warnings -from idlelib import RemoteDebugger -from idlelib import RemoteObjectBrowser -from idlelib import StackViewer -from idlelib import rpc -from idlelib import PyShell -from idlelib import IOBinding +import tkinter # Tcl, deletions, messagebox if startup fails +from idlelib import autocomplete # AutoComplete, fetch_encodings +from idlelib import calltips # CallTips +from idlelib import debugger_r # start_debugger +from idlelib import debugobj_r # remote_object_tree_item +from idlelib import iomenu # encoding +from idlelib import rpc # multiple objects +from idlelib import stackviewer # StackTreeItem import __main__ for mod in ('simpledialog', 'messagebox', 'font', 'dialog', 'filedialog', 'commondialog', - 'colorchooser'): + 'ttk'): delattr(tkinter, mod) del sys.modules['tkinter.' + mod] LOCALHOST = '127.0.0.1' -import warnings + +def idle_formatwarning(message, category, filename, lineno, line=None): + """Format warnings the IDLE way.""" + + s = "\nWarning (from warnings module):\n" + s += ' File \"%s\", line %s\n' % (filename, lineno) + if line is None: + line = linecache.getline(filename, lineno) + line = line.strip() + if line: + s += " %s\n" % line + s += "%s: %s\n" % (category.__name__, message) + return s def idle_showwarning_subproc( message, category, filename, lineno, file=None, line=None): @@ -38,7 +50,7 @@ def idle_showwarning_subproc( if file is None: file = sys.stderr try: - file.write(PyShell.idle_formatwarning( + file.write(idle_formatwarning( message, category, filename, lineno, line)) except IOError: pass # the file (probably stderr) is invalid - this warning gets lost. @@ -88,7 +100,7 @@ def main(del_exitfunc=False): MyHandler object. That reference is saved as attribute rpchandler of the Executive instance. The Executive methods have access to the reference and can pass it on to entities that they command - (e.g. RemoteDebugger.Debugger.start_debugger()). The latter, in turn, can + (e.g. debugger_r.Debugger.start_debugger()). The latter, in turn, can call MyHandler(SocketIO) register/unregister methods via the reference to register and unregister themselves. @@ -210,7 +222,7 @@ def print_exception(): tbe = traceback.extract_tb(tb) print('Traceback (most recent call last):', file=efile) exclude = ("run.py", "rpc.py", "threading.py", "queue.py", - "RemoteDebugger.py", "bdb.py") + "debugger_r.py", "bdb.py") cleanup_traceback(tbe, exclude) traceback.print_list(tbe, file=efile) lines = traceback.format_exception_only(typ, exc) @@ -267,6 +279,7 @@ def exit(): capture_warnings(False) sys.exit(0) + class MyRPCServer(rpc.RPCServer): def handle_error(self, request, client_address): @@ -297,6 +310,96 @@ class MyRPCServer(rpc.RPCServer): quitting = True thread.interrupt_main() + +# Pseudofiles for shell-remote communication (also used in pyshell) + +class PseudoFile(io.TextIOBase): + + def __init__(self, shell, tags, encoding=None): + self.shell = shell + self.tags = tags + self._encoding = encoding + + @property + def encoding(self): + return self._encoding + + @property + def name(self): + return '<%s>' % self.tags + + def isatty(self): + return True + + +class PseudoOutputFile(PseudoFile): + + def writable(self): + return True + + def write(self, s): + if self.closed: + raise ValueError("write to closed file") + if type(s) is not str: + if not isinstance(s, str): + raise TypeError('must be str, not ' + type(s).__name__) + # See issue #19481 + s = str.__str__(s) + return self.shell.write(s, self.tags) + + +class PseudoInputFile(PseudoFile): + + def __init__(self, shell, tags, encoding=None): + PseudoFile.__init__(self, shell, tags, encoding) + self._line_buffer = '' + + def readable(self): + return True + + def read(self, size=-1): + if self.closed: + raise ValueError("read from closed file") + if size is None: + size = -1 + elif not isinstance(size, int): + raise TypeError('must be int, not ' + type(size).__name__) + result = self._line_buffer + self._line_buffer = '' + if size < 0: + while True: + line = self.shell.readline() + if not line: break + result += line + else: + while len(result) < size: + line = self.shell.readline() + if not line: break + result += line + self._line_buffer = result[size:] + result = result[:size] + return result + + def readline(self, size=-1): + if self.closed: + raise ValueError("read from closed file") + if size is None: + size = -1 + elif not isinstance(size, int): + raise TypeError('must be int, not ' + type(size).__name__) + line = self._line_buffer or self.shell.readline() + if size < 0: + size = len(line) + eol = line.find('\n', 0, size) + if eol >= 0: + size = eol + 1 + self._line_buffer = line[size:] + return line[:size] + + def close(self): + self.shell.close() + + class MyHandler(rpc.RPCHandler): def handle(self): @@ -304,12 +407,12 @@ class MyHandler(rpc.RPCHandler): executive = Executive(self) self.register("exec", executive) self.console = self.get_remote_proxy("console") - sys.stdin = PyShell.PseudoInputFile(self.console, "stdin", - IOBinding.encoding) - sys.stdout = PyShell.PseudoOutputFile(self.console, "stdout", - IOBinding.encoding) - sys.stderr = PyShell.PseudoOutputFile(self.console, "stderr", - IOBinding.encoding) + sys.stdin = PseudoInputFile(self.console, "stdin", + iomenu.encoding) + sys.stdout = PseudoOutputFile(self.console, "stdout", + iomenu.encoding) + sys.stderr = PseudoOutputFile(self.console, "stderr", + iomenu.encoding) sys.displayhook = rpc.displayhook # page help() text to shell. @@ -345,8 +448,8 @@ class Executive(object): def __init__(self, rpchandler): self.rpchandler = rpchandler self.locals = __main__.__dict__ - self.calltip = CallTips.CallTips() - self.autocomplete = AutoComplete.AutoComplete() + self.calltip = calltips.CallTips() + self.autocomplete = autocomplete.AutoComplete() def runcode(self, code): global interruptable @@ -378,7 +481,7 @@ class Executive(object): thread.interrupt_main() def start_the_debugger(self, gui_adap_oid): - return RemoteDebugger.start_debugger(self.rpchandler, gui_adap_oid) + return debugger_r.start_debugger(self.rpchandler, gui_adap_oid) def stop_the_debugger(self, idb_adap_oid): "Unregister the Idb Adapter. Link objects and Idb then subject to GC" @@ -402,7 +505,7 @@ class Executive(object): tb = tb.tb_next sys.last_type = typ sys.last_value = val - item = StackViewer.StackTreeItem(flist, tb) - return RemoteObjectBrowser.remote_object_tree_item(item) + item = stackviewer.StackTreeItem(flist, tb) + return debugobj_r.remote_object_tree_item(item) capture_warnings(False) # Make sure turned off; see issue 18081 diff --git a/Lib/idlelib/ScriptBinding.py b/Lib/idlelib/runscript.py index 5cb818d..79d86ad 100644 --- a/Lib/idlelib/ScriptBinding.py +++ b/Lib/idlelib/runscript.py @@ -20,11 +20,12 @@ XXX GvR Redesign this interface (yet again) as follows: import os import tabnanny import tokenize + import tkinter.messagebox as tkMessageBox -from idlelib import PyShell -from idlelib.configHandler import idleConf -from idlelib import macosxSupport +from idlelib.config import idleConf +from idlelib import macosx +from idlelib import pyshell indent_message = """Error: Inconsistent indentation detected! @@ -46,12 +47,12 @@ class ScriptBinding: def __init__(self, editwin): self.editwin = editwin - # Provide instance variables referenced by Debugger + # Provide instance variables referenced by debugger # XXX This should be done differently self.flist = self.editwin.flist self.root = self.editwin.root - if macosxSupport.isCocoaTk(): + if macosx.isCocoaTk(): self.editwin.text_frame.bind('<<run-module-event-2>>', self._run_module_event) def check_module_event(self, event): @@ -112,7 +113,7 @@ class ScriptBinding: shell.set_warning_stream(saved_stream) def run_module_event(self, event): - if macosxSupport.isCocoaTk(): + if macosx.isCocoaTk(): # Tk-Cocoa in MacOSX is broken until at least # Tk 8.5.9, and without this rather # crude workaround IDLE would hang when a user @@ -142,7 +143,7 @@ class ScriptBinding: if not self.tabnanny(filename): return 'break' interp = self.shell.interp - if PyShell.use_subprocess: + if pyshell.use_subprocess: interp.restart_subprocess(with_cwd=False, filename= self.editwin._filename_to_unicode(filename)) dirname = os.path.dirname(filename) @@ -161,7 +162,7 @@ class ScriptBinding: interp.prepend_syspath(filename) # XXX KBK 03Jul04 When run w/o subprocess, runtime warnings still # go to __stderr__. With subprocess, they go to the shell. - # Need to change streams in PyShell.ModifiedInterpreter. + # Need to change streams in pyshell.ModifiedInterpreter. interp.runcode(code) return 'break' diff --git a/Lib/idlelib/ScrolledList.py b/Lib/idlelib/scrolledlist.py index 53576b5..cc08c26 100644 --- a/Lib/idlelib/ScrolledList.py +++ b/Lib/idlelib/scrolledlist.py @@ -1,5 +1,8 @@ from tkinter import * -from idlelib import macosxSupport +from tkinter.ttk import Scrollbar + +from idlelib import macosx + class ScrolledList: @@ -23,7 +26,7 @@ class ScrolledList: # Bind events to the list box listbox.bind("<ButtonRelease-1>", self.click_event) listbox.bind("<Double-ButtonRelease-1>", self.double_click_event) - if macosxSupport.isAquaTk(): + if macosx.isAquaTk(): listbox.bind("<ButtonPress-2>", self.popup_event) listbox.bind("<Control-Button-1>", self.popup_event) else: @@ -124,22 +127,20 @@ class ScrolledList: pass -def _scrolled_list(parent): - root = Tk() - root.title("Test ScrolledList") - width, height, x, y = list(map(int, re.split('[x+]', parent.geometry()))) - root.geometry("+%d+%d"%(x, y + 150)) +def _scrolled_list(parent): # htest # + top = Toplevel(parent) + x, y = map(int, parent.geometry().split('+')[1:]) + top.geometry("+%d+%d" % (x+200, y + 175)) class MyScrolledList(ScrolledList): def fill_menu(self): self.menu.add_command(label="right click") def on_select(self, index): print("select", self.get(index)) def on_double(self, index): print("double", self.get(index)) - scrolled_list = MyScrolledList(root) + scrolled_list = MyScrolledList(top) for i in range(30): scrolled_list.append("Item %02d" % i) - root.mainloop() - if __name__ == '__main__': + # At the moment, test_scrolledlist merely creates instance, like htest. from idlelib.idle_test.htest import run run(_scrolled_list) diff --git a/Lib/idlelib/SearchDialog.py b/Lib/idlelib/search.py index 765d53f..4b90659 100644 --- a/Lib/idlelib/SearchDialog.py +++ b/Lib/idlelib/search.py @@ -1,12 +1,12 @@ -from tkinter import * +from tkinter import TclError -from idlelib import SearchEngine -from idlelib.SearchDialogBase import SearchDialogBase +from idlelib import searchengine +from idlelib.searchbase import SearchDialogBase def _setup(text): "Create or find the singleton SearchDialog instance." root = text._root() - engine = SearchEngine.get(root) + engine = searchengine.get(root) if not hasattr(engine, "_searchdialog"): engine._searchdialog = SearchDialog(root, engine) return engine._searchdialog @@ -24,6 +24,7 @@ def find_selection(text): "Handle the editor edit menu item and corresponding event." return _setup(text).find_selection(text) + class SearchDialog(SearchDialogBase): def create_widgets(self): @@ -51,7 +52,7 @@ class SearchDialog(SearchDialogBase): selfirst = text.index("sel.first") sellast = text.index("sel.last") if selfirst == first and sellast == last: - text.bell() + self.bell() return False except TclError: pass @@ -61,7 +62,7 @@ class SearchDialog(SearchDialogBase): text.see("insert") return True else: - text.bell() + self.bell() return False def find_selection(self, text): @@ -72,26 +73,30 @@ class SearchDialog(SearchDialogBase): def _search_dialog(parent): # htest # - '''Display search test box.''' + "Display search test box." + from tkinter import Toplevel, Text + from tkinter.ttk import Button + box = Toplevel(parent) box.title("Test SearchDialog") - width, height, x, y = list(map(int, re.split('[x+]', parent.geometry()))) - box.geometry("+%d+%d"%(x, y + 150)) + x, y = map(int, parent.geometry().split('+')[1:]) + box.geometry("+%d+%d" % (x, y + 175)) text = Text(box, inactiveselectbackground='gray') text.pack() text.insert("insert","This is a sample string.\n"*5) def show_find(): - text.tag_add(SEL, "1.0", END) + text.tag_add('sel', '1.0', 'end') _setup(text).open(text) - text.tag_remove(SEL, "1.0", END) + text.tag_remove('sel', '1.0', 'end') button = Button(box, text="Search (selection ignored)", command=show_find) button.pack() if __name__ == '__main__': import unittest - unittest.main('idlelib.idle_test.test_searchdialog', + unittest.main('idlelib.idle_test.test_search', verbosity=2, exit=False) + from idlelib.idle_test.htest import run run(_search_dialog) diff --git a/Lib/idlelib/SearchDialogBase.py b/Lib/idlelib/searchbase.py index 5fa84e2..5f81785 100644 --- a/Lib/idlelib/SearchDialogBase.py +++ b/Lib/idlelib/searchbase.py @@ -1,7 +1,8 @@ '''Define SearchDialogBase used by Search, Replace, and Grep dialogs.''' -from tkinter import (Toplevel, Frame, Entry, Label, Button, - Checkbutton, Radiobutton) +from tkinter import Toplevel, Frame +from tkinter.ttk import Entry, Label, Button, Checkbutton, Radiobutton + class SearchDialogBase: '''Create most of a 3 or 4 row, 3 column search dialog. @@ -79,6 +80,7 @@ class SearchDialogBase: top.wm_title(self.title) top.wm_iconname(self.icon) self.top = top + self.bell = top.bell self.row = 0 self.top.grid_columnconfigure(0, pad=2, weight=0) @@ -125,7 +127,7 @@ class SearchDialogBase: def create_option_buttons(self): '''Return (filled frame, options) for testing. - Options is a list of SearchEngine booleanvar, label pairs. + Options is a list of searchengine booleanvar, label pairs. A gridded frame from make_frame is filled with a Checkbutton for each pair, bound to the var, with the corresponding label. ''' @@ -137,10 +139,8 @@ class SearchDialogBase: if self.needwrapbutton: options.append((engine.wrapvar, "Wrap around")) for var, label in options: - btn = Checkbutton(frame, anchor="w", variable=var, text=label) + btn = Checkbutton(frame, variable=var, text=label) btn.pack(side="left", fill="both") - if var.get(): - btn.select() return frame, options def create_other_buttons(self): @@ -153,11 +153,8 @@ class SearchDialogBase: var = self.engine.backvar others = [(1, 'Up'), (0, 'Down')] for val, label in others: - btn = Radiobutton(frame, anchor="w", - variable=var, value=val, text=label) + btn = Radiobutton(frame, variable=var, value=val, text=label) btn.pack(side="left", fill="both") - if var.get() == val: - btn.select() return frame, others def make_button(self, label, command, isdef=0): @@ -178,7 +175,26 @@ class SearchDialogBase: b = self.make_button("close", self.close) b.lower() + +class _searchbase(SearchDialogBase): # htest # + "Create auto-opening dialog with no text connection." + + def __init__(self, parent): + import re + from idlelib import searchengine + + self.root = parent + self.engine = searchengine.get(parent) + self.create_widgets() + print(parent.geometry()) + width,height, x,y = list(map(int, re.split('[x+]', parent.geometry()))) + self.top.geometry("+%d+%d" % (x + 40, y + 175)) + + def default_command(self, dummy): pass + if __name__ == '__main__': import unittest - unittest.main( - 'idlelib.idle_test.test_searchdialogbase', verbosity=2) + unittest.main('idlelib.idle_test.test_searchbase', verbosity=2, exit=False) + + from idlelib.idle_test.htest import run + run(_searchbase) diff --git a/Lib/idlelib/SearchEngine.py b/Lib/idlelib/searchengine.py index 37883bf..253f1b0 100644 --- a/Lib/idlelib/SearchEngine.py +++ b/Lib/idlelib/searchengine.py @@ -1,5 +1,6 @@ '''Define SearchEngine for search dialogs.''' import re + from tkinter import StringVar, BooleanVar, TclError import tkinter.messagebox as tkMessageBox @@ -14,6 +15,7 @@ def get(root): # This creates a cycle that persists until root is deleted. return root._searchengine + class SearchEngine: """Handles searching a text widget for Find, Replace, and Grep.""" @@ -57,7 +59,7 @@ class SearchEngine: def setcookedpat(self, pat): "Set pattern after escaping if re." - # called only in SearchDialog.py: 66 + # called only in search.py: 66 if self.isre(): pat = re.escape(pat) self.setpat(pat) @@ -186,6 +188,7 @@ class SearchEngine: col = len(chars) - 1 return None + def search_reverse(prog, chars, col): '''Search backwards and return an re match object or None. diff --git a/Lib/idlelib/StackViewer.py b/Lib/idlelib/stackviewer.py index ccc755c..0698def 100644 --- a/Lib/idlelib/StackViewer.py +++ b/Lib/idlelib/stackviewer.py @@ -1,12 +1,12 @@ -import os -import sys import linecache +import os import re +import sys + import tkinter as tk -from idlelib.TreeWidget import TreeNode, TreeItem, ScrolledCanvas -from idlelib.ObjectBrowser import ObjectTreeItem, make_objecttreeitem -from idlelib.PyShell import PyShellFileList +from idlelib.debugobj import ObjectTreeItem, make_objecttreeitem +from idlelib.tree import TreeNode, TreeItem, ScrolledCanvas def StackBrowser(root, flist=None, tb=None, top=None): if top is None: @@ -17,6 +17,7 @@ def StackBrowser(root, flist=None, tb=None, top=None): node = TreeNode(sc.canvas, None, item) node.expand() + class StackTreeItem(TreeItem): def __init__(self, flist=None, tb=None): @@ -55,6 +56,7 @@ class StackTreeItem(TreeItem): sublist.append(item) return sublist + class FrameTreeItem(TreeItem): def __init__(self, info, flist): @@ -96,6 +98,7 @@ class FrameTreeItem(TreeItem): if os.path.isfile(filename): self.flist.gotofileline(filename, lineno) + class VariablesTreeItem(ObjectTreeItem): def GetText(self): @@ -120,15 +123,14 @@ class VariablesTreeItem(ObjectTreeItem): sublist.append(item) return sublist - def keys(self): # unused, left for possible 3rd party use - return list(self.object.keys()) -def _stack_viewer(parent): - root = tk.Tk() - root.title("Test StackViewer") - width, height, x, y = list(map(int, re.split('[x+]', parent.geometry()))) - root.geometry("+%d+%d"%(x, y + 150)) - flist = PyShellFileList(root) +def _stack_viewer(parent): # htest # + from idlelib.pyshell import PyShellFileList + top = tk.Toplevel(parent) + top.title("Test StackViewer") + x, y = map(int, parent.geometry().split('+')[1:]) + top.geometry("+%d+%d" % (x + 50, y + 175)) + flist = PyShellFileList(top) try: # to obtain a traceback object intentional_name_error except NameError: @@ -139,7 +141,7 @@ def _stack_viewer(parent): sys.last_value = exc_value sys.last_traceback = exc_tb - StackBrowser(root, flist=flist, top=root, tb=exc_tb) + StackBrowser(top, flist=flist, top=top, tb=exc_tb) # restore sys to original state del sys.last_type diff --git a/Lib/idlelib/MultiStatusBar.py b/Lib/idlelib/statusbar.py index e82ba9a..8618528 100644 --- a/Lib/idlelib/MultiStatusBar.py +++ b/Lib/idlelib/statusbar.py @@ -1,16 +1,15 @@ -from tkinter import * +from tkinter import Frame, Label + class MultiStatusBar(Frame): - def __init__(self, master=None, **kw): - if master is None: - master = Tk() + def __init__(self, master, **kw): Frame.__init__(self, master, **kw) self.labels = {} - def set_label(self, name, text='', side=LEFT, width=0): + def set_label(self, name, text='', side='left', width=0): if name not in self.labels: - label = Label(self, borderwidth=0, anchor=W) + label = Label(self, borderwidth=0, anchor='w') label.pack(side=side, pady=0, padx=4) self.labels[name] = label else: @@ -19,28 +18,28 @@ class MultiStatusBar(Frame): label.config(width=width) label.config(text=text) -def _multistatus_bar(parent): - root = Tk() - width, height, x, y = list(map(int, re.split('[x+]', parent.geometry()))) - root.geometry("+%d+%d" %(x, y + 150)) - root.title("Test multistatus bar") - frame = Frame(root) - text = Text(frame) + +def _multistatus_bar(parent): # htest # + from tkinter import Toplevel, Frame, Text, Button + top = Toplevel(parent) + x, y = map(int, parent.geometry().split('+')[1:]) + top.geometry("+%d+%d" %(x, y + 175)) + top.title("Test multistatus bar") + frame = Frame(top) + text = Text(frame, height=5, width=40) text.pack() msb = MultiStatusBar(frame) msb.set_label("one", "hello") msb.set_label("two", "world") - msb.pack(side=BOTTOM, fill=X) + msb.pack(side='bottom', fill='x') def change(): msb.set_label("one", "foo") msb.set_label("two", "bar") - button = Button(root, text="Update status", command=change) - button.pack(side=BOTTOM) + button = Button(top, text="Update status", command=change) + button.pack(side='bottom') frame.pack() - frame.mainloop() - root.mainloop() if __name__ == '__main__': from idlelib.idle_test.htest import run diff --git a/Lib/idlelib/tabbedpages.py b/Lib/idlelib/tabbedpages.py index 965f9f8..4186fa2 100644 --- a/Lib/idlelib/tabbedpages.py +++ b/Lib/idlelib/tabbedpages.py @@ -285,6 +285,7 @@ class TabSet(Frame): # placed hide it self.tab_set.lower() + class TabbedPageSet(Frame): """A Tkinter tabbed-pane widget. @@ -302,6 +303,7 @@ class TabbedPageSet(Frame): remove_page() methods. """ + class Page(object): """Abstract base class for TabbedPageSet's pages. @@ -467,31 +469,29 @@ class TabbedPageSet(Frame): self._tab_set.set_selected_tab(page_name) -def _tabbed_pages(parent): - # test dialog - root=Tk() - width, height, x, y = list(map(int, re.split('[x+]', parent.geometry()))) - root.geometry("+%d+%d"%(x, y + 175)) - root.title("Test tabbed pages") - tabPage=TabbedPageSet(root, page_names=['Foobar','Baz'], n_rows=0, + +def _tabbed_pages(parent): # htest # + top=Toplevel(parent) + x, y = map(int, parent.geometry().split('+')[1:]) + top.geometry("+%d+%d" % (x, y + 175)) + top.title("Test tabbed pages") + tabPage=TabbedPageSet(top, page_names=['Foobar','Baz'], n_rows=0, expand_tabs=False, ) tabPage.pack(side=TOP, expand=TRUE, fill=BOTH) Label(tabPage.pages['Foobar'].frame, text='Foo', pady=20).pack() Label(tabPage.pages['Foobar'].frame, text='Bar', pady=20).pack() Label(tabPage.pages['Baz'].frame, text='Baz').pack() - entryPgName=Entry(root) - buttonAdd=Button(root, text='Add Page', + entryPgName=Entry(top) + buttonAdd=Button(top, text='Add Page', command=lambda:tabPage.add_page(entryPgName.get())) - buttonRemove=Button(root, text='Remove Page', + buttonRemove=Button(top, text='Remove Page', command=lambda:tabPage.remove_page(entryPgName.get())) - labelPgName=Label(root, text='name of page to add/remove:') + labelPgName=Label(top, text='name of page to add/remove:') buttonAdd.pack(padx=5, pady=5) buttonRemove.pack(padx=5, pady=5) labelPgName.pack(padx=5) entryPgName.pack(padx=5) - root.mainloop() - if __name__ == '__main__': from idlelib.idle_test.htest import run diff --git a/Lib/idlelib/textView.py b/Lib/idlelib/textview.py index 12ac319..adee326 100644 --- a/Lib/idlelib/textView.py +++ b/Lib/idlelib/textview.py @@ -1,14 +1,14 @@ """Simple text browser for IDLE """ - from tkinter import * -import tkinter.messagebox as tkMessageBox +from tkinter.ttk import Scrollbar +from tkinter.messagebox import showerror + class TextViewer(Toplevel): - """A simple text viewer dialog for IDLE + "A simple text viewer dialog for IDLE." - """ def __init__(self, parent, title, text, modal=True, _htest=False): """Show the given text in a scrollable window with a 'close' button @@ -20,11 +20,11 @@ class TextViewer(Toplevel): """ Toplevel.__init__(self, parent) self.configure(borderwidth=5) - # place dialog below parent if running htest + # Place dialog below parent if running htest. self.geometry("=%dx%d+%d+%d" % (750, 500, parent.winfo_rootx() + 10, parent.winfo_rooty() + (10 if not _htest else 100))) - #elguavas - config placeholders til config stuff completed + # TODO: get fg/bg from theme. self.bg = '#ffffff' self.fg = '#000000' @@ -33,9 +33,9 @@ class TextViewer(Toplevel): self.protocol("WM_DELETE_WINDOW", self.Ok) self.parent = parent self.textView.focus_set() - #key bindings for this dialog - self.bind('<Return>',self.Ok) #dismiss dialog - self.bind('<Escape>',self.Ok) #dismiss dialog + # Bind keys for closing this dialog. + self.bind('<Return>',self.Ok) + self.bind('<Escape>',self.Ok) self.textView.insert(0.0, text) self.textView.config(state=DISABLED) @@ -50,7 +50,7 @@ class TextViewer(Toplevel): self.buttonOk = Button(frameButtons, text='Close', command=self.Ok, takefocus=FALSE) self.scrollbarView = Scrollbar(frameText, orient=VERTICAL, - takefocus=FALSE, highlightthickness=0) + takefocus=FALSE) self.textView = Text(frameText, wrap=WORD, highlightthickness=0, fg=self.fg, bg=self.bg) self.scrollbarView.config(command=self.textView.yview) @@ -72,14 +72,14 @@ def view_file(parent, title, filename, encoding=None, modal=True): try: with open(filename, 'r', encoding=encoding) as file: contents = file.read() - except IOError: - tkMessageBox.showerror(title='File Load Error', - message='Unable to load file %r .' % filename, - parent=parent) + except OSError: + 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) + showerror(title='Unicode Decode Error', + message=str(err), + parent=parent) else: return view_text(parent, title, contents, modal) diff --git a/Lib/idlelib/ToolTip.py b/Lib/idlelib/tooltip.py index 964107e..843fb4a 100644 --- a/Lib/idlelib/ToolTip.py +++ b/Lib/idlelib/tooltip.py @@ -1,4 +1,4 @@ -# general purpose 'tooltip' routines - currently unused in idlefork +# general purpose 'tooltip' routines - currently unused in idlelib # (although the 'calltips' extension is partly based on this code) # may be useful for some purposes in (or almost in ;) the current project scope # Ideas gleaned from PySol @@ -76,21 +76,20 @@ class ListboxToolTip(ToolTipBase): for item in self.items: listbox.insert(END, item) -def _tooltip(parent): - root = Tk() - root.title("Test tooltip") - width, height, x, y = list(map(int, re.split('[x+]', parent.geometry()))) - root.geometry("+%d+%d"%(x, y + 150)) - label = Label(root, text="Place your mouse over buttons") +def _tooltip(parent): # htest # + top = Toplevel(parent) + top.title("Test tooltip") + x, y = map(int, parent.geometry().split('+')[1:]) + top.geometry("+%d+%d" % (x, y + 150)) + label = Label(top, text="Place your mouse over buttons") label.pack() - button1 = Button(root, text="Button 1") - button2 = Button(root, text="Button 2") + button1 = Button(top, text="Button 1") + button2 = Button(top, text="Button 2") button1.pack() button2.pack() ToolTip(button1, "This is tooltip text for button1.") ListboxToolTip(button2, ["This is","multiple line", "tooltip text","for button2"]) - root.mainloop() if __name__ == '__main__': from idlelib.idle_test.htest import run diff --git a/Lib/idlelib/TreeWidget.py b/Lib/idlelib/tree.py index a19578f..292ce36 100644 --- a/Lib/idlelib/TreeWidget.py +++ b/Lib/idlelib/tree.py @@ -15,10 +15,12 @@ # - optimize tree redraw after expand of subnode import os + from tkinter import * +from tkinter.ttk import Scrollbar -from idlelib import ZoomHeight -from idlelib.configHandler import idleConf +from idlelib.config import idleConf +from idlelib import zoomheight ICONDIR = "Icons" @@ -445,22 +447,21 @@ class ScrolledCanvas: self.canvas.yview_scroll(1, "unit") return "break" def zoom_height(self, event): - ZoomHeight.zoom_height(self.master) + zoomheight.zoom_height(self.master) return "break" -def _tree_widget(parent): - root = Tk() - root.title("Test TreeWidget") - width, height, x, y = list(map(int, re.split('[x+]', parent.geometry()))) - root.geometry("+%d+%d"%(x, y + 150)) - sc = ScrolledCanvas(root, bg="white", highlightthickness=0, takefocus=1) +def _tree_widget(parent): # htest # + top = Toplevel(parent) + x, y = map(int, parent.geometry().split('+')[1:]) + top.geometry("+%d+%d" % (x+50, y+175)) + sc = ScrolledCanvas(top, bg="white", highlightthickness=0, takefocus=1) sc.frame.pack(expand=1, fill="both", side=LEFT) - item = FileTreeItem(os.getcwd()) + item = FileTreeItem(ICONDIR) node = TreeNode(sc.canvas, None, item) node.expand() - root.mainloop() if __name__ == '__main__': + # test_tree is currently a copy of this from idlelib.idle_test.htest import run run(_tree_widget) diff --git a/Lib/idlelib/UndoDelegator.py b/Lib/idlelib/undo.py index 1c2502d..4332f10 100644 --- a/Lib/idlelib/UndoDelegator.py +++ b/Lib/idlelib/undo.py @@ -1,7 +1,9 @@ import string -from tkinter import * -from idlelib.Delegator import Delegator +from idlelib.delegator import Delegator + +# tkintter import not needed because module does not create widgets, +# although many methods operate on text widget arguments. #$ event <<redo>> #$ win <Control-y> @@ -158,7 +160,6 @@ class UndoDelegator(Delegator): class Command: - # Base class for Undoable commands tags = None @@ -204,7 +205,6 @@ class Command: class InsertCommand(Command): - # Undoable insert command def __init__(self, index1, chars, tags=None): @@ -262,7 +262,6 @@ class InsertCommand(Command): class DeleteCommand(Command): - # Undoable delete command def __init__(self, index1, index2=None): @@ -297,8 +296,8 @@ class DeleteCommand(Command): text.see('insert') ##sys.__stderr__.write("undo: %s\n" % self) -class CommandSequence(Command): +class CommandSequence(Command): # Wrapper for a sequence of undoable cmds to be undone/redone # as a unit @@ -338,13 +337,12 @@ class CommandSequence(Command): def _undo_delegator(parent): # htest # - import re - import tkinter as tk - from idlelib.Percolator import Percolator - undowin = tk.Toplevel() + from tkinter import Toplevel, Text, Button + from idlelib.percolator import Percolator + undowin = Toplevel(parent) undowin.title("Test UndoDelegator") - width, height, x, y = list(map(int, re.split('[x+]', parent.geometry()))) - undowin.geometry("+%d+%d"%(x, y + 150)) + x, y = map(int, parent.geometry().split('+')[1:]) + undowin.geometry("+%d+%d" % (x, y + 175)) text = Text(undowin, height=10) text.pack() @@ -362,7 +360,7 @@ def _undo_delegator(parent): # htest # if __name__ == "__main__": import unittest - unittest.main('idlelib.idle_test.test_undodelegator', verbosity=2, - exit=False) + unittest.main('idlelib.idle_test.test_undo', verbosity=2, exit=False) + from idlelib.idle_test.htest import run run(_undo_delegator) diff --git a/Lib/idlelib/WindowList.py b/Lib/idlelib/windows.py index bc74348..a3f858a 100644 --- a/Lib/idlelib/WindowList.py +++ b/Lib/idlelib/windows.py @@ -1,5 +1,6 @@ from tkinter import * + class WindowList: def __init__(self): @@ -48,6 +49,7 @@ class WindowList: t, v, tb = sys.exc_info() print("warning: callback failed in WindowList", t, ":", v) + registry = WindowList() add_windows_to_menu = registry.add_windows_to_menu diff --git a/Lib/idlelib/ZoomHeight.py b/Lib/idlelib/zoomheight.py index a5d679e..aa4a427 100644 --- a/Lib/idlelib/ZoomHeight.py +++ b/Lib/idlelib/zoomheight.py @@ -3,7 +3,8 @@ import re import sys -from idlelib import macosxSupport +from idlelib import macosx + class ZoomHeight: @@ -20,6 +21,7 @@ class ZoomHeight: top = self.editwin.top zoom_height(top) + def zoom_height(top): geom = top.wm_geometry() m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom) @@ -32,7 +34,7 @@ def zoom_height(top): newy = 0 newheight = newheight - 72 - elif macosxSupport.isAquaTk(): + elif macosx.isAquaTk(): # The '88' below is a magic number that avoids placing the bottom # of the window below the panel on my machine. I don't know how # to calculate the correct value for this with tkinter. |