python-3.6.zip added from Github

README.cosmo contains the necessary links.
This commit is contained in:
ahgamut 2021-08-08 09:38:33 +05:30 committed by Justine Tunney
parent 75fc601ff5
commit 0c4c56ff39
4219 changed files with 1968626 additions and 0 deletions

View file

@ -0,0 +1,37 @@
Guido van Rossum, as well as being the creator of the Python language, is the
original creator of IDLE. Other contributors prior to Version 0.8 include
Mark Hammond, Jeremy Hylton, Tim Peters, and Moshe Zadka.
IDLE's recent development was carried out in the SF IDLEfork project. The
objective was to develop a version of IDLE which had an execution environment
which could be initialized prior to each run of user code.
The IDLEfork project was initiated by David Scherer, with some help from Peter
Schneider-Kamp and Nicholas Riley. David wrote the first version of the RPC
code and designed a fast turn-around environment for VPython. Guido developed
the RPC code and Remote Debugger currently integrated in IDLE. Bruce Sherwood
contributed considerable time testing and suggesting improvements.
Besides David and Guido, the main developers who were active on IDLEfork
are Stephen M. Gava, who implemented the configuration GUI, the new
configuration system, and the About dialog, and Kurt B. Kaiser, who completed
the integration of the RPC and remote debugger, implemented the threaded
subprocess, and made a number of usability enhancements.
Other contributors include Raymond Hettinger, Tony Lownds (Mac integration),
Neal Norwitz (code check and clean-up), Ronald Oussoren (Mac integration),
Noam Raphael (Code Context, Call Tips, many other patches), and Chui Tey (RPC
integration, debugger integration and persistent breakpoints).
Scott David Daniels, Tal Einat, Hernan Foffani, Christos Georgiou,
Jim Jewett, Martin v. Löwis, Jason Orendorff, Guilherme Polo, Josh Robb,
Nigel Rowe, Bruce Sherwood, Jeff Shute, and Weeble have submitted useful
patches. Thanks, guys!
For additional details refer to NEWS.txt and Changelog.
Please contact the IDLE maintainer (kbk@shore.net) to have yourself included
here if you are one of those we missed!

1591
third_party/python/Lib/idlelib/ChangeLog vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,296 @@
IDLE History
============
This file contains the release messages for previous IDLE releases.
As you read on you go back to the dark ages of IDLE's history.
What's New in IDLEfork 0.8.1?
=============================
*Release date: 22-Jul-2001*
- New tarball released as a result of the 'revitalisation' of the IDLEfork
project.
- This release requires python 2.1 or better. Compatibility with earlier
versions of python (especially ancient ones like 1.5x) is no longer a
priority in IDLEfork development.
- This release is based on a merging of the earlier IDLE fork work with current
cvs IDLE (post IDLE version 0.8), with some minor additional coding by Kurt
B. Kaiser and Stephen M. Gava.
- This release is basically functional but also contains some known breakages,
particularly with running things from the shell window. Also the debugger is
not working, but I believe this was the case with the previous IDLE fork
release (0.7.1) as well.
- This release is being made now to mark the point at which IDLEfork is
launching into a new stage of development.
- IDLEfork CVS will now be branched to enable further development and
exploration of the two "execution in a remote process" patches submitted by
David Scherer (David's is currently in IDLEfork) and GvR, while stabilisation
and development of less heavyweight improvements (like user customisation)
can continue on the trunk.
What's New in IDLEfork 0.7.1?
==============================
*Release date: 15-Aug-2000*
- First project tarball released.
- This was the first release of IDLE fork, which at this stage was a
combination of IDLE 0.5 and the VPython idle fork, with additional changes
coded by David Scherer, Peter Schneider-Kamp and Nicholas Riley.
IDLEfork 0.7.1 - 29 May 2000
-----------------------------
David Scherer <dscherer@cmu.edu>
- This is a modification of the CVS version of IDLE 0.5, updated as of
2000-03-09. It is alpha software and might be unstable. If it breaks, you
get to keep both pieces.
- If you have problems or suggestions, you should either contact me or post to
the list at http://www.python.org/mailman/listinfo/idle-dev (making it clear
that you are using this modified version of IDLE).
- Changes:
- The ExecBinding module, a replacement for ScriptBinding, executes programs
in a separate process, piping standard I/O through an RPC mechanism to an
OnDemandOutputWindow in IDLE. It supports executing unnamed programs
(through a temporary file). It does not yet support debugging.
- When running programs with ExecBinding, tracebacks will be clipped to
exclude system modules. If, however, a system module calls back into the
user program, that part of the traceback will be shown.
- The OnDemandOutputWindow class has been improved. In particular, it now
supports a readline() function used to implement user input, and a
scroll_clear() operation which is used to hide the output of a previous run
by scrolling it out of the window.
- Startup behavior has been changed. By default IDLE starts up with just a
blank editor window, rather than an interactive window. Opening a file in
such a blank window replaces the (nonexistent) contents of that window
instead of creating another window. Because of the need to have a
well-known port for the ExecBinding protocol, only one copy of IDLE can be
running. Additional invocations use the RPC mechanism to report their
command line arguments to the copy already running.
- The menus have been reorganized. In particular, the excessively large
'edit' menu has been split up into 'edit', 'format', and 'run'.
- 'Python Documentation' now works on Windows, if the win32api module is
present.
- A few key bindings have been changed: F1 now loads Python Documentation
instead of the IDLE help; shift-TAB is now a synonym for unindent.
- New modules:
ExecBinding.py Executes program through loader
loader.py Bootstraps user program
protocol.py RPC protocol
Remote.py User-process interpreter
spawn.py OS-specific code to start programs
- Files modified:
autoindent.py ( bindings tweaked )
bindings.py ( menus reorganized )
config.txt ( execbinding enabled )
editorwindow.py ( new menus, fixed 'Python Documentation' )
filelist.py ( hook for "open in same window" )
formatparagraph.py ( bindings tweaked )
idle.bat ( removed absolute pathname )
idle.pyw ( weird bug due to import with same name? )
iobinding.py ( open in same window, EOL convention )
keydefs.py ( bindings tweaked )
outputwindow.py ( readline, scroll_clear, etc )
pyshell.py ( changed startup behavior )
readme.txt ( <Recursion on file with id=1234567> )
IDLE 0.5 - February 2000 - Release Notes
----------------------------------------
This is an early release of IDLE, my own attempt at a Tkinter-based
IDE for Python.
(For a more detailed change log, see the file ChangeLog.)
FEATURES
IDLE has the following features:
- coded in 100% pure Python, using the Tkinter GUI toolkit (i.e. Tcl/Tk)
- cross-platform: works on Windows and Unix (on the Mac, there are
currently problems with Tcl/Tk)
- multi-window text editor with multiple undo, Python colorizing
and many other features, e.g. smart indent and call tips
- Python shell window (a.k.a. interactive interpreter)
- debugger (not complete, but you can set breakpoints, view and step)
USAGE
The main program is in the file "idle.py"; on Unix, you should be able
to run it by typing "./idle.py" to your shell. On Windows, you can
run it by double-clicking it; you can use idle.pyw to avoid popping up
a DOS console. If you want to pass command line arguments on Windows,
use the batch file idle.bat.
Command line arguments: files passed on the command line are executed,
not opened for editing, unless you give the -e command line option.
Try "./idle.py -h" to see other command line options.
IDLE requires Python 1.5.2, so it is currently only usable with a
Python 1.5.2 distribution. (An older version of IDLE is distributed
with Python 1.5.2; you can drop this version on top of it.)
COPYRIGHT
IDLE is covered by the standard Python copyright notice
(http://www.python.org/doc/Copyright.html).
New in IDLE 0.5 (2/15/2000)
---------------------------
Tons of stuff, much of it contributed by Tim Peters and Mark Hammond:
- Status bar, displaying current line/column (Moshe Zadka).
- Better stack viewer, using tree widget. (XXX Only used by Stack
Viewer menu, not by the debugger.)
- Format paragraph now recognizes Python block comments and reformats
them correctly (MH)
- New version of pyclbr.py parses top-level functions and understands
much more of Python's syntax; this is reflected in the class and path
browsers (TP)
- Much better auto-indent; knows how to indent the insides of
multi-line statements (TP)
- Call tip window pops up when you type the name of a known function
followed by an open parenthesis. Hit ESC or click elsewhere in the
window to close the tip window (MH)
- Comment out region now inserts ## to make it stand out more (TP)
- New path and class browsers based on a tree widget that looks
familiar to Windows users
- Reworked script running commands to be more intuitive: I/O now
always goes to the *Python Shell* window, and raw_input() works
correctly. You use F5 to import/reload a module: this adds the module
name to the __main__ namespace. You use Control-F5 to run a script:
this runs the script *in* the __main__ namespace. The latter also
sets sys.argv[] to the script name
New in IDLE 0.4 (4/7/99)
------------------------
Most important change: a new menu entry "File -> Path browser", shows
a 4-column hierarchical browser which lets you browse sys.path,
directories, modules, and classes. Yes, it's a superset of the Class
browser menu entry. There's also a new internal module,
MultiScrolledLists.py, which provides the framework for this dialog.
New in IDLE 0.3 (2/17/99)
-------------------------
Most important changes:
- Enabled support for running a module, with or without the debugger.
Output goes to a new window. Pressing F5 in a module is effectively a
reload of that module; Control-F5 loads it under the debugger.
- Re-enable tearing off the Windows menu, and make a torn-off Windows
menu update itself whenever a window is opened or closed.
- Menu items can now be have a checkbox (when the menu label starts
with "!"); use this for the Debugger and "Auto-open stack viewer"
(was: JIT stack viewer) menu items.
- Added a Quit button to the Debugger API.
- The current directory is explicitly inserted into sys.path.
- Fix the debugger (when using Python 1.5.2b2) to use canonical
filenames for breakpoints, so these actually work. (There's still a
lot of work to be done to the management of breakpoints in the
debugger though.)
- Closing a window that is still colorizing now actually works.
- Allow dragging of the separator between the two list boxes in the
class browser.
- Bind ESC to "close window" of the debugger, stack viewer and class
browser. It removes the selection highlighting in regular text
windows. (These are standard Windows conventions.)
New in IDLE 0.2 (1/8/99)
------------------------
Lots of changes; here are the highlights:
General:
- You can now write and configure your own IDLE extension modules; see
extend.txt.
File menu:
The command to open the Python shell window is now in the File menu.
Edit menu:
New Find dialog with more options; replace dialog; find in files dialog.
Commands to tabify or untabify a region.
Command to format a paragraph.
Debug menu:
JIT (Just-In-Time) stack viewer toggle -- if set, the stack viewer
automaticall pops up when you get a traceback.
Windows menu:
Zoom height -- make the window full height.
Help menu:
The help text now show up in a regular window so you can search and
even edit it if you like.
IDLE 0.1 was distributed with the Python 1.5.2b1 release on 12/22/98.
======================================================================

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 585 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 B

958
third_party/python/Lib/idlelib/NEWS.txt vendored Normal file
View file

@ -0,0 +1,958 @@
What's New in IDLE 3.6.8
Released on 2018-12-20?
**This is the final 3.6 bugfix maintenance release**
======================================
bpo-34864: When starting IDLE on MacOS, warn if the system setting
"Prefer tabs when opening documents" is "Always". As previous
documented for this issue, running IDLE with this setting causes
problems. If the setting is changed while IDLE is running,
there will be no warning until IDLE is restarted.
bpo-35213: Where appropriate, use 'macOS' in idlelib.
bpo-34864: Document two IDLE on MacOS issues. The System Preferences
Dock "prefer tabs always" setting disables some IDLE features.
Menus are a bit different than as described for Windows and Linux.
bpo-35202: Remove unused imports in idlelib.
bpo-33000: Document that IDLE's shell has no line limit.
A program that runs indefinitely can overfill memory.
bpo-23220: Explain how IDLE's Shell displays output.
Add new subsection "User output in Shell".
bpo-35099: Improve the doc about IDLE running user code.
"IDLE -- console differences" is renamed "Running user code".
It mostly covers the implications of using custom sys.stdxxx objects.
bpo-35097: Add IDLE doc subsection explaining editor windows.
Topics include opening, title and status bars, .py* extension, and running.
Issue 35093: Document the IDLE document viewer in the IDLE doc.
Add a paragraph in "Help and preferences", "Help sources" subsection.
bpo-1529353: Explain Shell text squeezing in the IDLE doc.
bpo-35088: Update idlelib.help.copy_string docstring.
We now use git and backporting instead of hg and forward merging.
bpo-35087: Update idlelib help files for the current doc build.
The main change is the elimination of chapter-section numbers.
What's New in IDLE 3.6.7
Released on 2018-10-20
======================================
bpo-1529353: Output over N lines (50 by default) is squeezed down to a button.
N can be changed in the PyShell section of the General page of the
Settings dialog. Fewer, but possibly extra long, lines can be squeezed by
right clicking on the output. Squeezed output can be expanded in place
by double-clicking the button or into the clipboard or a separate window
by right-clicking the button.
bpo-34548: Use configured color theme for read-only text views.
bpo-33839: Refactor ToolTip and CallTip classes; add documentation
and tests.
bpo-34047: Fix mouse wheel scrolling direction on macOS.
bpo-34275: Make calltips always visible on Mac.
Patch by Kevin Walzer.
bpo-34120: Fix freezing after closing some dialogs on Mac.
This is one of multiple regressions from using newer tcl/tk.
bpo-33975: Avoid small type when running htests.
Since part of the purpose of human-viewed tests is to determine that
widgets look right, it is important that they look the same for
testing as when running IDLE.
bpo-33905: Add test for idlelib.stackview.StackBrowser.
bpo-33924: Change mainmenu.menudefs key 'windows' to 'window'.
Every other menudef key is the lowercase version of the
corresponding main menu entry (in this case, 'Window').
bpo-33906: Rename idlelib.windows as window
Match Window on the main menu and remove last plural module name.
Change imports, test, and attribute references to match new name.
bpo-33917: Fix and document idlelib/idle_test/template.py.
The revised file compiles, runs, and tests OK. idle_test/README.txt
explains how to use it to create new IDLE test files.
bpo-33904: In rstrip module, rename class RstripExtension as Rstrip.
bpo-33907: For consistency and clarity, rename calltip objects.
Module calltips and its class CallTips are now calltip and Calltip.
In module calltip_w, class CallTip is now CalltipWindow.
bpo-33855: Minimally test all IDLE modules.
Standardize the test file format. Add missing test files that import
the tested module and perform at least one test. Check and record the
coverage of each test.
bpo-33856: Add 'help' to Shell's initial welcome message.
What's New in IDLE 3.6.6
Released on 2018-06-27
======================================
bpo-33656: On Windows, add API call saying that tk scales for DPI.
On Windows 8.1+ or 10, with DPI compatibility properties of the Python
binary unchanged, and a monitor resolution greater than 96 DPI, this
should make text and lines sharper and some colors brighter.
On other systems, it should have no effect. If you have a custom theme,
you may want to adjust a color or two. If perchance it make text worse
on your monitor, you can disable the ctypes.OleDLL call near the top of
pyshell.py and report the problem on python-list or idle-dev@python.org.
bpo-33768: Clicking on a context line moves that line to the top
of the editor window.
bpo-33763: Replace the code context label widget with a text widget.
bpo-33664: Scroll IDLE editor text by lines.
(Previously, the mouse wheel and scrollbar slider moved text by a fixed
number of pixels, resulting in partial lines at the top of the editor
box.) This change also applies to the shell and grep output windows,
but currently not to read-only text views.
bpo-33679: Enable theme-specific color configuration for Code Context.
(Previously, there was one code context foreground and background font
color setting, default or custom, on the extensions tab, that applied
to all themes.) For built-in themes, the foreground is the same as
normal text and the background is a contrasting gray. Context colors for
custom themes are set on the Hightlights tab along with other colors.
When one starts IDLE from a console and loads a custom theme without
definitions for 'context', one will see a warning message on the
console.
bpo-33642: Display up to maxlines non-blank lines for Code Context.
If there is no current context, show a single blank line. (Previously,
the Code Contex had numlines lines, usually with some blank.) The use
of a new option, 'maxlines' (default 15), avoids possible interference
with user settings of the old option, 'numlines' (default 3).
bpo-33628: Cleanup codecontext.py and its test.
bpo-32831: Add docstrings and tests for codecontext.py.
Coverage is 100%. Patch by Cheryl Sabella.
bpo-33564: Code context now recognizes async as a block opener.
bpo-29706: IDLE now colors async and await as keywords in 3.6.
They become full keywords in 3.7.
bpo-21474: Update word/identifier definition from ascii to unicode.
In text and entry boxes, this affects selection by double-click,
movement left/right by control-left/right, and deletion left/right
by control-BACKSPACE/DEL.
bpo-33204: Consistently color invalid string prefixes.
A 'u' string prefix cannot be paired with either 'r' or 'f'.
IDLE now consistently colors as much of the prefix, starting at the
right, as is valid. Revise and extend colorizer test.
What's New in IDLE 3.6.5
Released on 2018-03-28
======================================
bpo-32984: Set __file__ while running a startup file.
Like Python, IDLE optionally runs 1 startup file in the Shell window
before presenting the first interactive input prompt. For IDLE,
option -s runs a file named in environmental variable IDLESTARTUP or
PYTHONSTARTUP; -r file runs file. Python sets __file__ to the startup
file name before running the file and unsets it before the first
prompt. IDLE now does the same when run normally, without the -n
option.
bpo-32940: Replace StringTranslatePseudoMapping with faster code.
bpo-32916: Change 'str' to 'code' in idlelib.pyparse and users.
bpo-32905: Remove unused code in pyparse module.
bpo-32874: IDLE - add pyparse tests with 97% coverage.
bpo-32837: IDLE - require encoding argument for textview.view_file.
Using the system and place-dependent default encoding for open()
is a bad idea for IDLE's system and location-independent files.
bpo-32826: Add "encoding=utf-8" to open() in IDLE's test_help_about.
GUI test test_file_buttons() only looks at initial ascii-only lines,
but failed on systems where open() defaults to 'ascii' because
readline() internally reads and decodes far enough ahead to encounter
a non-ascii character in CREDITS.txt.
bpo-32765: Update configdialog General tab create page docstring.
Add new widgets to the widget list.
What's New in IDLE 3.6.4
Released on 2017-12-19
========================
bpo-32207: Improve tk event exception tracebacks in IDLE.
When tk event handling is driven by IDLE's run loop, a confusing
and distracting queue.EMPTY traceback context is no longer added
to tk event exception tracebacks. The traceback is now the same
as when event handling is driven by user code. Patch based on
a suggestion by Serhiy Storchaka.
bpo-32164: Delete unused file idlelib/tabbedpages.py.
Use of TabbedPageSet in configdialog was replaced by ttk.Notebook.
bpo-32100: Fix old and new bugs in pathbrowser; improve tests.
Patch mostly by Cheryl Sabella.
bpo-31860: The font sample in the settings dialog is now editable.
Edits persist while IDLE remains open.
Patch by Serhiy Storchake and Terry Jan Reedy.
bpo-31858: Restrict shell prompt manipulation to the shell.
Editor and output windows only see an empty last prompt line. This
simplifies the code and fixes a minor bug when newline is inserted.
Sys.ps1, if present, is read on Shell start-up, but is not set or changed.
Patch by Terry Jan Reedy.
bpo-28603: Fix a TypeError that caused a shell restart when printing
a traceback that includes an exception that is unhashable.
Patch by Zane Bitter.
bpo-13802: Use non-Latin characters in the Font settings sample.
Even if one selects a font that defines a limited subset of the unicode
Basic Multilingual Plane, tcl/tk will use other fonts that define a
character. The expanded example give users of non-Latin characters
a better idea of what they might see in the shell and editors.
To make room for the expanded sample, frames on the Font tab are
re-arranged. The Font/Tabs help explains a bit about the additions.
Patch by Terry Jan Reedy
What's New in IDLE 3.6.3
Released on 2017-10-03
========================
bpo-31460: Simplify the API of IDLE's Module Browser.
Passing a widget instead of an flist with a root widget opens the
option of creating a browser frame that is only part of a window.
Passing a full file name instead of pieces assumed to come from a
.py file opens the possibility of browsing python files that do not
end in .py.
bpo-31649: Make _htest and _utest parameters keyword-only.
These are used to adjust code for human and unit tests.
bpo-31459: Rename module browser from Class Browser to Module Browser.
The original module-level class and method browser became a module
browser, with the addition of module-level functions, years ago.
Nested classes and functions were added yesterday. For back-
compatibility, the virtual event <<open-class-browser>>, which
appears on the Keys tab of the Settings dialog, is not changed.
Patch by Cheryl Sabella.
bpo-1612262: Module browser now shows nested classes and functions.
Original patches for code and tests by Guilherme Polo and
Cheryl Sabella, respectively. Revisions by Terry Jan Reedy.
bpo-31500: Tk's default fonts now are scaled on HiDPI displays.
This affects all dialogs. Patch by Serhiy Storchaka.
bpo-31493: Fix code context update and font update timers.
Canceling timers prevents a warning message when test_idle completes.
bpo-31488: Update non-key options in former extension classes.
When applying configdialog changes, call .reload for each feature class.
Change ParenMatch so updated options affect existing instances attached
to existing editor windows.
bpo-31477: Improve rstrip entry in IDLE doc.
Strip Trailing Whitespace strips more than blank spaces.
Multiline string literals are not skipped.
bpo-31480: fix tests to pass with zzdummy extension disabled. (#3590)
To see the example in action, enable it on options extensions tab.
bpo-31421: Document how IDLE runs tkinter programs.
IDLE calls tcl/tk update in the background in order to make live
interaction and experimentation with tkinter applications much easier.
bpo-31414: Fix tk entry box tests by deleting first.
Adding to an int entry is not the same as deleting and inserting
because int('') will fail. Patch by Terry Jan Reedy.
bpo-27099: Convert IDLE's built-in 'extensions' to regular features.
About 10 IDLE features were implemented as supposedly optional
extensions. Their different behavior could be confusing or worse for
users and not good for maintenance. Hence the conversion.
The main difference for users is that user configurable key bindings
for builtin features are now handled uniformly. Now, editing a binding
in a keyset only affects its value in the keyset. All bindings are
defined together in the system-specific default keysets in config-
extensions.def. All custom keysets are saved as a whole in config-
extension.cfg. All take effect as soon as one clicks Apply or Ok.
The affected events are '<<force-open-completions>>',
'<<expand-word>>', '<<force-open-calltip>>', '<<flash-paren>>',
'<<format-paragraph>>', '<<run-module>>', '<<check-module>>', and
'<<zoom-height>>'. Any (global) customizations made before 3.6.3 will
not affect their keyset-specific customization after 3.6.3. and vice
versa.
Initial patch by Charles Wohlganger, revised by Terry Jan Reedy.
bpo-31051: Rearrange condigdialog General tab.
Sort non-Help options into Window (Shell+Editor) and Editor (only).
Leave room for the addition of new options.
Patch by Terry Jan Reedy.
bpo-30617: Add docstrings and tests for outwin subclass of editor.
Move some data and functions from the class to module level.
Patch by Cheryl Sabella.
bpo-31287: Do not modify tkinter.messagebox in test_configdialog.
Instead, mask it with an instance mock that can be deleted.
Patch by Terry Jan Reedy.
bpo-30781: Use ttk widgets in ConfigDialog pages.
These should especially look better on MacOSX.
Patches by Terry Jan Reedy and Cheryl Sabella.
bpo-31206: Factor HighPage(Frame) class from ConfigDialog.
Patch by Cheryl Sabella.
bp0-31001: Add tests for configdialog highlight tab.
Patch by Cheryl Sabella.
bpo-31205: Factor KeysPage(Frame) class from ConfigDialog.
The slightly modified tests continue to pass.
Patch by Cheryl Sabella.
bpo-31002: Add tests for configdialog keys tab.
Patch by Cheryl Sabella.
bpo-19903: Change calltipes to use inspect.signature.
Idlelib.calltips.get_argspec now uses inspect.signature instead of
inspect.getfullargspec, like help() does. This improves the signature
in the call tip in a few different cases, including builtins converted
to provide a signature. A message is added if the object is not
callable, has an invalid signature, or if it has positional-only
parameters. Patch by Louie Lu.
bop-31083: Add an outline of a TabPage class in configdialog.
Add template as comment. Update existing classes to match outline.
Initial patch by Cheryl Sabella.
bpo-31050: Factor GenPage(Frame) class from ConfigDialog.
The slightly modified tests for the General tab continue to pass.
Patch by Cheryl Sabella.
bpo-31004: Factor FontPage(Frame) class from ConfigDialog.
The slightly modified tests continue to pass. The General test
broken by the switch to ttk.Notebook is fixed.
Patch mostly by Cheryl Sabella.
bpo-30781: IDLE - Use ttk Notebook in ConfigDialog.
This improves navigation by tabbing.
Patch by Terry Jan Reedy.
bpo-31060: IDLE - Finish rearranging methods of ConfigDialog.
Grouping methods pertaining to each tab and the buttons will aid
writing tests and improving the tabs and will enable splitting the
groups into classes.
Patch by Terry Jan Reedy.
bpo-30853: IDLE -- Factor a VarTrace class out of ConfigDialog.
Instance tracers manages pairs consisting of a tk variable and a
callback function. When tracing is turned on, setting the variable
calls the function. Test coverage for the new class is 100%.
Patch by Terry Jan Reedy.
bpo-31003: IDLE: Add more tests for General tab.
Patch by Terry Jan Reedy.
bpo-30993: IDLE - Improve configdialog font page and tests.
*In configdialog: Document causal pathways in create_font_tab
docstring. Simplify some attribute names. Move set_samples calls to
var_changed_font (idea from Cheryl Sabella). Move related functions to
positions after the create widgets function.
* In test_configdialog: Fix test_font_set so not order dependent. Fix
renamed test_indent_scale so it tests the widget. Adjust tests for
movement of set_samples call. Add tests for load functions. Put all
font tests in one class and tab indent tests in another. Except for
two lines, these tests completely cover the related functions.
Patch by Terry Jan Reedy.
bpo-30981: IDLE -- Add more configdialog font page tests.
bpo-28523: IDLE: replace 'colour' with 'color' in configdialog.
bpo-30917: Add tests for idlelib.config.IdleConf.
Increase coverage from 46% to 96%.
Patch by Louie Lu.
bpo-30913: Document ConfigDialog tk Vars, methods, and widgets in docstrings
This will facilitate improving the dialog and splitting up the class.
Original patch by Cheryl Sabella.
bpo-30899: Add tests for ConfigParser subclasses in config.
Coverage is 100% for those classes and ConfigChanges.
Patch by Louie Lu.
bpo-30881: Add docstrings to browser.py.
Patch by Cheryl Sabella.
bpo-30851: Remove unused tk variables in configdialog.
One is a duplicate, one is set but cannot be altered by users.
Patch by Cheryl Sabella.
bpo-30870: Select font option with Up and Down keys, as well as with mouse.
Added test increases configdialog coverage to 60%
Patches mostly by Louie Lu.
bpo-8231: Call config.IdleConf.GetUserCfgDir only once per process.
bpo-30779: Factor ConfigChanges class from configdialog, put in config; test.
* In config, put dump test code in a function; run it and unittest in
'if __name__ == '__main__'.
* Add class config.ConfigChanges based on changes_class_v4.py on bpo issue.
* Add class test_config.ChangesTest, partly using configdialog_tests_v1.py.
* Revise configdialog to use ConfigChanges; see tracker msg297804.
* Revise test_configdialog to match configdialog changes.
* Remove configdialog functions unused or moved to ConfigChanges.
Cheryl Sabella contributed parts of the patch.
bpo-30777: Configdialog - add docstrings and improve comments.
Patch by Cheryl Sabella.
bpo-30495: Improve textview with docstrings, PEP8 names, and more tests.
Split TextViewer class into ViewWindow, ViewFrame, and TextFrame classes
so that instances of the latter two can be placed with other widgets
within a multiframe window.
Patches by Cheryl Sabella and Terry Jan Reedy.
bpo-30723: Make several improvements to parenmatch.
* Add 'parens' style to highlight both opener and closer.
* Make 'default' style, which is not default, a synonym for 'opener'.
* Make time-delay work the same with all styles.
* Add help for config dialog extensions tab, including parenmatch.
* Add new tests.
Original patch by Charles Wohlganger. Revisions by Terry Jan Reedy
bpo-30674: Grep -- Add docstrings. Patch by Cheryl Sabella.
bpo-21519: IDLE's basic custom key entry dialog now detects
duplicates properly. Original patch by Saimadhav Heblikar.
bpo-29910: IDLE no longer deletes a character after commenting out a
region by a key shortcut. Add "return 'break'" for this and other
potential conflicts between IDLE and default key bindings.
Patch by Serhiy Storchaka.
bpo-30728: Modernize idlelib.configdialog:
* replace import * with specific imports;
* lowercase method and attribute lines.
Patch by Cheryl Sabella.
bpo-6739: Verify user-entered key sequences by trying to bind them
with to a tk widget. Add tests for all 3 validation functions.
Original patch by G Polo. Tests added by Cheryl Sabella.
Code revised and more tests added by Terry Jan Reedy
bpo-24813: Add icon to help_about and make other changes.
What's New in IDLE 3.6.2
Released on 2017-07-17
========================
bpo-15786: Fix several problems with IDLE's autocompletion box.
The following should now work: clicking on selection box items;
using the scrollbar; selecting an item by hitting Return.
Hangs on MacOSX should no longer happen. Patch by Louie Lu.
bpo-25514: Add doc subsubsection about IDLE failure to start.
Popup no-connection message directs users to this section.
bpo-30642: Fix reference leaks in IDLE tests.
Patches by Louie Lu and Terry Jan Reedy.
bpo-30495: Add docstrings for textview.py and use PEP8 names.
Patches by Cheryl Sabella and Terry Jan Reedy.
bpo-30290: Help-about: use pep8 names and add tests.
Increase coverage to 100%.
Patches by Louie Lu, Cheryl Sabella, and Terry Jan Reedy.
bpo-30303: Add _utest option to textview; add new tests.
Increase coverage to 100%.
Patches by Louie Lu and Terry Jan Reedy.
What's New in IDLE 3.6.1
Released on 2017-03-21
========================
Issue #29071: IDLE colors f-string prefixes but not invalid ur prefixes.
Issue #28572: Add 10% to coverage of IDLE's test_configdialog.
Update and augment description of the configuration system.
What's New in IDLE 3.6.0 (since 3.5.0)
Released on 2016-12-23
======================================
- 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 messagebox. 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.
- 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 macosx.
- 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 '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
- 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.
- Issue #21939: Add test for IDLE's percolator.
Original patch by Saimadhav Heblikar.
- Issue #21676: Add test for IDLE's replace dialog.
Original patch by Saimadhav Heblikar.
- Issue #18410: Add test for IDLE's search dialog.
Original patch by Westley Martínez.
- Issue #21703: Add test for undo delegator. Patch mostly by
Saimadhav Heblikar .
- Issue #27044: Add ConfigDialog.remove_var_callbacks to stop memory leaks.
- Issue #23977: Add more asserts to test_delegator.
- Issue #20640: Add tests for idlelib.configHelpSourceEdit.
Patch by Saimadhav Heblikar.
- In the 'IDLE-console differences' section of the IDLE doc, clarify
how running with IDLE affects sys.modules and the standard streams.
- Issue #25507: fix incorrect change in IOBinding that prevented printing.
Augment IOBinding htest to include all major IOBinding functions.
- Issue #25905: Revert unwanted conversion of ' to RIGHT SINGLE QUOTATION
MARK in README.txt and open this and NEWS.txt with 'ascii'.
Re-encode CREDITS.txt to utf-8 and open it with 'utf-8'.
- 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.
- Issue #24455: Prevent IDLE from hanging when a) closing the shell while the
debugger is active (15347); b) closing the debugger with the [X] button
(15348); and c) activating the debugger when already active (24455).
The patch by Mark Roseman does this by making two changes.
1. Suspend and resume the gui.interaction method with the tcl vwait
mechanism intended for this purpose (instead of root.mainloop & .quit).
2. In gui.run, allow any existing interaction to terminate first.
- Change 'The program' to 'Your program' in an IDLE 'kill program?' message
to make it clearer that the program referred to is the currently running
user program, not IDLE itself.
- Issue #24750: Improve the appearance of the IDLE editor window status bar.
Patch by Mark Roseman.
- Issue #25313: Change the handling of new built-in text color themes to better
address the compatibility problem introduced by the addition of IDLE Dark.
Consistently use the revised idleConf.CurrentTheme everywhere in idlelib.
- Issue #24782: Extension configuration is now a tab in the IDLE Preferences
dialog rather than a separate dialog. The former tabs are now a sorted
list. Patch by Mark Roseman.
- Issue #22726: Re-activate the config dialog help button with some content
about the other buttons and the new IDLE Dark theme.
- Issue #24820: IDLE now has an 'IDLE Dark' built-in text color theme.
It is more or less IDLE Classic inverted, with a cobalt blue background.
Strings, comments, keywords, ... are still green, red, orange, ... .
To use it with IDLEs released before November 2015, hit the
'Save as New Custom Theme' button and enter a new name,
such as 'Custom Dark'. The custom theme will work with any IDLE
release, and can be modified.
- Issue #25224: README.txt is now an idlelib index for IDLE developers and
curious users. The previous user content is now in the IDLE doc chapter.
'IDLE' now means 'Integrated Development and Learning Environment'.
- Issue #24820: Users can now set breakpoint colors in
Settings -> Custom Highlighting. Original patch by Mark Roseman.
- Issue #24972: Inactive selection background now matches active selection
background, as configured by users, on all systems. Found items are now
always highlighted on Windows. Initial patch by Mark Roseman.
- Issue #24570: Idle: make calltip and completion boxes appear on Macs
affected by a tk regression. Initial patch by Mark Roseman.
- Issue #24988: Idle ScrolledList context menus (used in debugger)
now work on Mac Aqua. Patch by Mark Roseman.
- Issue #24801: Make right-click for context menu work on Mac Aqua.
Patch by Mark Roseman.
- Issue #25173: Associate tkinter messageboxes with a specific widget.
For Mac OSX, make them a 'sheet'. Patch by Mark Roseman.
- Issue #25198: Enhance the initial html viewer now used for Idle Help.
* Properly indent fixed-pitch text (patch by Mark Roseman).
* Give code snippet a very Sphinx-like light blueish-gray background.
* Re-use initial width and height set by users for shell and editor.
* When the Table of Contents (TOC) menu is used, put the section header
at the top of the screen.
- Issue #25225: Condense and rewrite Idle doc section on text colors.
- Issue #21995: Explain some differences between IDLE and console Python.
- Issue #22820: Explain need for *print* when running file from Idle editor.
- Issue #25224: Doc: augment Idle feature list and no-subprocess section.
- Issue #25219: Update doc for Idle command line options.
Some were missing and notes were not correct.
- Issue #24861: Most of idlelib is private and subject to change.
Use idleib.idle.* to start Idle. See idlelib.__init__.__doc__.
- Issue #25199: Idle: add synchronization comments for future maintainers.
- Issue #16893: Replace help.txt with help.html for Idle doc display.
The new idlelib/help.html is rstripped Doc/build/html/library/idle.html.
It looks better than help.txt and will better document Idle as released.
The tkinter html viewer that works for this file was written by Mark Roseman.
The now unused EditorWindow.HelpDialog class and helt.txt file are deprecated.
- Issue #24199: Deprecate unused idlelib.idlever with possible removal in 3.6.
- Issue #24790: Remove extraneous code (which also create 2 & 3 conflicts).
What's New in IDLE 3.5.0?
=========================
*Release date: 2015-09-13*
- Issue #23672: Allow Idle to edit and run files with astral chars in name.
Patch by Mohd Sanad Zaki Rizvi.
- Issue 24745: Idle editor default font. Switch from Courier to
platform-sensitive TkFixedFont. This should not affect current customized
font selections. If there is a problem, edit $HOME/.idlerc/config-main.cfg
and remove 'fontxxx' entries from [Editor Window]. Patch by Mark Roseman.
- Issue #21192: Idle editor. When a file is run, put its name in the restart bar.
Do not print false prompts. Original patch by Adnan Umer.
- Issue #13884: Idle menus. Remove tearoff lines. Patch by Roger Serwy.
- Issue #23184: remove unused names and imports in idlelib.
Initial patch by Al Sweigart.
- Issue #20577: Configuration of the max line length for the FormatParagraph
extension has been moved from the General tab of the Idle preferences dialog
to the FormatParagraph tab of the Config Extensions dialog.
Patch by Tal Einat.
- Issue #16893: Update Idle doc chapter to match current Idle and add new
information.
- Issue #3068: Add Idle extension configuration dialog to Options menu.
Changes are written to HOME/.idlerc/config-extensions.cfg.
Original patch by Tal Einat.
- Issue #16233: A module browser (File : Class Browser, Alt+C) requires an
editor window with a filename. When Class Browser is requested otherwise,
from a shell, output window, or 'Untitled' editor, Idle no longer displays
an error box. It now pops up an Open Module box (Alt+M). If a valid name
is entered and a module is opened, a corresponding browser is also opened.
- Issue #4832: Save As to type Python files automatically adds .py to the
name you enter (even if your system does not display it). Some systems
automatically add .txt when type is Text files.
- Issue #21986: Code objects are not normally pickled by the pickle module.
To match this, they are no longer pickled when running under Idle.
- Issue #23180: Rename IDLE "Windows" menu item to "Window".
Patch by Al Sweigart.
- Issue #17390: Adjust Editor window title; remove 'Python',
move version to end.
- Issue #14105: Idle debugger breakpoints no longer disappear
when inserting or deleting lines.
- Issue #17172: Turtledemo can now be run from Idle.
Currently, the entry is on the Help menu, but it may move to Run.
Patch by Ramchandra Apt and Lita Cho.
- Issue #21765: Add support for non-ascii identifiers to HyperParser.
- Issue #21940: Add unittest for WidgetRedirector. Initial patch by Saimadhav
Heblikar.
- Issue #18592: Add unittest for SearchDialogBase. Patch by Phil Webster.
- Issue #21694: Add unittest for ParenMatch. Patch by Saimadhav Heblikar.
- Issue #21686: add unittest for HyperParser. Original patch by Saimadhav
Heblikar.
- Issue #12387: Add missing upper(lower)case versions of default Windows key
bindings for Idle so Caps Lock does not disable them. Patch by Roger Serwy.
- Issue #21695: Closing a Find-in-files output window while the search is
still in progress no longer closes Idle.
- Issue #18910: Add unittest for textView. Patch by Phil Webster.
- Issue #18292: Add unittest for AutoExpand. Patch by Saihadhav Heblikar.
- Issue #18409: Add unittest for AutoComplete. Patch by Phil Webster.
- Issue #21477: htest.py - Improve framework, complete set of tests.
Patches by Saimadhav Heblikar
- Issue #18104: Add idlelib/idle_test/htest.py with a few sample tests to begin
consolidating and improving human-validated tests of Idle. Change other files
as needed to work with htest. Running the module as __main__ runs all tests.
- Issue #21139: Change default paragraph width to 72, the PEP 8 recommendation.
- Issue #21284: Paragraph reformat test passes after user changes reformat width.
- Issue #17654: Ensure IDLE menus are customized properly on OS X for
non-framework builds and for all variants of Tk.
What's New in IDLE 3.4.0?
=========================
*Release date: 2014-03-16*
- Issue #17390: Display Python version on Idle title bar.
Initial patch by Edmond Burnett.
- Issue #5066: Update IDLE docs. Patch by Todd Rovito.
- Issue #17625: Close the replace dialog after it is used.
- Issue #16226: Fix IDLE Path Browser crash.
(Patch by Roger Serwy)
- Issue #15853: Prevent IDLE crash on OS X when opening Preferences menu
with certain versions of Tk 8.5. Initial patch by Kevin Walzer.
What's New in IDLE 3.3.0?
=========================
*Release date: 2012-09-29*
- Issue #17625: Close the replace dialog after it is used.
- Issue #7163: Propagate return value of sys.stdout.write.
- Issue #15318: Prevent writing to sys.stdin.
- Issue #4832: Modify IDLE to save files with .py extension by
default on Windows and OS X (Tk 8.5) as it already does with X11 Tk.
- Issue #13532, #15319: Check that arguments to sys.stdout.write are strings.
- Issue # 12510: Attempt to get certain tool tips no longer crashes IDLE.
Erroneous tool tips have been corrected. Default added for callables.
- Issue #10365: File open dialog now works instead of crashing even when
parent window is closed while dialog is open.
- Issue 14876: use user-selected font for highlight configuration.
- Issue #14937: Perform auto-completion of filenames in strings even for
non-ASCII filenames. Likewise for identifiers.
- Issue #8515: Set __file__ when run file in IDLE.
Initial patch by Bruce Frederiksen.
- IDLE can be launched as `python -m idlelib`
- Issue #14409: IDLE now properly executes commands in the Shell window
when it cannot read the normal config files on startup and
has to use the built-in default key bindings.
There was previously a bug in one of the defaults.
- Issue #3573: IDLE hangs when passing invalid command line args
(directory(ies) instead of file(s)).
- Issue #14018: Update checks for unstable system Tcl/Tk versions on OS X
to include versions shipped with OS X 10.7 and 10.8 in addition to 10.6.
What's New in IDLE 3.2.1?
=========================
*Release date: 15-May-11*
- Issue #6378: Further adjust idle.bat to start associated Python
- Issue #11896: Save on Close failed despite selecting "Yes" in dialog.
- Issue #1028: Ctrl-space binding to show completions was causing IDLE to exit.
Tk < 8.5 was sending invalid Unicode null; replaced with valid null.
- Issue #4676: <Home> toggle failing on Tk 8.5, causing IDLE exits and strange selection
behavior. Improve selection extension behaviour.
- Issue #3851: <Home> toggle non-functional when NumLock set on Windows.
What's New in IDLE 3.1b1?
=========================
*Release date: 06-May-09*
- Issue #5707: Use of 'filter' in keybindingDialog.py was causing custom key assignment to
fail. Patch by Amaury Forgeot d'Arc.
- Issue #4815: Offer conversion to UTF-8 if source files have
no encoding declaration and are not encoded in UTF-8.
- Issue #4008: Fix problems with non-ASCII source files.
- Issue #4323: Always encode source as UTF-8 without asking
the user (unless a different encoding is declared); remove
user configuration of source encoding; all according to
PEP 3120.
- Issue #2665: On Windows, an IDLE installation upgraded from an old version
would not start if a custom theme was defined.
------------------------------------------------------------------------
Refer to NEWS2x.txt and HISTORY.txt for information on earlier releases.
------------------------------------------------------------------------

View file

@ -0,0 +1,660 @@
What's New in IDLE 2.7? (Merged into 3.1 before 2.7 release.)
=======================
*Release date: XX-XXX-2010*
- idle.py modified and simplified to better support developing experimental
versions of IDLE which are not installed in the standard location.
- OutputWindow/PyShell right click menu "Go to file/line" wasn't working with
file paths containing spaces. Bug 5559.
- Windows: Version string for the .chm help file changed, file not being
accessed Patch 5783 Guilherme Polo
- Allow multiple IDLE GUI/subprocess pairs to exist simultaneously. Thanks to
David Scherer for suggesting the use of an ephemeral port for the GUI.
Patch 1529142 Weeble.
- Remove port spec from run.py and fix bug where subprocess fails to
extract port from command line when warnings are present.
- Tk 8.5 Text widget requires 'wordprocessor' tabstyle attr to handle
mixed space/tab properly. Issue 5129, patch by Guilherme Polo.
- Issue #3549: On MacOS the preferences menu was not present
- IDLE would print a "Unhandled server exception!" message when internal
debugging is enabled.
- Issue #4455: IDLE failed to display the windows list when two windows have
the same title.
- Issue #4383: When IDLE cannot make the connection to its subprocess, it would
fail to properly display the error message.
- help() was not paging to the shell. Issue1650.
- CodeContext was not importing.
- Corrected two 3.0 compatibility errors reported by Mark Summerfield:
http://mail.python.org/pipermail/python-3000/2007-December/011491.html
- Shell was not colorizing due to bug introduced at r57998, Bug 1586.
- Issue #1585: IDLE uses non-existent xrange() function.
- Windows EOL sequence not converted correctly, encoding error.
Caused file save to fail. Bug 1130.
- IDLE converted to Python 3000 syntax.
- Strings became Unicode.
- CallTips module now uses the inspect module to produce the argspec.
- IDLE modules now use absolute import instead of implied relative import.
- atexit call replaces sys.exitfunc. The functionality of delete-exitfunc flag
in config-main.cfg remains unchanged: if set, registered exit functions will
be cleared before IDLE exits.
What's New in IDLE 2.6
======================
*Release date: 01-Oct-2008*, merged into 3.0 releases detailed above (3.0rc2)
- Issue #2665: On Windows, an IDLE installation upgraded from an old version
would not start if a custom theme was defined.
- Home / Control-A toggles between left margin and end of leading white
space. issue1196903, patch by Jeff Shute.
- Improved AutoCompleteWindow logic. issue2062, patch by Tal Einat.
- Autocompletion of filenames now support alternate separators, e.g. the
'/' char on Windows. issue2061 Patch by Tal Einat.
- Configured selection highlighting colors were ignored; updating highlighting
in the config dialog would cause non-Python files to be colored as if they
were Python source; improve use of ColorDelagator. Patch 1334. Tal Einat.
- ScriptBinding event handlers weren't returning 'break'. Patch 2050, Tal Einat
- There was an error on exit if no sys.exitfunc was defined. Issue 1647.
- Could not open files in .idlerc directory if latter was hidden on Windows.
Issue 1743, Issue 1862.
- Configure Dialog: improved layout for keybinding. Patch 1457 Tal Einat.
- tabpage.py updated: tabbedPages.py now supports multiple dynamic rows
of tabs. Patch 1612746 Tal Einat.
- Add confirmation dialog before printing. Patch 1717170 Tal Einat.
- Show paste position if > 80 col. Patch 1659326 Tal Einat.
- Update cursor color without restarting. Patch 1725576 Tal Einat.
- Allow keyboard interrupt only when user code is executing in subprocess.
Patch 1225 Tal Einat (reworked from IDLE-Spoon).
- configDialog cleanup. Patch 1730217 Tal Einat.
- textView cleanup. Patch 1718043 Tal Einat.
- Clean up EditorWindow close.
- Patch 1693258: Fix for duplicate "preferences" menu-OS X. Backport of r56204.
- OSX: Avoid crash for those versions of Tcl/Tk which don't have a console
- Bug in idlelib.MultiCall: Options dialog was crashing IDLE if there was an
option in config-extensions w/o a value. Patch #1672481, Tal Einat
- Corrected some bugs in AutoComplete. Also, Page Up/Down in ACW implemented;
mouse and cursor selection in ACWindow implemented; double Tab inserts
current selection and closes ACW (similar to double-click and Return); scroll
wheel now works in ACW. Added AutoComplete instructions to IDLE Help.
- AutoCompleteWindow moved below input line, will move above if there
isn't enough space. Patch 1621265 Tal Einat
- Calltips now 'handle' tuples in the argument list (display '<tuple>' :)
Suggested solution by Christos Georgiou, Bug 791968.
- Add 'raw' support to configHandler. Patch 1650174 Tal Einat.
- Avoid hang when encountering a duplicate in a completion list. Bug 1571112.
- Patch #1362975: Rework CodeContext indentation algorithm to
avoid hard-coding pixel widths.
- Bug #813342: Start the IDLE subprocess with -Qnew if the parent
is started with that option.
- Honor the "Cancel" action in the save dialog (Debian bug #299092)
- Some syntax errors were being caught by tokenize during the tabnanny
check, resulting in obscure error messages. Do the syntax check
first. Bug 1562716, 1562719
- IDLE's version number takes a big jump to match the version number of
the Python release of which it's a part.
What's New in IDLE 1.2?
=======================
*Release date: 19-SEP-2006*
- File menu hotkeys: there were three 'p' assignments. Reassign the
'Save Copy As' and 'Print' hotkeys to 'y' and 't'. Change the
Shell hotkey from 's' to 'l'.
- IDLE honors new quit() and exit() commands from site.py Quitter() object.
Patch 1540892, Jim Jewett
- The 'with' statement is now a Code Context block opener.
Patch 1540851, Jim Jewett
- Retrieval of previous shell command was not always preserving indentation
(since 1.2a1) Patch 1528468 Tal Einat.
- Changing tokenize (39046) to detect dedent broke tabnanny check (since 1.2a1)
- ToggleTab dialog was setting indent to 8 even if cancelled (since 1.2a1).
- When used w/o subprocess, all exceptions were preceded by an error
message claiming they were IDLE internal errors (since 1.2a1).
- Bug #1525817: Don't truncate short lines in IDLE's tool tips.
- Bug #1517990: IDLE keybindings on MacOS X now work correctly
- Bug #1517996: IDLE now longer shows the default Tk menu when a
path browser, class browser or debugger is the frontmost window on MacOS X
- EditorWindow.test() was failing. Bug 1417598
- EditorWindow failed when used stand-alone if sys.ps1 not set.
Bug 1010370 Dave Florek
- Tooltips failed on new-syle class __init__ args. Bug 1027566 Loren Guthrie
- Avoid occasional failure to detect closing paren properly.
Patch 1407280 Tal Einat
- Rebinding Tab key was inserting 'tab' instead of 'Tab'. Bug 1179168.
- Colorizer now handles #<builtin> correctly, also unicode strings and
'as' keyword in comment directly following import command. Closes 1325071.
Patch 1479219 Tal Einat
- Patch #1162825: Support non-ASCII characters in IDLE window titles.
- Source file f.flush() after writing; trying to avoid lossage if user
kills GUI.
- Options / Keys / Advanced dialog made functional. Also, allow binding
of 'movement' keys.
- 'syntax' patch adds improved calltips and a new class attribute listbox.
MultiCall module allows binding multiple actions to an event.
Patch 906702 Noam Raphael
- Better indentation after first line of string continuation.
IDLEfork Patch 681992, Noam Raphael
- Fixed CodeContext alignment problem, following suggestion from Tal Einat.
- Increased performance in CodeContext extension Patch 936169 Noam Raphael
- Mac line endings were incorrect when pasting code from some browsers
when using X11 and the Fink distribution. Python Bug 1263656.
- <Enter> when cursor is on a previous command retrieves that command. Instead
of replacing the input line, the previous command is now appended to the
input line. Indentation is preserved, and undo is enabled.
Patch 1196917 Jeff Shute
- Clarify "tab/space" Error Dialog and "Tab Width" Dialog associated with
the Untabify command.
- Corrected "tab/space" Error Dialog to show correct menu for Untabify.
Patch 1196980 Jeff Shute
- New files are colorized by default, and colorizing is removed when
saving as non-Python files. Patch 1196895 Jeff Shute
Closes Python Bugs 775012 and 800432, partial fix IDLEfork 763524
- Improve subprocess link error notification.
- run.py: use Queue's blocking feature instead of sleeping in the main
loop. Patch # 1190163 Michiel de Hoon
- Add config-main option to make the 'history' feature non-cyclic.
Default remains cyclic. Python Patch 914546 Noam Raphael.
- Removed ability to configure tabs indent from Options dialog. This 'feature'
has never worked and no one has complained. It is still possible to set a
default tabs (v. spaces) indent 'manually' via config-main.def (or to turn on
tabs for the current EditorWindow via the Format menu) but IDLE will
encourage indentation via spaces.
- Enable setting the indentation width using the Options dialog.
Bug # 783877
- Add keybindings for del-word-left and del-word-right.
- Discourage using an indent width other than 8 when using tabs to indent
Python code.
- Restore use of EditorWindow.set_indentation_params(), was dead code since
Autoindent was merged into EditorWindow. This allows IDLE to conform to the
indentation width of a loaded file. (But it still will not switch to tabs
even if the file uses tabs.) Any change in indent width is local to that
window.
- Add Tabnanny check before Run/F5, not just when Checking module.
- If an extension can't be loaded, print warning and skip it instead of
erroring out.
- Improve error handling when .idlerc can't be created (warn and exit).
- The GUI was hanging if the shell window was closed while a raw_input()
was pending. Restored the quit() of the readline() mainloop().
http://mail.python.org/pipermail/idle-dev/2004-December/002307.html
- The remote procedure call module rpc.py can now access data attributes of
remote registered objects. Changes to these attributes are local, however.
What's New in IDLE 1.1?
=======================
*Release date: 30-NOV-2004*
- On OpenBSD, terminating IDLE with ctrl-c from the command line caused a
stuck subprocess MainThread because only the SocketThread was exiting.
- Saving a Keyset w/o making changes (by using the "Save as New Custom Key Set"
button) caused IDLE to fail on restart (no new keyset was created in
config-keys.cfg). Also true for Theme/highlights. Python Bug 1064535.
- A change to the linecache.py API caused IDLE to exit when an exception was
raised while running without the subprocess (-n switch). Python Bug 1063840.
- When paragraph reformat width was made configurable, a bug was
introduced that caused reformatting of comment blocks to ignore how
far the block was indented, effectively adding the indentation width
to the reformat width. This has been repaired, and the reformat
width is again a bound on the total width of reformatted lines.
- Improve keyboard focus binding, especially in Windows menu. Improve
window raising, especially in the Windows menu and in the debugger.
IDLEfork 763524.
- If user passes a non-existent filename on the commandline, just
open a new file, don't raise a dialog. IDLEfork 854928.
- EditorWindow.py was not finding the .chm help file on Windows. Typo
at Rev 1.54. Python Bug 990954
- checking sys.platform for substring 'win' was breaking IDLE docs on Mac
(darwin). Also, Mac Safari browser requires full file:// URIs. SF 900580.
- Redirect the warning stream to the shell during the ScriptBinding check of
user code and format the warning similarly to an exception for both that
check and for runtime warnings raised in the subprocess.
- CodeContext hint pane visibility state is now persistent across sessions.
The pane no longer appears in the shell window. Added capability to limit
extensions to shell window or editor windows. Noam Raphael addition
to Patch 936169.
- Paragraph reformat width is now a configurable parameter in the
Options GUI.
- New Extension: CodeContext. Provides block structuring hints for code
which has scrolled above an edit window. Patch 936169 Noam Raphael.
- If nulls somehow got into the strings in recent-files.lst
EditorWindow.update_recent_files_list() was failing. Python Bug 931336.
- If the normal background is changed via Configure/Highlighting, it will
update immediately, thanks to the previously mentioned patch by Nigel Rowe.
- Add a highlight theme for builtin keywords. Python Patch 805830 Nigel Rowe
This also fixed IDLEfork bug [ 693418 ] Normal text background color not
refreshed and Python bug [897872 ] Unknown color name on HP-UX
- rpc.py:SocketIO - Large modules were generating large pickles when downloaded
to the execution server. The return of the OK response from the subprocess
initialization was interfering and causing the sending socket to be not
ready. Add an IO ready test to fix this. Moved the polling IO ready test
into pollpacket().
- Fix typo in rpc.py, s/b "pickle.PicklingError" not "pickle.UnpicklingError".
- Added a Tk error dialog to run.py inform the user if the subprocess can't
connect to the user GUI process. Added a timeout to the GUI's listening
socket. Added Tk error dialogs to PyShell.py to announce a failure to bind
the port or connect to the subprocess. Clean up error handling during
connection initiation phase. This is an update of Python Patch 778323.
- Print correct exception even if source file changed since shell was
restarted. IDLEfork Patch 869012 Noam Raphael
- Keybindings with the Shift modifier now work correctly. So do bindings which
use the Space key. Limit unmodified user keybindings to the function keys.
Python Bug 775353, IDLEfork Bugs 755647, 761557
- After an exception, run.py was not setting the exception vector. Noam
Raphael suggested correcting this so pdb's postmortem pm() would work.
IDLEfork Patch 844675
- IDLE now does not fail to save the file anymore if the Tk buffer is not a
Unicode string, yet eol_convention is. Python Bugs 774680, 788378
- IDLE didn't start correctly when Python was installed in "Program Files" on
W2K and XP. Python Bugs 780451, 784183
- config-main.def documentation incorrectly referred to idle- instead of
config- filenames. SF 782759 Also added note about .idlerc location.
What's New in IDLE 1.0?
=======================
*Release date: 29-Jul-2003*
- Added a banner to the shell discussing warnings possibly raised by personal
firewall software. Added same comment to README.txt.
- Calltip error when docstring was None Python Bug 775541
- Updated extend.txt, help.txt, and config-extensions.def to correctly
reflect the current status of the configuration system. Python Bug 768469
- Fixed: Call Tip Trimming May Loop Forever. Python Patch 769142 (Daniels)
- Replaced apply(f, args, kwds) with f(*args, **kwargs) to improve performance
Python Patch 768187
- Break or continue statements outside a loop were causing IDLE crash
Python Bug 767794
- Convert Unicode strings from readline to IOBinding.encoding. Also set
sys.std{in|out|err}.encoding, for both the local and the subprocess case.
SF IDLEfork patch 682347.
- Extend AboutDialog.ViewFile() to support file encodings. Make the CREDITS
file Latin-1.
- Updated the About dialog to reflect re-integration into Python. Provide
buttons to display Python's NEWS, License, and Credits, plus additional
buttons for IDLE's README and NEWS.
- TextViewer() now has a third parameter which allows inserting text into the
viewer instead of reading from a file.
- (Created the .../Lib/idlelib directory in the Python CVS, which is a clone of
IDLEfork modified to install in the Python environment. The code in the
interrupt module has been moved to thread.interrupt_main(). )
- Printing the Shell window was failing if it was not saved first SF 748975
- When using the Search in Files dialog, if the user had a selection
highlighted in his Editor window, insert it into the dialog search field.
- The Python Shell entry was disappearing from the Windows menu.
- Update the Windows file list when a file name change occurs
- Change to File / Open Module: always pop up the dialog, using the current
selection as the default value. This is easier to use habitually.
- Avoided a problem with starting the subprocess when 'localhost' doesn't
resolve to the user's loopback interface. SF 747772
- Fixed an issue with highlighted errors never de-colorizing. SF 747677. Also
improved notification of Tabnanny Token Error.
- File / New will by default save in the directory of the Edit window from
which it was initiated. SF 748973 Guido van Rossum patch.
What's New in IDLEfork 0.9b1?
=============================
*Release date: 02-Jun-2003*
- The current working directory of the execution environment (and shell
following completion of execution) is now that of the module being run.
- Added the delete-exitfunc option to config-main.def. (This option is not
included in the Options dialog.) Setting this to True (the default) will
cause IDLE to not run sys.exitfunc/atexit when the subprocess exits.
- IDLE now preserves the line ending codes when editing a file produced on
a different platform. SF 661759, SF 538584
- Reduced default editor font size to 10 point and increased window height
to provide a better initial impression on Windows.
- Options / Fonts/Tabs / Set Base Editor Font: List box was not highlighting
the default font when first installed on Windows. SF 661676
- Added Autosave feature: when user runs code from edit window, if the file
has been modified IDLE will silently save it if Autosave is enabled. The
option is set in the Options dialog, and the default is to prompt the
user to save the file. SF 661318 Bruce Sherwood patch.
- Improved the RESTART annotation in the shell window when the user restarts
the shell while it is generating output. Also improved annotation when user
repeatedly hammers the Ctrl-F6 restart.
- Allow IDLE to run when not installed and cwd is not the IDLE directory
SF Patch 686254 "Run IDLEfork from any directory without set-up" - Raphael
- When a module is run from an EditorWindow: if its directory is not in
sys.path, prepend it. This allows the module to import other modules in
the same directory. Do the same for a script run from the command line.
- Correctly restart the subprocess if it is running user code and the user
attempts to run some other module or restarts the shell. Do the same if
the link is broken and it is possible to restart the subprocess and re-
connect to the GUI. SF RFE 661321.
- Improved exception reporting when running commands or scripts from the
command line.
- Added a -n command line switch to start IDLE without the subprocess.
Removed the Shell menu when running in that mode. Updated help messages.
- Added a comment to the shell startup header to indicate when IDLE is not
using the subprocess.
- Restore the ability to run without the subprocess. This can be important for
some platforms or configurations. (Running without the subprocess allows the
debugger to trace through parts of IDLE itself, which may or may not be
desirable, depending on your point of view. In addition, the traditional
reload/import tricks must be use if user source code is changed.) This is
helpful for developing IDLE using IDLE, because one instance can be used to
edit the code and a separate instance run to test changes. (Multiple
concurrent IDLE instances with subprocesses is a future feature)
- Improve the error message a user gets when saving a file with non-ASCII
characters and no source encoding is specified. Done by adding a dialog
'EncodingMessage', which contains the line to add in a fixed-font entry
widget, and which has a button to add that line to the file automatically.
Also, add a configuration option 'EditorWindow/encoding', which has three
possible values: none, utf-8, and locale. None is the default: IDLE will show
this dialog when non-ASCII characters are encountered. utf-8 means that files
with non-ASCII characters are saved as utf-8-with-bom. locale means that
files are saved in the locale's encoding; the dialog is only displayed if the
source contains characters outside the locale's charset. SF 710733 - Loewis
- Improved I/O response by tweaking the wait parameter in various
calls to signal.signal().
- Implemented a threaded subprocess which allows interrupting a pass
loop in user code using the 'interrupt' extension. User code runs
in MainThread, while the RPCServer is handled by SockThread. This is
necessary because Windows doesn't support signals.
- Implemented the 'interrupt' extension module, which allows a subthread
to raise a KeyboardInterrupt in the main thread.
- Attempting to save the shell raised an error related to saving
breakpoints, which are not implemented in the shell
- Provide a correct message when 'exit' or 'quit' are entered at the
IDLE command prompt SF 695861
- Eliminate extra blank line in shell output caused by not flushing
stdout when user code ends with an unterminated print. SF 695861
- Moved responsibility for exception formatting (i.e. pruning IDLE internal
calls) out of rpc.py into the client and server.
- Exit IDLE cleanly even when doing subprocess I/O
- Handle subprocess interrupt with an RPC message.
- Restart the subprocess if it terminates itself. (VPython programs do that)
- Support subclassing of exceptions, including in the shell, by moving the
exception formatting to the subprocess.
What's New in IDLEfork 0.9 Alpha 2?
===================================
*Release date: 27-Jan-2003*
- Updated INSTALL.txt to claify use of the python2 rpm.
- Improved formatting in IDLE Help.
- Run menu: Replace "Run Script" with "Run Module".
- Code encountering an unhandled exception under the debugger now shows
the correct traceback, with IDLE internal levels pruned out.
- If an exception occurs entirely in IDLE, don't prune the IDLE internal
modules from the traceback displayed.
- Class Browser and Path Browser now use Alt-Key-2 for vertical zoom.
- IDLE icons will now install correctly even when setup.py is run from the
build directory
- Class Browser now compatible with Python2.3 version of pyclbr.py
- Left cursor move in presence of selected text now moves from left end
of the selection.
- Add Meta keybindings to "IDLE Classic Windows" to handle reversed
Alt/Meta on some Linux distros.
- Change default: IDLE now starts with Python Shell.
- Removed the File Path from the Additional Help Sources scrolled list.
- Add capability to access Additional Help Sources on the web if the
Help File Path begins with //http or www. (Otherwise local path is
validated, as before.)
- Additional Help Sources were not being posted on the Help menu in the
order entered. Implement sorting the list by [HelpFiles] 'option'
number.
- Add Browse button to New Help Source dialog. Arrange to start in
Python/Doc if platform is Windows, otherwise start in current directory.
- Put the Additional Help Sources directly on the Help menu instead of in
an Extra Help cascade menu. Rearrange the Help menu so the Additional
Help Sources come last. Update help.txt appropriately.
- Fix Tk root pop-ups in configSectionNameDialog.py and configDialog.py
- Uniform capitalization in General tab of ConfigDialog, update the doc string.
- Fix bug in ConfigDialog where SaveAllChangedConfig() was unexpectedly
deleting Additional Help Sources from the user's config file.
- Make configHelpSourceEdit OK button the default and bind <Return>
- Fix Tk root pop-ups in configHelpSourceEdit: error dialogs not attached
to parents.
- Use os.startfile() to open both Additional Help and Python Help on the
Windows platform. The application associated with the file type will act as
the viewer. Windows help files (.chm) are now supported via the
Settings/General/Additional Help facility.
- If Python Help files are installed locally on Linux, use them instead of
accessing python.org.
- Make the methods for finding the Python help docs more robust, and make
them work in the installed configuration, also.
- On the Save Before Run dialog, make the OK button the default. One
less mouse action!
- Add a method: EditorWindow.get_geometry() for future use in implementing
window location persistence.
- Removed the "Help/Advice" menu entry. Thanks, David! We'll remember!
- Change the "Classic Windows" theme's paste key to be <ctrl-v>.
- Rearrange the Shell menu to put Stack Viewer entries adjacent.
- Add the ability to restart the subprocess interpreter from the shell window;
add an associated menu entry "Shell/Restart" with binding Control-F6. Update
IDLE help.
- Upon a restart, annotate the shell window with a "restart boundary". Add a
shell window menu "Shell/View Restart" with binding F6 to jump to the most
recent restart boundary.
- Add Shell menu to Python Shell; change "Settings" to "Options".
- Remove incorrect comment in setup.py: IDLEfork is now installed as a package.
- Add INSTALL.txt, HISTORY.txt, NEWS.txt to installed configuration.
- In installer text, fix reference to Visual Python, should be VPython.
Properly credit David Scherer.
- Modified idle, idle.py, idle.pyw to improve exception handling.
What's New in IDLEfork 0.9 Alpha 1?
===================================
*Release date: 31-Dec-2002*
- First release of major new functionality. For further details refer to
Idle-dev and/or the Sourceforge CVS.
- Adapted to the Mac platform.
- Overhauled the IDLE startup options and revised the idle -h help message,
which provides details of command line usage.
- Multiple bug fixes and usability enhancements.
- Introduced the new RPC implementation, which includes a debugger. The output
of user code is to the shell, and the shell may be used to inspect the
environment after the run has finished. (In version 0.8.1 the shell
environment was separate from the environment of the user code.)
- Introduced the configuration GUI and a new About dialog.
- Removed David Scherer's Remote Procedure Call code and replaced with Guido
van Rossum's. GvR code has support for the IDLE debugger and uses the shell
to inspect the environment of code Run from an Edit window. Files removed:
ExecBinding.py, loader.py, protocol.py, Remote.py, spawn.py
--------------------------------------------------------------------
Refer to HISTORY.txt for additional information on earlier releases.
--------------------------------------------------------------------

View file

@ -0,0 +1,251 @@
README.txt: an index to idlelib files and the IDLE menu.
IDLE is Python's Integrated Development and Learning
Environment. The user documentation is part of the Library Reference and
is available in IDLE by selecting Help => IDLE Help. This README documents
idlelib for IDLE developers and curious users.
IDLELIB FILES lists files alphabetically by category,
with a short description of each.
IDLE MENU show the menu tree, annotated with the module
or module object that implements the corresponding function.
This file is descriptive, not prescriptive, and may have errors
and omissions and lag behind changes in idlelib.
IDLELIB FILES
Implementation files not in IDLE MENU are marked (nim).
Deprecated files and objects are listed separately as the end.
Startup
-------
__init__.py # import, does nothing
__main__.py # -m, starts IDLE
idle.bat
idle.py
idle.pyw
Implementation
--------------
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.
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).
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
-------------
config-extensions.def # Defaults for extensions
config-highlight.def # Defaults for colorizing
config-keys.def # Defaults for key bindings
config-main.def # Defai;ts fpr font and geneal
Text
----
CREDITS.txt # not maintained, displayed by About IDLE
HISTORY.txt # NEWS up to July 2001
NEWS.txt # commits, displayed by About IDLE
README.txt # this file, displeyed by About IDLE
TODO.txt # needs review
extend.txt # about writing extensions
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
Unused and Deprecated files and objects (nim)
---------------------------------------------
tooltip.py # unused
IDLE MENUS
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
New File # eEW.new_callback
Open... # iomenu.open
Open Module # eEw.open_module
Recent Files
Class Browser # eEW.open_class_browser, browser.ClassBrowser
Path Browser # eEW.open_path_browser, pathbrowser
---
Save # iomenu.save
Save As... # iomenu.save_as
Save Copy As... # iomenu.save_a_copy
---
Print Window # iomenu.print_window
---
Close # eEW.close_event
Exit # flist.close_all_callback (bound in eEW)
Edit
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, debugger_r, PyShell.toggle_debuger
Stack Viewer # stackviewer, PyShell.open_stack_viewer
Auto-open Stack Viewer # stackviewer
Format (Editor only)
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 # rstrip extension
Run (Editor only)
Python Shell # pyshell
---
Check Module # runscript
Run Module # runscript
Options
Configure IDLE # eEW.config_dialog, configdialog
(tabs in the dialog)
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 (ed)# codecontext extension
Window
Zoomheight # zoomheight extension
---
<open windows> # windows
Help
About IDLE # eEW.about_dialog, help_about.AboutDialog
---
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 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.

210
third_party/python/Lib/idlelib/TODO.txt vendored Normal file
View file

@ -0,0 +1,210 @@
Original IDLE todo, much of it now outdated:
============================================
TO DO:
- improve debugger:
- manage breakpoints globally, allow bp deletion, tbreak, cbreak etc.
- real object browser
- help on how to use it (a simple help button will do wonders)
- performance? (updates of large sets of locals are slow)
- better integration of "debug module"
- debugger should be global resource (attached to flist, not to shell)
- fix the stupid bug where you need to step twice
- display class name in stack viewer entries for methods
- suppress tracing through IDLE internals (e.g. print) DONE
- add a button to suppress through a specific module or class or method
- more object inspection to stack viewer, e.g. to view all array items
- insert the initial current directory into sys.path DONE
- default directory attribute for each window instead of only for windows
that have an associated filename
- command expansion from keywords, module contents, other buffers, etc.
- "Recent documents" menu item DONE
- Filter region command
- Optional horizontal scroll bar
- more Emacsisms:
- ^K should cut to buffer
- M-[, M-] to move by paragraphs
- incremental search?
- search should indicate wrap-around in some way
- restructure state sensitive code to avoid testing flags all the time
- persistent user state (e.g. window and cursor positions, bindings)
- make backups when saving
- check file mtimes at various points
- Pluggable interface with RCS/CVS/Perforce/Clearcase
- better help?
- don't open second class browser on same module (nor second path browser)
- unify class and path browsers
- Need to define a standard way whereby one can determine one is running
inside IDLE (needed for Tk mainloop, also handy for $PYTHONSTARTUP)
- Add more utility methods for use by extensions (a la get_selection)
- Way to run command in totally separate interpreter (fork+os.system?) DONE
- Way to find definition of fully-qualified name:
In other words, select "UserDict.UserDict", hit some magic key and
it loads up UserDict.py and finds the first def or class for UserDict.
- need a way to force colorization on/off
- need a way to force auto-indent on/off
Details:
- ^O (on Unix -- open-line) should honor autoindent
- after paste, show end of pasted text
- on Windows, should turn short filename to long filename (not only in argv!)
(shouldn't this be done -- or undone -- by ntpath.normpath?)
- new autoindent after colon even indents when the colon is in a comment!
- sometimes forward slashes in pathname remain
- sometimes star in window name remains in Windows menu
- With unix bindings, ESC by itself is ignored
- Sometimes for no apparent reason a selection from the cursor to the
end of the command buffer appears, which is hard to get rid of
because it stays when you are typing!
- The Line/Col in the status bar can be wrong initially in PyShell DONE
Structural problems:
- too much knowledge in FileList about EditorWindow (for example)
- should add some primitives for accessing the selection etc.
to repeat cumbersome code over and over
======================================================================
Jeff Bauer suggests:
- Open Module doesn't appear to handle hierarchical packages.
- Class browser should also allow hierarchical packages.
- Open and Open Module could benefit from a history, DONE
either command line style, or Microsoft recent-file
style.
- Add a Smalltalk-style inspector (i.e. Tkinspect)
The last suggestion is already a reality, but not yet
integrated into IDLE. I use a module called inspector.py,
that used to be available from python.org(?) It no longer
appears to be in the contributed section, and the source
has no author attribution.
In any case, the code is useful for visually navigating
an object's attributes, including its container hierarchy.
>>> from inspector import Tkinspect
>>> Tkinspect(None, myObject)
Tkinspect could probably be extended and refined to
integrate better into IDLE.
======================================================================
Comparison to PTUI
------------------
+ PTUI's help is better (HTML!)
+ PTUI can attach a shell to any module
+ PTUI has some more I/O commands:
open multiple
append
examine (what's that?)
======================================================================
Notes after trying to run Grail
-------------------------------
- Grail does stuff to sys.path based on sys.argv[0]; you must set
sys.argv[0] to something decent first (it is normally set to the path of
the idle script).
- Grail must be exec'ed in __main__ because that's imported by some
other parts of Grail.
- Grail uses a module called History and so does idle :-(
======================================================================
Robin Friedrich's items:
Things I'd like to see:
- I'd like support for shift-click extending the selection. There's a
bug now that it doesn't work the first time you try it.
- Printing is needed. How hard can that be on Windows? FIRST CUT DONE
- The python-mode trick of autoindenting a line with <tab> is neat and
very handy.
- (someday) a spellchecker for docstrings and comments.
- a pagedown/up command key which moves to next class/def statement (top
level)
- split window capability
- DnD text relocation/copying
Things I don't want to see.
- line numbers... will probably slow things down way too much.
- Please use another icon for the tree browser leaf. The small snake
isn't cutting it.
----------------------------------------------------------------------
- Customizable views (multi-window or multi-pane). (Markus Gritsch)
- Being able to double click (maybe double right click) on a callable
object in the editor which shows the source of the object, if
possible. (Gerrit Holl)
- Hooks into the guts, like in Emacs. (Mike Romberg)
- Sharing the editor with a remote tutor. (Martijn Faassen)
- Multiple views on the same file. (Tony J Ibbs)
- Store breakpoints in a global (per-project) database (GvR); Dirk
Heise adds: save some space-trimmed context and search around when
reopening a file that might have been edited by someone else.
- Capture menu events in extensions without changing the IDLE source.
(Matthias Barmeier)
- Use overlapping panels (a "notebook" in MFC terms I think) for info
that doesn't need to be accessible simultaneously (e.g. HTML source
and output). Use multi-pane windows for info that does need to be
shown together (e.g. class browser and source). (Albert Brandl)
- A project should invisibly track all symbols, for instant search,
replace and cross-ref. Projects should be allowed to span multiple
directories, hosts, etc. Project management files are placed in a
directory you specify. A global mapping between project names and
project directories should exist [not so sure --GvR]. (Tim Peters)
- Merge attr-tips and auto-expand. (Mark Hammond, Tim Peters)
- Python Shell should behave more like a "shell window" as users know
it -- i.e. you can only edit the current command, and the cursor can't
escape from the command area. (Albert Brandl)
- Set X11 class to "idle/Idle", set icon and title to something
beginning with "idle" -- for window manangers. (Randall Hopper)
- Config files editable through a preferences dialog. (me) DONE
- Config files still editable outside the preferences dialog.
(Randall Hopper) DONE
- When you're editing a command in PyShell, and there are only blank
lines below the cursor, hitting Return should ignore or delete those
blank lines rather than deciding you're not on the last line. (me)
- Run command (F5 c.s.) should be more like Pythonwin's Run -- a
dialog with options to give command line arguments, run the debugger,
etc. (me)
- Shouldn't be able to delete part of the prompt (or any text before
it) in the PyShell. (Martijn Faassen) DONE
- Emacs style auto-fill (also smart about comments and strings).
(Jeremy Hylton)
- Output of Run Script should go to a separate output window, not to
the shell window. Output of separate runs should all go to the same
window but clearly delimited. (David Scherer) REJECT FIRST, LATTER DONE
- GUI form designer to kick VB's butt. (Robert Geiger) THAT'S NOT IDLE
- Printing! Possibly via generation of PDF files which the user must
then send to the printer separately. (Dinu Gherman) FIRST CUT

View file

@ -0,0 +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.

View file

@ -0,0 +1,8 @@
"""
IDLE main entry point
Run IDLE as python -m idlelib
"""
import idlelib.pyshell
idlelib.pyshell.main()
# This file does not work for 2.7; See issue 24212.

View file

@ -0,0 +1,402 @@
# A private copy of 3.7.0a1 pyclbr for use by idlelib.browser
"""Parse a Python module and describe its classes and functions.
Parse enough of a Python file to recognize imports and class and
function definitions, and to find out the superclasses of a class.
The interface consists of a single function:
readmodule_ex(module, path=None)
where module is the name of a Python module, and path is an optional
list of directories where the module is to be searched. If present,
path is prepended to the system search path sys.path. The return value
is a dictionary. The keys of the dictionary are the names of the
classes and functions defined in the module (including classes that are
defined via the from XXX import YYY construct). The values are
instances of classes Class and Function. One special key/value pair is
present for packages: the key '__path__' has a list as its value which
contains the package search path.
Classes and Functions have a common superclass: _Object. Every instance
has the following attributes:
module -- name of the module;
name -- name of the object;
file -- file in which the object is defined;
lineno -- line in the file where the object's definition starts;
parent -- parent of this object, if any;
children -- nested objects contained in this object.
The 'children' attribute is a dictionary mapping names to objects.
Instances of Function describe functions with the attributes from _Object.
Instances of Class describe classes with the attributes from _Object,
plus the following:
super -- list of super classes (Class instances if possible);
methods -- mapping of method names to beginning line numbers.
If the name of a super class is not recognized, the corresponding
entry in the list of super classes is not a class instance but a
string giving the name of the super class. Since import statements
are recognized and imported modules are scanned as well, this
shouldn't happen often.
"""
import io
import sys
import importlib.util
import tokenize
from token import NAME, DEDENT, OP
__all__ = ["readmodule", "readmodule_ex", "Class", "Function"]
_modules = {} # Initialize cache of modules we've seen.
class _Object:
"Informaton about Python class or function."
def __init__(self, module, name, file, lineno, parent):
self.module = module
self.name = name
self.file = file
self.lineno = lineno
self.parent = parent
self.children = {}
def _addchild(self, name, obj):
self.children[name] = obj
class Function(_Object):
"Information about a Python function, including methods."
def __init__(self, module, name, file, lineno, parent=None):
_Object.__init__(self, module, name, file, lineno, parent)
class Class(_Object):
"Information about a Python class."
def __init__(self, module, name, super, file, lineno, parent=None):
_Object.__init__(self, module, name, file, lineno, parent)
self.super = [] if super is None else super
self.methods = {}
def _addmethod(self, name, lineno):
self.methods[name] = lineno
def _nest_function(ob, func_name, lineno):
"Return a Function after nesting within ob."
newfunc = Function(ob.module, func_name, ob.file, lineno, ob)
ob._addchild(func_name, newfunc)
if isinstance(ob, Class):
ob._addmethod(func_name, lineno)
return newfunc
def _nest_class(ob, class_name, lineno, super=None):
"Return a Class after nesting within ob."
newclass = Class(ob.module, class_name, super, ob.file, lineno, ob)
ob._addchild(class_name, newclass)
return newclass
def readmodule(module, path=None):
"""Return Class objects for the top-level classes in module.
This is the original interface, before Functions were added.
"""
res = {}
for key, value in _readmodule(module, path or []).items():
if isinstance(value, Class):
res[key] = value
return res
def readmodule_ex(module, path=None):
"""Return a dictionary with all functions and classes in module.
Search for module in PATH + sys.path.
If possible, include imported superclasses.
Do this by reading source, without importing (and executing) it.
"""
return _readmodule(module, path or [])
def _readmodule(module, path, inpackage=None):
"""Do the hard work for readmodule[_ex].
If inpackage is given, it must be the dotted name of the package in
which we are searching for a submodule, and then PATH must be the
package search path; otherwise, we are searching for a top-level
module, and path is combined with sys.path.
"""
# Compute the full module name (prepending inpackage if set).
if inpackage is not None:
fullmodule = "%s.%s" % (inpackage, module)
else:
fullmodule = module
# Check in the cache.
if fullmodule in _modules:
return _modules[fullmodule]
# Initialize the dict for this module's contents.
tree = {}
# Check if it is a built-in module; we don't do much for these.
if module in sys.builtin_module_names and inpackage is None:
_modules[module] = tree
return tree
# Check for a dotted module name.
i = module.rfind('.')
if i >= 0:
package = module[:i]
submodule = module[i+1:]
parent = _readmodule(package, path, inpackage)
if inpackage is not None:
package = "%s.%s" % (inpackage, package)
if not '__path__' in parent:
raise ImportError('No package named {}'.format(package))
return _readmodule(submodule, parent['__path__'], package)
# Search the path for the module.
f = None
if inpackage is not None:
search_path = path
else:
search_path = path + sys.path
spec = importlib.util._find_spec_from_path(fullmodule, search_path)
_modules[fullmodule] = tree
# Is module a package?
if spec.submodule_search_locations is not None:
tree['__path__'] = spec.submodule_search_locations
try:
source = spec.loader.get_source(fullmodule)
if source is None:
return tree
except (AttributeError, ImportError):
# If module is not Python source, we cannot do anything.
return tree
fname = spec.loader.get_filename(fullmodule)
return _create_tree(fullmodule, path, fname, source, tree, inpackage)
def _create_tree(fullmodule, path, fname, source, tree, inpackage):
"""Return the tree for a particular module.
fullmodule (full module name), inpackage+module, becomes o.module.
path is passed to recursive calls of _readmodule.
fname becomes o.file.
source is tokenized. Imports cause recursive calls to _readmodule.
tree is {} or {'__path__': <submodule search locations>}.
inpackage, None or string, is passed to recursive calls of _readmodule.
The effect of recursive calls is mutation of global _modules.
"""
f = io.StringIO(source)
stack = [] # Initialize stack of (class, indent) pairs.
g = tokenize.generate_tokens(f.readline)
try:
for tokentype, token, start, _end, _line in g:
if tokentype == DEDENT:
lineno, thisindent = start
# Close previous nested classes and defs.
while stack and stack[-1][1] >= thisindent:
del stack[-1]
elif token == 'def':
lineno, thisindent = start
# Close previous nested classes and defs.
while stack and stack[-1][1] >= thisindent:
del stack[-1]
tokentype, func_name, start = next(g)[0:3]
if tokentype != NAME:
continue # Skip def with syntax error.
cur_func = None
if stack:
cur_obj = stack[-1][0]
cur_func = _nest_function(cur_obj, func_name, lineno)
else:
# It is just a function.
cur_func = Function(fullmodule, func_name, fname, lineno)
tree[func_name] = cur_func
stack.append((cur_func, thisindent))
elif token == 'class':
lineno, thisindent = start
# Close previous nested classes and defs.
while stack and stack[-1][1] >= thisindent:
del stack[-1]
tokentype, class_name, start = next(g)[0:3]
if tokentype != NAME:
continue # Skip class with syntax error.
# Parse what follows the class name.
tokentype, token, start = next(g)[0:3]
inherit = None
if token == '(':
names = [] # Initialize list of superclasses.
level = 1
super = [] # Tokens making up current superclass.
while True:
tokentype, token, start = next(g)[0:3]
if token in (')', ',') and level == 1:
n = "".join(super)
if n in tree:
# We know this super class.
n = tree[n]
else:
c = n.split('.')
if len(c) > 1:
# Super class form is module.class:
# look in module for class.
m = c[-2]
c = c[-1]
if m in _modules:
d = _modules[m]
if c in d:
n = d[c]
names.append(n)
super = []
if token == '(':
level += 1
elif token == ')':
level -= 1
if level == 0:
break
elif token == ',' and level == 1:
pass
# Only use NAME and OP (== dot) tokens for type name.
elif tokentype in (NAME, OP) and level == 1:
super.append(token)
# Expressions in the base list are not supported.
inherit = names
if stack:
cur_obj = stack[-1][0]
cur_class = _nest_class(
cur_obj, class_name, lineno, inherit)
else:
cur_class = Class(fullmodule, class_name, inherit,
fname, lineno)
tree[class_name] = cur_class
stack.append((cur_class, thisindent))
elif token == 'import' and start[1] == 0:
modules = _getnamelist(g)
for mod, _mod2 in modules:
try:
# Recursively read the imported module.
if inpackage is None:
_readmodule(mod, path)
else:
try:
_readmodule(mod, path, inpackage)
except ImportError:
_readmodule(mod, [])
except:
# If we can't find or parse the imported module,
# too bad -- don't die here.
pass
elif token == 'from' and start[1] == 0:
mod, token = _getname(g)
if not mod or token != "import":
continue
names = _getnamelist(g)
try:
# Recursively read the imported module.
d = _readmodule(mod, path, inpackage)
except:
# If we can't find or parse the imported module,
# too bad -- don't die here.
continue
# Add any classes that were defined in the imported module
# to our name space if they were mentioned in the list.
for n, n2 in names:
if n in d:
tree[n2 or n] = d[n]
elif n == '*':
# Don't add names that start with _.
for n in d:
if n[0] != '_':
tree[n] = d[n]
except StopIteration:
pass
f.close()
return tree
def _getnamelist(g):
"""Return list of (dotted-name, as-name or None) tuples for token source g.
An as-name is the name that follows 'as' in an as clause.
"""
names = []
while True:
name, token = _getname(g)
if not name:
break
if token == 'as':
name2, token = _getname(g)
else:
name2 = None
names.append((name, name2))
while token != "," and "\n" not in token:
token = next(g)[1]
if token != ",":
break
return names
def _getname(g):
"Return (dotted-name or None, next-token) tuple for token source g."
parts = []
tokentype, token = next(g)[0:2]
if tokentype != NAME and token != '*':
return (None, token)
parts.append(token)
while True:
tokentype, token = next(g)[0:2]
if token != '.':
break
tokentype, token = next(g)[0:2]
if tokentype != NAME:
break
parts.append(token)
return (".".join(parts), token)
def _main():
"Print module output (default this file) for quick visual check."
import os
try:
mod = sys.argv[1]
except:
mod = __file__
if os.path.exists(mod):
path = [os.path.dirname(mod)]
mod = os.path.basename(mod)
if mod.lower().endswith(".py"):
mod = mod[:-3]
else:
path = []
tree = readmodule_ex(mod, path)
lineno_key = lambda a: getattr(a, 'lineno', 0)
objs = sorted(tree.values(), key=lineno_key, reverse=True)
indent_level = 2
while objs:
obj = objs.pop()
if isinstance(obj, list):
# Value is a __path__ key.
continue
if not hasattr(obj, 'indent'):
obj.indent = 0
if isinstance(obj, _Object):
new_objs = sorted(obj.children.values(),
key=lineno_key, reverse=True)
for ob in new_objs:
ob.indent = obj.indent + indent_level
objs.extend(new_objs)
if isinstance(obj, Class):
print("{}class {} {} {}"
.format(' ' * obj.indent, obj.name, obj.super, obj.lineno))
elif isinstance(obj, Function):
print("{}def {} {}".format(' ' * obj.indent, obj.name, obj.lineno))
if __name__ == "__main__":
_main()

View file

@ -0,0 +1,231 @@
"""Complete either attribute names or file names.
Either on demand or after a user-selected delay after a key character,
pop up a list of candidates.
"""
import os
import string
import sys
# 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 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:
def __init__(self, editwin=None):
self.editwin = editwin
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
@classmethod
def reload(cls):
cls.popupwait = idleConf.GetOption(
"extensions", "AutoComplete", "popupwait", type="int", default=0)
def _make_autocomplete_window(self):
return autocomplete_w.AutoCompleteWindow(self.text)
def _remove_autocomplete_window(self, event=None):
if self.autocompletewindow:
self.autocompletewindow.hide_window()
self.autocompletewindow = None
def force_open_completions_event(self, event):
"""Happens when the user really wants to open a completion list, even
if a function call is needed.
"""
self.open_completions(True, False, True)
return "break"
def try_open_completions_event(self, event):
"""Happens when it would be nice to open a completion list, but not
really necessary, for example after a dot, so function
calls won't be made.
"""
lastchar = self.text.get("insert-1c")
if lastchar == ".":
self._open_completions_later(False, False, False,
COMPLETE_ATTRIBUTES)
elif lastchar in SEPS:
self._open_completions_later(False, False, False,
COMPLETE_FILES)
def autocomplete_event(self, event):
"""Happens when the user wants to complete his word, and if necessary,
open a completion list after that (if there is more than one
completion)
"""
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)
return "break" if opened else None
def _open_completions_later(self, *args):
self._delayed_completion_index = self.text.index("insert")
if self._delayed_completion_id is not None:
self.text.after_cancel(self._delayed_completion_id)
self._delayed_completion_id = \
self.text.after(self.popupwait, self._delayed_open_completions,
*args)
def _delayed_open_completions(self, *args):
self._delayed_completion_id = None
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.
Return True if successful (no syntax error or so found).
if complete is True, then if there's nothing to complete and no
start of completion, won't open completions and return False.
If mode is given, will open a completion list only in this mode.
"""
# Cancel another delayed call, if it exists.
if self._delayed_completion_id is not None:
self.text.after_cancel(self._delayed_completion_id)
self._delayed_completion_id = None
hp = HyperParser(self.editwin, "insert")
curline = self.text.get("insert linestart", "insert")
i = j = len(curline)
if hp.is_in_string() and (not mode or mode==COMPLETE_FILES):
# Find the beginning of the string
# fetch_completions will look at the file system to determine whether the
# string value constitutes an actual file name
# XXX could consider raw strings here and unescape the string value if it's
# not raw.
self._remove_autocomplete_window()
mode = COMPLETE_FILES
# Find last separator or string start
while i and curline[i-1] not in "'\"" + SEPS:
i -= 1
comp_start = curline[i:j]
j = i
# Find string start
while i and curline[i-1] not in "'\"":
i -= 1
comp_what = curline[i:j]
elif hp.is_in_code() and (not mode or mode==COMPLETE_ATTRIBUTES):
self._remove_autocomplete_window()
mode = COMPLETE_ATTRIBUTES
while i and (curline[i-1] in ID_CHARS or ord(curline[i-1]) > 127):
i -= 1
comp_start = curline[i:j]
if i and curline[i-1] == '.':
hp.set_index("insert-%dc" % (len(curline)-(i-1)))
comp_what = hp.get_expression()
if not comp_what or \
(not evalfuncs and comp_what.find('(') != -1):
return None
else:
comp_what = ""
else:
return None
if complete and not comp_what and not comp_start:
return None
comp_lists = self.fetch_completions(comp_what, mode)
if not comp_lists[0]:
return None
self.autocompletewindow = self._make_autocomplete_window()
return not self.autocompletewindow.show_window(
comp_lists, "insert-%dc" % len(comp_start),
complete, mode, userWantsWin)
def fetch_completions(self, what, mode):
"""Return a pair of lists of completions for something. The first list
is a sublist of the second. Both are sorted.
If there is a Python subprocess, get the comp. list there. Otherwise,
either fetch_completions() is running in the subprocess itself or it
was called in an IDLE EditorWindow before any script had been run.
The subprocess environment is that of the most recently run script. If
two unrelated modules are being edited some calltips in the current
module may be inoperative if the module was not the last to run.
"""
try:
rpcclt = self.editwin.flist.pyshell.interp.rpcclt
except:
rpcclt = None
if rpcclt:
return rpcclt.remotecall("exec", "get_the_completion_list",
(what, mode), {})
else:
if mode == COMPLETE_ATTRIBUTES:
if what == "":
namespace = __main__.__dict__.copy()
namespace.update(__main__.__builtins__.__dict__)
bigl = eval("dir()", namespace)
bigl.sort()
if "__all__" in bigl:
smalll = sorted(eval("__all__", namespace))
else:
smalll = [s for s in bigl if s[:1] != '_']
else:
try:
entity = self.get_entity(what)
bigl = dir(entity)
bigl.sort()
if "__all__" in bigl:
smalll = sorted(entity.__all__)
else:
smalll = [s for s in bigl if s[:1] != '_']
except:
return [], []
elif mode == COMPLETE_FILES:
if what == "":
what = "."
try:
expandedpath = os.path.expanduser(what)
bigl = os.listdir(expandedpath)
bigl.sort()
smalll = [s for s in bigl if s[:1] != '.']
except OSError:
return [], []
if not smalll:
smalll = bigl
return smalll, bigl
def get_entity(self, name):
"""Lookup name in a namespace spanning sys.modules and __main.dict__"""
namespace = sys.modules.copy()
namespace.update(__main__.__dict__)
return eval(name, namespace)
AutoComplete.reload()
if __name__ == '__main__':
from unittest import main
main('idlelib.idle_test.test_autocomplete', verbosity=2)

View file

@ -0,0 +1,467 @@
"""
An auto-completion window for IDLE, used by the autocomplete extension
"""
import platform
from tkinter import *
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_FOCUS_OUT_SEQUENCE = "<FocusOut>"
HIDE_SEQUENCES = (HIDE_FOCUS_OUT_SEQUENCE, "<ButtonPress>")
KEYPRESS_VIRTUAL_EVENT_NAME = "<<autocompletewindow-keypress>>"
# We need to bind event beyond <Key> so that the function will be called
# before the default specific IDLE function
KEYPRESS_SEQUENCES = ("<Key>", "<Key-BackSpace>", "<Key-Return>", "<Key-Tab>",
"<Key-Up>", "<Key-Down>", "<Key-Home>", "<Key-End>",
"<Key-Prior>", "<Key-Next>")
KEYRELEASE_VIRTUAL_EVENT_NAME = "<<autocompletewindow-keyrelease>>"
KEYRELEASE_SEQUENCE = "<KeyRelease>"
LISTUPDATE_SEQUENCE = "<B1-ButtonRelease>"
WINCONFIG_SEQUENCE = "<Configure>"
DOUBLECLICK_SEQUENCE = "<B1-Double-ButtonRelease>"
class AutoCompleteWindow:
def __init__(self, widget):
# The widget (Text) on which we place the AutoCompleteWindow
self.widget = widget
# The widgets we create
self.autocompletewindow = self.listbox = self.scrollbar = None
# The default foreground and background of a selection. Saved because
# they are changed to the regular colors of list items when the
# completion start is not a prefix of the selected completion
self.origselforeground = self.origselbackground = None
# The list of completions
self.completions = None
# A list with more completions, or None
self.morecompletions = None
# 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
# The index of the start of the completion
self.startindex = None
# The last typed start, used so that when the selection changes,
# the new start will be as close as possible to the last typed one.
self.lasttypedstart = None
# Do we have an indication that the user wants the completion window
# (for example, he clicked the list)
self.userwantswindow = None
# event ids
self.hideid = self.keypressid = self.listupdateid = self.winconfigid \
= self.keyreleaseid = self.doubleclickid = None
# Flag set if last keypress was a tab
self.lastkey_was_tab = False
def _change_start(self, newstart):
min_len = min(len(self.start), len(newstart))
i = 0
while i < min_len and self.start[i] == newstart[i]:
i += 1
if i < len(self.start):
self.widget.delete("%s+%dc" % (self.startindex, i),
"%s+%dc" % (self.startindex, len(self.start)))
if i < len(newstart):
self.widget.insert("%s+%dc" % (self.startindex, i),
newstart[i:])
self.start = newstart
def _binary_search(self, s):
"""Find the first index in self.completions where completions[i] is
greater or equal to s, or the last index if there is no such
one."""
i = 0; j = len(self.completions)
while j > i:
m = (i + j) // 2
if self.completions[m] >= s:
j = m
else:
i = m + 1
return min(i, len(self.completions)-1)
def _complete_string(self, s):
"""Assuming that s is the prefix of a string in self.completions,
return the longest string which is a prefix of all the strings which
s is a prefix of them. If s is not a prefix of a string, return s."""
first = self._binary_search(s)
if self.completions[first][:len(s)] != s:
# There is not even one completion which s is a prefix of.
return s
# Find the end of the range of completions where s is a prefix of.
i = first + 1
j = len(self.completions)
while j > i:
m = (i + j) // 2
if self.completions[m][:len(s)] != s:
j = m
else:
i = m + 1
last = i-1
if first == last: # only one possible completion
return self.completions[first]
# We should return the maximum prefix of first and last
first_comp = self.completions[first]
last_comp = self.completions[last]
min_len = min(len(first_comp), len(last_comp))
i = len(s)
while i < min_len and first_comp[i] == last_comp[i]:
i += 1
return first_comp[:i]
def _selection_changed(self):
"""Should be called when the selection of the Listbox has changed.
Updates the Listbox display and calls _change_start."""
cursel = int(self.listbox.curselection()[0])
self.listbox.see(cursel)
lts = self.lasttypedstart
selstart = self.completions[cursel]
if self._binary_search(lts) == cursel:
newstart = lts
else:
min_len = min(len(lts), len(selstart))
i = 0
while i < min_len and lts[i] == selstart[i]:
i += 1
newstart = selstart[:i]
self._change_start(newstart)
if self.completions[cursel][:len(self.start)] == self.start:
# start is a prefix of the selected completion
self.listbox.configure(selectbackground=self.origselbackground,
selectforeground=self.origselforeground)
else:
self.listbox.configure(selectbackground=self.listbox.cget("bg"),
selectforeground=self.listbox.cget("fg"))
# If there are more completions, show them, and call me again.
if self.morecompletions:
self.completions = self.morecompletions
self.morecompletions = None
self.listbox.delete(0, END)
for item in self.completions:
self.listbox.insert(END, item)
self.listbox.select_set(self._binary_search(self.start))
self._selection_changed()
def show_window(self, comp_lists, index, complete, mode, userWantsWin):
"""Show the autocomplete list, bind events.
If complete is True, complete the text, and if there is exactly one
matching completion, don't open a list."""
# Handle the start we already have
self.completions, self.morecompletions = comp_lists
self.mode = mode
self.startindex = self.widget.index(index)
self.start = self.widget.get(self.startindex, "insert")
if complete:
completed = self._complete_string(self.start)
start = self.start
self._change_start(completed)
i = self._binary_search(completed)
if self.completions[i] == completed and \
(i == len(self.completions)-1 or
self.completions[i+1][:len(completed)] != completed):
# There is exactly one matching completion
return completed == start
self.userwantswindow = userWantsWin
self.lasttypedstart = self.start
# Put widgets in place
self.autocompletewindow = acw = Toplevel(self.widget)
# Put it in a position so that it is not seen.
acw.wm_geometry("+10000+10000")
# Make it float
acw.wm_overrideredirect(1)
try:
# This command is only needed and available on Tk >= 8.4.0 for OSX
# Without it, call tips intrude on the typing process by grabbing
# the focus.
acw.tk.call("::tk::unsupported::MacWindowStyle", "style", acw._w,
"help", "noActivates")
except TclError:
pass
self.scrollbar = scrollbar = Scrollbar(acw, orient=VERTICAL)
self.listbox = listbox = Listbox(acw, yscrollcommand=scrollbar.set,
exportselection=False, bg="white")
for item in self.completions:
listbox.insert(END, item)
self.origselforeground = listbox.cget("selectforeground")
self.origselbackground = listbox.cget("selectbackground")
scrollbar.config(command=listbox.yview)
scrollbar.pack(side=RIGHT, fill=Y)
listbox.pack(side=LEFT, fill=BOTH, expand=True)
acw.lift() # work around bug in Tk 8.5.18+ (issue #24570)
# Initialize the listbox selection
self.listbox.select_set(self._binary_search(self.start))
self._selection_changed()
# bind events
self.hideaid = acw.bind(HIDE_VIRTUAL_EVENT_NAME, self.hide_event)
self.hidewid = self.widget.bind(HIDE_VIRTUAL_EVENT_NAME, self.hide_event)
acw.event_add(HIDE_VIRTUAL_EVENT_NAME, HIDE_FOCUS_OUT_SEQUENCE)
for seq in HIDE_SEQUENCES:
self.widget.event_add(HIDE_VIRTUAL_EVENT_NAME, seq)
self.keypressid = self.widget.bind(KEYPRESS_VIRTUAL_EVENT_NAME,
self.keypress_event)
for seq in KEYPRESS_SEQUENCES:
self.widget.event_add(KEYPRESS_VIRTUAL_EVENT_NAME, seq)
self.keyreleaseid = self.widget.bind(KEYRELEASE_VIRTUAL_EVENT_NAME,
self.keyrelease_event)
self.widget.event_add(KEYRELEASE_VIRTUAL_EVENT_NAME,KEYRELEASE_SEQUENCE)
self.listupdateid = listbox.bind(LISTUPDATE_SEQUENCE,
self.listselect_event)
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():
return
# Position the completion list window
text = self.widget
text.see(self.startindex)
x, y, cx, cy = text.bbox(self.startindex)
acw = self.autocompletewindow
acw_width, acw_height = acw.winfo_width(), acw.winfo_height()
text_width, text_height = text.winfo_width(), text.winfo_height()
new_x = text.winfo_rootx() + min(x, max(0, text_width - acw_width))
new_y = text.winfo_rooty() + y
if (text_height - (y + cy) >= acw_height # enough height below
or y < acw_height): # not enough height above
# place acw below current line
new_y += cy
else:
# place acw above current line
new_y -= acw_height
acw.wm_geometry("+%d+%d" % (new_x, new_y))
if platform.system().startswith('Windows'):
# See issue 15786. When on Windows platform, Tk will misbehave
# to call winconfig_event multiple times, we need to prevent this,
# otherwise mouse button double click will not be able to used.
acw.unbind(WINCONFIG_SEQUENCE, self.winconfigid)
self.winconfigid = None
def _hide_event_check(self):
if not self.autocompletewindow:
return
try:
if not self.autocompletewindow.focus_get():
self.hide_window()
except KeyError:
# See issue 734176, when user click on menu, acw.focus_get()
# will get KeyError.
self.hide_window()
def hide_event(self, event):
# Hide autocomplete list if it exists and does not have focus or
# mouse click on widget / text area.
if self.is_active():
if event.type == EventType.FocusOut:
# On Windows platform, it will need to delay the check for
# acw.focus_get() when click on acw, otherwise it will return
# None and close the window
self.widget.after(1, self._hide_event_check)
elif event.type == EventType.ButtonPress:
# ButtonPress event only bind to self.widget
self.hide_window()
def listselect_event(self, event):
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
cursel = int(self.listbox.curselection()[0])
self._change_start(self.completions[cursel])
self.hide_window()
def keypress_event(self, event):
if not self.is_active():
return None
keysym = event.keysym
if hasattr(event, "mc_state"):
state = event.mc_state
else:
state = 0
if keysym != "Tab":
self.lastkey_was_tab = False
if (len(keysym) == 1 or keysym in ("underscore", "BackSpace")
or (self.mode == COMPLETE_FILES and keysym in
("period", "minus"))) \
and not (state & ~MC_SHIFT):
# Normal editing of text
if len(keysym) == 1:
self._change_start(self.start + keysym)
elif keysym == "underscore":
self._change_start(self.start + '_')
elif keysym == "period":
self._change_start(self.start + '.')
elif keysym == "minus":
self._change_start(self.start + '-')
else:
# keysym == "BackSpace"
if len(self.start) == 0:
self.hide_window()
return None
self._change_start(self.start[:-1])
self.lasttypedstart = self.start
self.listbox.select_clear(0, int(self.listbox.curselection()[0]))
self.listbox.select_set(self._binary_search(self.start))
self._selection_changed()
return "break"
elif keysym == "Return":
self.complete()
self.hide_window()
return 'break'
elif (self.mode == COMPLETE_ATTRIBUTES and keysym in
("period", "space", "parenleft", "parenright", "bracketleft",
"bracketright")) or \
(self.mode == COMPLETE_FILES and keysym in
("slash", "backslash", "quotedbl", "apostrophe")) \
and not (state & ~MC_SHIFT):
# If start is a prefix of the selection, but is not '' when
# completing file names, put the whole
# selected completion. Anyway, close the list.
cursel = int(self.listbox.curselection()[0])
if self.completions[cursel][:len(self.start)] == self.start \
and (self.mode == COMPLETE_ATTRIBUTES or self.start):
self._change_start(self.completions[cursel])
self.hide_window()
return None
elif keysym in ("Home", "End", "Prior", "Next", "Up", "Down") and \
not state:
# Move the selection in the listbox
self.userwantswindow = True
cursel = int(self.listbox.curselection()[0])
if keysym == "Home":
newsel = 0
elif keysym == "End":
newsel = len(self.completions)-1
elif keysym in ("Prior", "Next"):
jump = self.listbox.nearest(self.listbox.winfo_height()) - \
self.listbox.nearest(0)
if keysym == "Prior":
newsel = max(0, cursel-jump)
else:
assert keysym == "Next"
newsel = min(len(self.completions)-1, cursel+jump)
elif keysym == "Up":
newsel = max(0, cursel-1)
else:
assert keysym == "Down"
newsel = min(len(self.completions)-1, cursel+1)
self.listbox.select_clear(cursel)
self.listbox.select_set(newsel)
self._selection_changed()
self._change_start(self.completions[newsel])
return "break"
elif (keysym == "Tab" and not state):
if self.lastkey_was_tab:
# two tabs in a row; insert current selection and close acw
cursel = int(self.listbox.curselection()[0])
self._change_start(self.completions[cursel])
self.hide_window()
return "break"
else:
# first tab; let AutoComplete handle the completion
self.userwantswindow = True
self.lastkey_was_tab = True
return None
elif any(s in keysym for s in ("Shift", "Control", "Alt",
"Meta", "Command", "Option")):
# A modifier key, so ignore
return None
elif event.char and event.char >= ' ':
# Regular character with a non-length-1 keycode
self._change_start(self.start + event.char)
self.lasttypedstart = self.start
self.listbox.select_clear(0, int(self.listbox.curselection()[0]))
self.listbox.select_set(self._binary_search(self.start))
self._selection_changed()
return "break"
else:
# Unknown event, close the window and let it through.
self.hide_window()
return None
def keyrelease_event(self, event):
if not self.is_active():
return
if self.widget.index("insert") != \
self.widget.index("%s+%dc" % (self.startindex, len(self.start))):
# If we didn't catch an event which moved the insert, close window
self.hide_window()
def is_active(self):
return self.autocompletewindow is not None
def complete(self):
self._change_start(self._complete_string(self.start))
# The selection doesn't change.
def hide_window(self):
if not self.is_active():
return
# unbind events
self.autocompletewindow.event_delete(HIDE_VIRTUAL_EVENT_NAME,
HIDE_FOCUS_OUT_SEQUENCE)
for seq in HIDE_SEQUENCES:
self.widget.event_delete(HIDE_VIRTUAL_EVENT_NAME, seq)
self.autocompletewindow.unbind(HIDE_VIRTUAL_EVENT_NAME, self.hideaid)
self.widget.unbind(HIDE_VIRTUAL_EVENT_NAME, self.hidewid)
self.hideaid = None
self.hidewid = None
for seq in KEYPRESS_SEQUENCES:
self.widget.event_delete(KEYPRESS_VIRTUAL_EVENT_NAME, seq)
self.widget.unbind(KEYPRESS_VIRTUAL_EVENT_NAME, self.keypressid)
self.keypressid = None
self.widget.event_delete(KEYRELEASE_VIRTUAL_EVENT_NAME,
KEYRELEASE_SEQUENCE)
self.widget.unbind(KEYRELEASE_VIRTUAL_EVENT_NAME, self.keyreleaseid)
self.keyreleaseid = None
self.listbox.unbind(LISTUPDATE_SEQUENCE, self.listupdateid)
self.listupdateid = None
if self.winconfigid:
self.autocompletewindow.unbind(WINCONFIG_SEQUENCE, self.winconfigid)
self.winconfigid = None
# Re-focusOn frame.text (See issue #15786)
self.widget.focus_set()
# destroy widgets
self.scrollbar.destroy()
self.scrollbar = None
self.listbox.destroy()
self.listbox = None
self.autocompletewindow.destroy()
self.autocompletewindow = None
if __name__ == '__main__':
from unittest import main
main('idlelib.idle_test.test_autocomplete_w', verbosity=2, exit=False)
# TODO: autocomplete/w htest here

View file

@ -0,0 +1,96 @@
'''Complete the current word before the cursor with words in the editor.
Each menu selection or shortcut key selection replaces the word with a
different word with the same prefix. The search for matches begins
before the target and moves toward the top of the editor. It then starts
after the cursor and moves down. It then returns to the original word and
the cycle starts again.
Changing the current text line or leaving the cursor in a different
place before requesting the next selection causes AutoExpand to reset
its state.
There is only one instance of Autoexpand.
'''
import re
import string
class AutoExpand:
wordchars = string.ascii_letters + string.digits + "_"
def __init__(self, editwin):
self.text = editwin.text
self.bell = self.text.bell
self.state = None
def expand_word_event(self, event):
"Replace the current word with the next expansion."
curinsert = self.text.index("insert")
curline = self.text.get("insert linestart", "insert lineend")
if not self.state:
words = self.getwords()
index = 0
else:
words, index, insert, line = self.state
if insert != curinsert or line != curline:
words = self.getwords()
index = 0
if not words:
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.bell() # Warn we cycled around
self.text.insert("insert", newword)
curinsert = self.text.index("insert")
curline = self.text.get("insert linestart", "insert lineend")
self.state = words, index, curinsert, curline
return "break"
def getwords(self):
"Return a list of words that match the prefix before the cursor."
word = self.getprevword()
if not word:
return []
before = self.text.get("1.0", "insert wordstart")
wbefore = re.findall(r"\b" + word + r"\w+\b", before)
del before
after = self.text.get("insert wordend", "end")
wafter = re.findall(r"\b" + word + r"\w+\b", after)
del after
if not wbefore and not wafter:
return []
words = []
dict = {}
# search backwards through words before
wbefore.reverse()
for w in wbefore:
if dict.get(w):
continue
words.append(w)
dict[w] = w
# search onwards through words after
for w in wafter:
if dict.get(w):
continue
words.append(w)
dict[w] = w
words.append(word)
return words
def getprevword(self):
"Return the word prefix before the cursor."
line = self.text.get("insert linestart", "insert")
i = len(line)
while i > 0 and line[i-1] in self.wordchars:
i = i-1
return line[i:]
if __name__ == '__main__':
from unittest import main
main('idlelib.idle_test.test_autoexpand', verbosity=2)

View file

@ -0,0 +1,248 @@
"""Module browser.
XXX TO DO:
- reparse when source changed (maybe just a button would be OK?)
(or recheck on window popup)
- add popup menu with more options (e.g. doc strings, base classes, imports)
- add base classes to class browser tree
- finish removing limitation to x.py files (ModuleBrowserTreeItem)
"""
import os
from idlelib import _pyclbr as pyclbr
import sys
from idlelib.config import idleConf
from idlelib import pyshell
from idlelib.tree import TreeNode, TreeItem, ScrolledCanvas
from idlelib.window import ListedToplevel
file_open = None # Method...Item and Class...Item use this.
# Normally pyshell.flist.open, but there is no pyshell.flist for htest.
def transform_children(child_dict, modname=None):
"""Transform a child dictionary to an ordered sequence of objects.
The dictionary maps names to pyclbr information objects.
Filter out imported objects.
Augment class names with bases.
Sort objects by line number.
The current tree only calls this once per child_dic as it saves
TreeItems once created. A future tree and tests might violate this,
so a check prevents multiple in-place augmentations.
"""
obs = [] # Use list since values should already be sorted.
for key, obj in child_dict.items():
if modname is None or obj.module == modname:
if hasattr(obj, 'super') and obj.super and obj.name == key:
# If obj.name != key, it has already been suffixed.
supers = []
for sup in obj.super:
if type(sup) is type(''):
sname = sup
else:
sname = sup.name
if sup.module != obj.module:
sname = f'{sup.module}.{sname}'
supers.append(sname)
obj.name += '({})'.format(', '.join(supers))
obs.append(obj)
return sorted(obs, key=lambda o: o.lineno)
class ModuleBrowser:
"""Browse module classes and functions in IDLE.
"""
# This class is also the base class for pathbrowser.PathBrowser.
# Init and close are inherited, other methods are overridden.
# PathBrowser.__init__ does not call __init__ below.
def __init__(self, master, path, *, _htest=False, _utest=False):
"""Create a window for browsing a module's structure.
Args:
master: parent for widgets.
path: full path of file to browse.
_htest - bool; change box location when running htest.
-utest - bool; suppress contents when running unittest.
Global variables:
file_open: Function used for opening a file.
Instance variables:
name: Module name.
file: Full path and module with .py extension. Used in
creating ModuleBrowserTreeItem as the rootnode for
the tree and subsequently in the children.
"""
self.master = master
self.path = path
self._htest = _htest
self._utest = _utest
self.init()
def close(self, event=None):
"Dismiss the window and the tree nodes."
self.top.destroy()
self.node.destroy()
def init(self):
"Create browser tkinter widgets, including the tree."
global file_open
root = self.master
flist = (pyshell.flist if not (self._htest or self._utest)
else pyshell.PyShellFileList(root))
file_open = flist.open
pyclbr._modules.clear()
# create top
self.top = top = ListedToplevel(root)
top.protocol("WM_DELETE_WINDOW", self.close)
top.bind("<Escape>", self.close)
if self._htest: # place dialog below parent if running htest
top.geometry("+%d+%d" %
(root.winfo_rootx(), root.winfo_rooty() + 200))
self.settitle()
top.focus_set()
# create scrolled canvas
theme = idleConf.CurrentTheme()
background = idleConf.GetHighlight(theme, 'normal')['background']
sc = ScrolledCanvas(top, bg=background, highlightthickness=0,
takefocus=1)
sc.frame.pack(expand=1, fill="both")
item = self.rootnode()
self.node = node = TreeNode(sc.canvas, None, item)
if not self._utest:
node.update()
node.expand()
def settitle(self):
"Set the window title."
self.top.wm_title("Module Browser - " + os.path.basename(self.path))
self.top.wm_iconname("Module Browser")
def rootnode(self):
"Return a ModuleBrowserTreeItem as the root of the tree."
return ModuleBrowserTreeItem(self.path)
class ModuleBrowserTreeItem(TreeItem):
"""Browser tree for Python module.
Uses TreeItem as the basis for the structure of the tree.
Used by both browsers.
"""
def __init__(self, file):
"""Create a TreeItem for the file.
Args:
file: Full path and module name.
"""
self.file = file
def GetText(self):
"Return the module name as the text string to display."
return os.path.basename(self.file)
def GetIconName(self):
"Return the name of the icon to display."
return "python"
def GetSubList(self):
"Return ChildBrowserTreeItems for children."
return [ChildBrowserTreeItem(obj) for obj in self.listchildren()]
def OnDoubleClick(self):
"Open a module in an editor window when double clicked."
if os.path.normcase(self.file[-3:]) != ".py":
return
if not os.path.exists(self.file):
return
file_open(self.file)
def IsExpandable(self):
"Return True if Python (.py) file."
return os.path.normcase(self.file[-3:]) == ".py"
def listchildren(self):
"Return sequenced classes and functions in the module."
dir, base = os.path.split(self.file)
name, ext = os.path.splitext(base)
if os.path.normcase(ext) != ".py":
return []
try:
tree = pyclbr.readmodule_ex(name, [dir] + sys.path)
except ImportError:
return []
return transform_children(tree, name)
class ChildBrowserTreeItem(TreeItem):
"""Browser tree for child nodes within the module.
Uses TreeItem as the basis for the structure of the tree.
"""
def __init__(self, obj):
"Create a TreeItem for a pyclbr class/function object."
self.obj = obj
self.name = obj.name
self.isfunction = isinstance(obj, pyclbr.Function)
def GetText(self):
"Return the name of the function/class to display."
name = self.name
if self.isfunction:
return "def " + name + "(...)"
else:
return "class " + name
def GetIconName(self):
"Return the name of the icon to display."
if self.isfunction:
return "python"
else:
return "folder"
def IsExpandable(self):
"Return True if self.obj has nested objects."
return self.obj.children != {}
def GetSubList(self):
"Return ChildBrowserTreeItems for children."
return [ChildBrowserTreeItem(obj)
for obj in transform_children(self.obj.children)]
def OnDoubleClick(self):
"Open module with file_open and position to lineno."
try:
edit = file_open(self.obj.file)
edit.gotoline(self.obj.lineno)
except (OSError, AttributeError):
pass
def _module_browser(parent): # htest #
if len(sys.argv) > 1: # If pass file on command line.
file = sys.argv[1]
else:
file = __file__
# Add nested objects for htest.
class Nested_in_func(TreeNode):
def nested_in_class(): pass
def closure():
class Nested_in_closure: pass
ModuleBrowser(parent, file, _htest=True)
if __name__ == "__main__":
if len(sys.argv) == 1: # If pass file on command line, unittest fails.
from unittest import main
main('idlelib.idle_test.test_browser', verbosity=2, exit=False)
from idlelib.idle_test.htest import run
run(_module_browser)

View file

@ -0,0 +1,178 @@
"""Pop up a reminder of how to call a function.
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 inspect
import re
import sys
import textwrap
import types
from idlelib import calltip_w
from idlelib.hyperparser import HyperParser
import __main__
class Calltip:
def __init__(self, editwin=None):
if editwin is None: # subprocess and test
self.editwin = None
else:
self.editwin = editwin
self.text = editwin.text
self.active_calltip = None
self._calltip_window = self._make_tk_calltip_window
def close(self):
self._calltip_window = None
def _make_tk_calltip_window(self):
# See __init__ for usage
return calltip_w.CalltipWindow(self.text)
def _remove_calltip_window(self, event=None):
if self.active_calltip:
self.active_calltip.hidetip()
self.active_calltip = None
def force_open_calltip_event(self, event):
"The user selected the menu entry or hotkey, open the tip."
self.open_calltip(True)
return "break"
def try_open_calltip_event(self, event):
"""Happens when it would be nice to open a calltip, but not really
necessary, for example after an opening bracket, so function calls
won't be made.
"""
self.open_calltip(False)
def refresh_calltip_event(self, event):
if self.active_calltip and self.active_calltip.tipwindow:
self.open_calltip(False)
def open_calltip(self, evalfuncs):
self._remove_calltip_window()
hp = HyperParser(self.editwin, "insert")
sur_paren = hp.get_surrounding_brackets('(')
if not sur_paren:
return
hp.set_index(sur_paren[0])
expression = hp.get_expression()
if not expression:
return
if not evalfuncs and (expression.find('(') != -1):
return
argspec = self.fetch_tip(expression)
if not argspec:
return
self.active_calltip = self._calltip_window()
self.active_calltip.showtip(argspec, sur_paren[0], sur_paren[1])
def fetch_tip(self, expression):
"""Return the argument list and docstring of a function or class.
If there is a Python subprocess, get the calltip there. Otherwise,
either this fetch_tip() is running in the subprocess or it was
called in an IDLE running without the subprocess.
The subprocess environment is that of the most recently run script. If
two unrelated modules are being edited some calltips in the current
module may be inoperative if the module was not the last to run.
To find methods, fetch_tip must be fed a fully qualified name.
"""
try:
rpcclt = self.editwin.flist.pyshell.interp.rpcclt
except AttributeError:
rpcclt = None
if rpcclt:
return rpcclt.remotecall("exec", "get_the_calltip",
(expression,), {})
else:
return get_argspec(get_entity(expression))
def get_entity(expression):
"""Return the object corresponding to expression evaluated
in a namespace spanning sys.modules and __main.dict__.
"""
if expression:
namespace = sys.modules.copy()
namespace.update(__main__.__dict__)
try:
return eval(expression, namespace)
except BaseException:
# An uncaught exception closes idle, and eval can raise any
# exception, especially if user classes are involved.
return None
# The following are used in get_argspec and some in tests
_MAX_COLS = 85
_MAX_LINES = 5 # enough for bytes
_INDENT = ' '*4 # for wrapped signatures
_first_param = re.compile(r'(?<=\()\w*\,?\s*')
_default_callable_argspec = "See source or doc"
_invalid_method = "invalid method signature"
_argument_positional = "\n['/' marks preceding arguments as positional-only]\n"
def get_argspec(ob):
'''Return a string describing the signature of a callable object, or ''.
For Python-coded functions and methods, the first line is introspected.
Delete 'self' parameter for classes (.__init__) and bound methods.
The next lines are the first lines of the doc string up to the first
empty line or _MAX_LINES. For builtins, this typically includes
the arguments in addition to the return value.
'''
argspec = default = ""
try:
ob_call = ob.__call__
except BaseException:
return default
fob = ob_call if isinstance(ob_call, types.MethodType) else ob
try:
argspec = str(inspect.signature(fob))
except ValueError as err:
msg = str(err)
if msg.startswith(_invalid_method):
return _invalid_method
if '/' in argspec:
"""Using AC's positional argument should add the explain"""
argspec += _argument_positional
if isinstance(fob, type) and argspec == '()':
"""fob with no argument, use default callable argspec"""
argspec = _default_callable_argspec
lines = (textwrap.wrap(argspec, _MAX_COLS, subsequent_indent=_INDENT)
if len(argspec) > _MAX_COLS else [argspec] if argspec else [])
if isinstance(ob_call, types.MethodType):
doc = ob_call.__doc__
else:
doc = getattr(ob, "__doc__", "")
if doc:
for line in doc.split('\n', _MAX_LINES)[:_MAX_LINES]:
line = line.strip()
if not line:
break
if len(line) > _MAX_COLS:
line = line[: _MAX_COLS - 3] + '...'
lines.append(line)
argspec = '\n'.join(lines)
if not argspec:
argspec = _default_callable_argspec
return argspec
if __name__ == '__main__':
from unittest import main
main('idlelib.idle_test.test_calltip', verbosity=2)

View file

@ -0,0 +1,200 @@
"""A call-tip window class for Tkinter/IDLE.
After tooltip.py, which uses ideas gleaned from PySol.
Used by calltip.py.
"""
from tkinter import Label, LEFT, SOLID, TclError
from idlelib.tooltip import TooltipBase
HIDE_EVENT = "<<calltipwindow-hide>>"
HIDE_SEQUENCES = ("<Key-Escape>", "<FocusOut>")
CHECKHIDE_EVENT = "<<calltipwindow-checkhide>>"
CHECKHIDE_SEQUENCES = ("<KeyRelease>", "<ButtonRelease>")
CHECKHIDE_TIME = 100 # milliseconds
MARK_RIGHT = "calltipwindowregion_right"
class CalltipWindow(TooltipBase):
"""A call-tip widget for tkinter text widgets."""
def __init__(self, text_widget):
"""Create a call-tip; shown by showtip().
text_widget: a Text widget with code for which call-tips are desired
"""
# Note: The Text widget will be accessible as self.anchor_widget
super(CalltipWindow, self).__init__(text_widget)
self.label = self.text = None
self.parenline = self.parencol = self.lastline = None
self.hideid = self.checkhideid = None
self.checkhide_after_id = None
def get_position(self):
"""Choose the position of the call-tip."""
curline = int(self.anchor_widget.index("insert").split('.')[0])
if curline == self.parenline:
anchor_index = (self.parenline, self.parencol)
else:
anchor_index = (curline, 0)
box = self.anchor_widget.bbox("%d.%d" % anchor_index)
if not box:
box = list(self.anchor_widget.bbox("insert"))
# align to left of window
box[0] = 0
box[2] = 0
return box[0] + 2, box[1] + box[3]
def position_window(self):
"Reposition the window if needed."
curline = int(self.anchor_widget.index("insert").split('.')[0])
if curline == self.lastline:
return
self.lastline = curline
self.anchor_widget.see("insert")
super(CalltipWindow, self).position_window()
def showtip(self, text, parenleft, parenright):
"""Show the call-tip, bind events which will close it and reposition it.
text: the text to display in the call-tip
parenleft: index of the opening parenthesis in the text widget
parenright: index of the closing parenthesis in the text widget,
or the end of the line if there is no closing parenthesis
"""
# Only called in calltip.Calltip, where lines are truncated
self.text = text
if self.tipwindow or not self.text:
return
self.anchor_widget.mark_set(MARK_RIGHT, parenright)
self.parenline, self.parencol = map(
int, self.anchor_widget.index(parenleft).split("."))
super(CalltipWindow, self).showtip()
self._bind_events()
def showcontents(self):
"""Create the call-tip widget."""
self.label = Label(self.tipwindow, text=self.text, justify=LEFT,
background="#ffffe0", relief=SOLID, borderwidth=1,
font=self.anchor_widget['font'])
self.label.pack()
def checkhide_event(self, event=None):
"""Handle CHECK_HIDE_EVENT: call hidetip or reschedule."""
if not self.tipwindow:
# If the event was triggered by the same event that unbound
# this function, the function will be called nevertheless,
# so do nothing in this case.
return None
# Hide the call-tip if the insertion cursor moves outside of the
# parenthesis.
curline, curcol = map(int, self.anchor_widget.index("insert").split('.'))
if curline < self.parenline or \
(curline == self.parenline and curcol <= self.parencol) or \
self.anchor_widget.compare("insert", ">", MARK_RIGHT):
self.hidetip()
return "break"
# Not hiding the call-tip.
self.position_window()
# Re-schedule this function to be called again in a short while.
if self.checkhide_after_id is not None:
self.anchor_widget.after_cancel(self.checkhide_after_id)
self.checkhide_after_id = \
self.anchor_widget.after(CHECKHIDE_TIME, self.checkhide_event)
return None
def hide_event(self, event):
"""Handle HIDE_EVENT by calling hidetip."""
if not self.tipwindow:
# See the explanation in checkhide_event.
return None
self.hidetip()
return "break"
def hidetip(self):
"""Hide the call-tip."""
if not self.tipwindow:
return
try:
self.label.destroy()
except TclError:
pass
self.label = None
self.parenline = self.parencol = self.lastline = None
try:
self.anchor_widget.mark_unset(MARK_RIGHT)
except TclError:
pass
try:
self._unbind_events()
except (TclError, ValueError):
# ValueError may be raised by MultiCall
pass
super(CalltipWindow, self).hidetip()
def _bind_events(self):
"""Bind event handlers."""
self.checkhideid = self.anchor_widget.bind(CHECKHIDE_EVENT,
self.checkhide_event)
for seq in CHECKHIDE_SEQUENCES:
self.anchor_widget.event_add(CHECKHIDE_EVENT, seq)
self.anchor_widget.after(CHECKHIDE_TIME, self.checkhide_event)
self.hideid = self.anchor_widget.bind(HIDE_EVENT,
self.hide_event)
for seq in HIDE_SEQUENCES:
self.anchor_widget.event_add(HIDE_EVENT, seq)
def _unbind_events(self):
"""Unbind event handlers."""
for seq in CHECKHIDE_SEQUENCES:
self.anchor_widget.event_delete(CHECKHIDE_EVENT, seq)
self.anchor_widget.unbind(CHECKHIDE_EVENT, self.checkhideid)
self.checkhideid = None
for seq in HIDE_SEQUENCES:
self.anchor_widget.event_delete(HIDE_EVENT, seq)
self.anchor_widget.unbind(HIDE_EVENT, self.hideid)
self.hideid = None
def _calltip_window(parent): # htest #
from tkinter import Toplevel, Text, LEFT, BOTH
top = Toplevel(parent)
top.title("Test call-tips")
x, y = map(int, parent.geometry().split('+')[1:])
top.geometry("250x100+%d+%d" % (x + 175, y + 150))
text = Text(top)
text.pack(side=LEFT, fill=BOTH, expand=1)
text.insert("insert", "string.split")
top.update()
calltip = CalltipWindow(text)
def calltip_show(event):
calltip.showtip("(s='Hello world')", "insert", "end")
def calltip_hide(event):
calltip.hidetip()
text.event_add("<<calltip-show>>", "(")
text.event_add("<<calltip-hide>>", ")")
text.bind("<<calltip-show>>", calltip_show)
text.bind("<<calltip-hide>>", calltip_hide)
text.focus_set()
if __name__ == '__main__':
from unittest import main
main('idlelib.idle_test.test_calltip_w', verbosity=2, exit=False)
from idlelib.idle_test.htest import run
run(_calltip_window)

View file

@ -0,0 +1,240 @@
"""codecontext - 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 maxlines
variable in the codecontext section of config-extensions.def. Lines which do
not open blocks are not shown in the context hints pane.
"""
import re
from sys import maxsize as INFINITY
import tkinter
from tkinter.constants import TOP, X, SUNKEN
from idlelib.config import idleConf
BLOCKOPENERS = {"class", "def", "elif", "else", "except", "finally", "for",
"if", "try", "while", "with", "async"}
UPDATEINTERVAL = 100 # millisec
CONFIGUPDATEINTERVAL = 1000 # millisec
def get_spaces_firstword(codeline, c=re.compile(r"^(\s*)(\w*)")):
"Extract the beginning whitespace and first word from codeline."
return c.match(codeline).groups()
def get_line_info(codeline):
"""Return tuple of (line indent value, codeline, block start keyword).
The indentation of empty lines (or comment lines) is INFINITY.
If the line does not start a block, the keyword value is False.
"""
spaces, firstword = get_spaces_firstword(codeline)
indent = len(spaces)
if len(codeline) == indent or codeline[indent] == '#':
indent = INFINITY
opener = firstword in BLOCKOPENERS and firstword
return indent, codeline, opener
class CodeContext:
"Display block context above the edit window."
def __init__(self, editwin):
"""Initialize settings for context block.
editwin is the Editor window for the context block.
self.text is the editor window text widget.
self.textfont is the editor window font.
self.context displays the code context text above the editor text.
Initially None, it is toggled via <<toggle-code-context>>.
self.topvisible is the number of the top text line displayed.
self.info is a list of (line number, indent level, line text,
block keyword) tuples for the block structure above topvisible.
self.info[0] is initialized with a 'dummy' line which
starts the toplevel 'block' of the module.
self.t1 and self.t2 are two timer events on the editor text widget to
monitor for changes to the context text or editor font.
"""
self.editwin = editwin
self.text = editwin.text
self.textfont = self.text["font"]
self.contextcolors = CodeContext.colors
self.context = None
self.topvisible = 1
self.info = [(0, -1, "", False)]
# Start two update cycles, one for context lines, one for font changes.
self.t1 = self.text.after(UPDATEINTERVAL, self.timer_event)
self.t2 = self.text.after(CONFIGUPDATEINTERVAL, self.config_timer_event)
@classmethod
def reload(cls):
"Load class variables from config."
cls.context_depth = idleConf.GetOption("extensions", "CodeContext",
"maxlines", type="int", default=15)
cls.colors = idleConf.GetHighlight(idleConf.CurrentTheme(), 'context')
def __del__(self):
"Cancel scheduled events."
try:
self.text.after_cancel(self.t1)
self.text.after_cancel(self.t2)
except:
pass
def toggle_code_context_event(self, event=None):
"""Toggle code context display.
If self.context doesn't exist, create it to match the size of the editor
window text (toggle on). If it does exist, destroy it (toggle off).
Return 'break' to complete the processing of the binding.
"""
if not self.context:
# Calculate the border width and horizontal padding required to
# align the context with the text in the main Text widget.
#
# All values are passed through getint(), since some
# values may be pixel objects, which can't simply be added to ints.
widgets = self.editwin.text, self.editwin.text_frame
# Calculate the required horizontal padding and border width.
padx = 0
border = 0
for widget in widgets:
padx += widget.tk.getint(widget.pack_info()['padx'])
padx += widget.tk.getint(widget.cget('padx'))
border += widget.tk.getint(widget.cget('border'))
self.context = tkinter.Text(
self.editwin.top, font=self.textfont,
bg=self.contextcolors['background'],
fg=self.contextcolors['foreground'],
height=1,
width=1, # Don't request more than we get.
padx=padx, border=border, relief=SUNKEN, state='disabled')
self.context.bind('<ButtonRelease-1>', self.jumptoline)
# Pack the context widget before and above the text_frame widget,
# thus ensuring that it will appear directly above text_frame.
self.context.pack(side=TOP, fill=X, expand=False,
before=self.editwin.text_frame)
else:
self.context.destroy()
self.context = None
return "break"
def get_context(self, new_topvisible, stopline=1, stopindent=0):
"""Return a list of block line tuples and the 'last' indent.
The tuple fields are (linenum, indent, text, opener).
The list represents header lines from new_topvisible back to
stopline with successively shorter indents > stopindent.
The list is returned ordered by line number.
Last indent returned is the smallest indent observed.
"""
assert stopline > 0
lines = []
# The indentation level we are currently in.
lastindent = INFINITY
# For a line to be interesting, it must begin with a block opening
# keyword, and have less indentation than lastindent.
for linenum in range(new_topvisible, stopline-1, -1):
codeline = self.text.get(f'{linenum}.0', f'{linenum}.end')
indent, text, opener = get_line_info(codeline)
if indent < lastindent:
lastindent = indent
if opener in ("else", "elif"):
# Also show the if statement.
lastindent += 1
if opener and linenum < new_topvisible and indent >= stopindent:
lines.append((linenum, indent, text, opener))
if lastindent <= stopindent:
break
lines.reverse()
return lines, lastindent
def update_code_context(self):
"""Update context information and lines visible in the context pane.
No update is done if the text hasn't been scrolled. If the text
was scrolled, the lines that should be shown in the context will
be retrieved and the context area will be updated with the code,
up to the number of maxlines.
"""
new_topvisible = int(self.text.index("@0,0").split('.')[0])
if self.topvisible == new_topvisible: # Haven't scrolled.
return
if self.topvisible < new_topvisible: # Scroll down.
lines, lastindent = self.get_context(new_topvisible,
self.topvisible)
# Retain only context info applicable to the region
# between topvisible and new_topvisible.
while self.info[-1][1] >= lastindent:
del self.info[-1]
else: # self.topvisible > new_topvisible: # Scroll up.
stopindent = self.info[-1][1] + 1
# Retain only context info associated
# with lines above new_topvisible.
while self.info[-1][0] >= new_topvisible:
stopindent = self.info[-1][1]
del self.info[-1]
lines, lastindent = self.get_context(new_topvisible,
self.info[-1][0]+1,
stopindent)
self.info.extend(lines)
self.topvisible = new_topvisible
# Last context_depth context lines.
context_strings = [x[2] for x in self.info[-self.context_depth:]]
showfirst = 0 if context_strings[0] else 1
# Update widget.
self.context['height'] = len(context_strings) - showfirst
self.context['state'] = 'normal'
self.context.delete('1.0', 'end')
self.context.insert('end', '\n'.join(context_strings[showfirst:]))
self.context['state'] = 'disabled'
def jumptoline(self, event=None):
"Show clicked context line at top of editor."
lines = len(self.info)
if lines == 1: # No context lines are showing.
newtop = 1
else:
# Line number clicked.
contextline = int(float(self.context.index('insert')))
# Lines not displayed due to maxlines.
offset = max(1, lines - self.context_depth) - 1
newtop = self.info[offset + contextline][0]
self.text.yview(f'{newtop}.0')
self.update_code_context()
def timer_event(self):
"Event on editor text widget triggered every UPDATEINTERVAL ms."
if self.context:
self.update_code_context()
self.t1 = self.text.after(UPDATEINTERVAL, self.timer_event)
def config_timer_event(self):
"Event on editor text widget triggered every CONFIGUPDATEINTERVAL ms."
newtextfont = self.text["font"]
if (self.context and (newtextfont != self.textfont or
CodeContext.colors != self.contextcolors)):
self.textfont = newtextfont
self.contextcolors = CodeContext.colors
self.context["font"] = self.textfont
self.context['background'] = self.contextcolors['background']
self.context['foreground'] = self.contextcolors['foreground']
self.t2 = self.text.after(CONFIGUPDATEINTERVAL, self.config_timer_event)
CodeContext.reload()
if __name__ == "__main__":
from unittest import main
main('idlelib.idle_test.test_codecontext', verbosity=2, exit=False)
# Add htest.

View file

@ -0,0 +1,296 @@
import builtins
import keyword
import re
import time
from idlelib.config import idleConf
from idlelib.delegator import Delegator
DEBUG = False
def any(name, alternates):
"Return a named group pattern matching list of alternates."
return "(?P<%s>" % name + "|".join(alternates) + ")"
def make_pat():
kw = r"\b" + any("KEYWORD", keyword.kwlist + ['async', 'await']) + r"\b"
builtinlist = [str(name) for name in dir(builtins)
if not name.startswith('_') and \
name not in keyword.kwlist]
builtin = r"([^.'\"\\#]\b|^)" + any("BUILTIN", builtinlist) + r"\b"
comment = any("COMMENT", [r"#[^\n]*"])
stringprefix = r"(?i:r|u|f|fr|rf|b|br|rb)?"
sqstring = stringprefix + r"'[^'\\\n]*(\\.[^'\\\n]*)*'?"
dqstring = stringprefix + r'"[^"\\\n]*(\\.[^"\\\n]*)*"?'
sq3string = stringprefix + r"'''[^'\\]*((\\.|'(?!''))[^'\\]*)*(''')?"
dq3string = stringprefix + r'"""[^"\\]*((\\.|"(?!""))[^"\\]*)*(""")?'
string = any("STRING", [sq3string, dq3string, sqstring, dqstring])
return kw + "|" + builtin + "|" + comment + "|" + string +\
"|" + any("SYNC", [r"\n"])
prog = re.compile(make_pat(), re.S)
idprog = re.compile(r"\s+(\w+)", re.S)
def color_config(text):
"""Set color options of Text widget.
If ColorDelegator is used, this should be called first.
"""
# Called from htest, TextFrame, Editor, and Turtledemo.
# Not automatic because ColorDelegator does not know 'text'.
theme = idleConf.CurrentTheme()
normal_colors = idleConf.GetHighlight(theme, 'normal')
cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
select_colors = idleConf.GetHighlight(theme, 'hilite')
text.config(
foreground=normal_colors['foreground'],
background=normal_colors['background'],
insertbackground=cursor_color,
selectforeground=select_colors['foreground'],
selectbackground=select_colors['background'],
inactiveselectbackground=select_colors['background'], # new in 8.5
)
class ColorDelegator(Delegator):
def __init__(self):
Delegator.__init__(self)
self.prog = prog
self.idprog = idprog
self.LoadTagDefs()
def setdelegate(self, delegate):
if self.delegate is not None:
self.unbind("<<toggle-auto-coloring>>")
Delegator.setdelegate(self, delegate)
if delegate is not None:
self.config_colors()
self.bind("<<toggle-auto-coloring>>", self.toggle_colorize_event)
self.notify_range("1.0", "end")
else:
# No delegate - stop any colorizing
self.stop_colorizing = True
self.allow_colorizing = False
def config_colors(self):
for tag, cnf in self.tagdefs.items():
if cnf:
self.tag_configure(tag, **cnf)
self.tag_raise('sel')
def LoadTagDefs(self):
theme = idleConf.CurrentTheme()
self.tagdefs = {
"COMMENT": idleConf.GetHighlight(theme, "comment"),
"KEYWORD": idleConf.GetHighlight(theme, "keyword"),
"BUILTIN": idleConf.GetHighlight(theme, "builtin"),
"STRING": idleConf.GetHighlight(theme, "string"),
"DEFINITION": idleConf.GetHighlight(theme, "definition"),
"SYNC": {'background':None,'foreground':None},
"TODO": {'background':None,'foreground':None},
"ERROR": idleConf.GetHighlight(theme, "error"),
# The following is used by ReplaceDialog:
"hit": idleConf.GetHighlight(theme, "hit"),
}
if DEBUG: print('tagdefs',self.tagdefs)
def insert(self, index, chars, tags=None):
index = self.index(index)
self.delegate.insert(index, chars, tags)
self.notify_range(index, index + "+%dc" % len(chars))
def delete(self, index1, index2=None):
index1 = self.index(index1)
self.delegate.delete(index1, index2)
self.notify_range(index1)
after_id = None
allow_colorizing = True
colorizing = False
def notify_range(self, index1, index2=None):
self.tag_add("TODO", index1, index2)
if self.after_id:
if DEBUG: print("colorizing already scheduled")
return
if self.colorizing:
self.stop_colorizing = True
if DEBUG: print("stop colorizing")
if self.allow_colorizing:
if DEBUG: print("schedule colorizing")
self.after_id = self.after(1, self.recolorize)
close_when_done = None # Window to be closed when done colorizing
def close(self, close_when_done=None):
if self.after_id:
after_id = self.after_id
self.after_id = None
if DEBUG: print("cancel scheduled recolorizer")
self.after_cancel(after_id)
self.allow_colorizing = False
self.stop_colorizing = True
if close_when_done:
if not self.colorizing:
close_when_done.destroy()
else:
self.close_when_done = close_when_done
def toggle_colorize_event(self, event):
if self.after_id:
after_id = self.after_id
self.after_id = None
if DEBUG: print("cancel scheduled recolorizer")
self.after_cancel(after_id)
if self.allow_colorizing and self.colorizing:
if DEBUG: print("stop colorizing")
self.stop_colorizing = True
self.allow_colorizing = not self.allow_colorizing
if self.allow_colorizing and not self.colorizing:
self.after_id = self.after(1, self.recolorize)
if DEBUG:
print("auto colorizing turned",\
self.allow_colorizing and "on" or "off")
return "break"
def recolorize(self):
self.after_id = None
if not self.delegate:
if DEBUG: print("no delegate")
return
if not self.allow_colorizing:
if DEBUG: print("auto colorizing is off")
return
if self.colorizing:
if DEBUG: print("already colorizing")
return
try:
self.stop_colorizing = False
self.colorizing = True
if DEBUG: print("colorizing...")
t0 = time.perf_counter()
self.recolorize_main()
t1 = time.perf_counter()
if DEBUG: print("%.3f seconds" % (t1-t0))
finally:
self.colorizing = False
if self.allow_colorizing and self.tag_nextrange("TODO", "1.0"):
if DEBUG: print("reschedule colorizing")
self.after_id = self.after(1, self.recolorize)
if self.close_when_done:
top = self.close_when_done
self.close_when_done = None
top.destroy()
def recolorize_main(self):
next = "1.0"
while True:
item = self.tag_nextrange("TODO", next)
if not item:
break
head, tail = item
self.tag_remove("SYNC", head, tail)
item = self.tag_prevrange("SYNC", head)
if item:
head = item[1]
else:
head = "1.0"
chars = ""
next = head
lines_to_get = 1
ok = False
while not ok:
mark = next
next = self.index(mark + "+%d lines linestart" %
lines_to_get)
lines_to_get = min(lines_to_get * 2, 100)
ok = "SYNC" in self.tag_names(next + "-1c")
line = self.get(mark, next)
##print head, "get", mark, next, "->", repr(line)
if not line:
return
for tag in self.tagdefs:
self.tag_remove(tag, mark, next)
chars = chars + line
m = self.prog.search(chars)
while m:
for key, value in m.groupdict().items():
if value:
a, b = m.span(key)
self.tag_add(key,
head + "+%dc" % a,
head + "+%dc" % b)
if value in ("def", "class"):
m1 = self.idprog.match(chars, b)
if m1:
a, b = m1.span(1)
self.tag_add("DEFINITION",
head + "+%dc" % a,
head + "+%dc" % b)
m = self.prog.search(chars, m.end())
if "SYNC" in self.tag_names(next + "-1c"):
head = next
chars = ""
else:
ok = False
if not ok:
# We're in an inconsistent state, and the call to
# update may tell us to stop. It may also change
# the correct value for "next" (since this is a
# line.col string, not a true mark). So leave a
# crumb telling the next invocation to resume here
# in case update tells us to leave.
self.tag_add("TODO", next)
self.update()
if self.stop_colorizing:
if DEBUG: print("colorizing stopped")
return
def removecolors(self):
for tag in self.tagdefs:
self.tag_remove(tag, "1.0", "end")
def _color_delegator(parent): # htest #
from tkinter import Toplevel, Text
from idlelib.percolator import Percolator
top = Toplevel(parent)
top.title("Test ColorDelegator")
x, y = map(int, parent.geometry().split('+')[1:])
top.geometry("700x250+%d+%d" % (x + 20, y + 175))
source = (
"if True: int ('1') # keyword, builtin, string, comment\n"
"elif False: print(0)\n"
"else: float(None)\n"
"if iF + If + IF: 'keyword matching must respect case'\n"
"if'': x or'' # valid string-keyword no-space combinations\n"
"async def f(): await g()\n"
"# All valid prefixes for unicode and byte strings should be colored.\n"
"'x', '''x''', \"x\", \"\"\"x\"\"\"\n"
"r'x', u'x', R'x', U'x', f'x', F'x'\n"
"fr'x', Fr'x', fR'x', FR'x', rf'x', rF'x', Rf'x', RF'x'\n"
"b'x',B'x', br'x',Br'x',bR'x',BR'x', rb'x'.rB'x',Rb'x',RB'x'\n"
"# Invalid combinations of legal characters should be half colored.\n"
"ur'x', ru'x', uf'x', fu'x', UR'x', ufr'x', rfu'x', xf'x', fx'x'\n"
)
text = Text(top, background="white")
text.pack(expand=1, fill="both")
text.insert("insert", source)
text.focus_set()
color_config(text)
p = Percolator(text)
d = ColorDelegator()
p.insertfilter(d)
if __name__ == "__main__":
from unittest import main
main('idlelib.idle_test.test_colorizer', verbosity=2, exit=False)
from idlelib.idle_test.htest import run
run(_color_delegator)

View file

@ -0,0 +1,62 @@
# config-extensions.def
#
# The following sections are for features that are no longer extensions.
# Their options values are left here for back-compatibility.
[AutoComplete]
popupwait= 2000
[CodeContext]
maxlines= 15
[FormatParagraph]
max-width= 72
[ParenMatch]
style= expression
flash-delay= 500
bell= True
# IDLE reads several config files to determine user preferences. This
# file is the default configuration file for IDLE extensions settings.
#
# Each extension must have at least one section, named after the
# extension module. This section must contain an 'enable' item (=True to
# enable the extension, =False to disable it), it may contain
# 'enable_editor' or 'enable_shell' items, to apply it only to editor ir
# shell windows, and may also contain any other general configuration
# items for the extension. Other True/False values will also be
# recognized as boolean by the Extension Configuration dialog.
#
# Each extension must define at least one section named
# ExtensionName_bindings or ExtensionName_cfgBindings. If present,
# ExtensionName_bindings defines virtual event bindings for the
# extension that are not user re-configurable. If present,
# ExtensionName_cfgBindings defines virtual event bindings for the
# extension that may be sensibly re-configured.
#
# If there are no keybindings for a menus' virtual events, include lines
# like <<toggle-code-context>>=.
#
# Currently it is necessary to manually modify this file to change
# extension key bindings and default values. To customize, create
# ~/.idlerc/config-extensions.cfg and append the appropriate customized
# section(s). Those sections will override the defaults in this file.
#
# Note: If a keybinding is already in use when the extension is loaded,
# the extension's virtual event's keybinding will be set to ''.
#
# See config-keys.def for notes on specifying keys and extend.txt for
# information on creating IDLE extensions.
# A fake extension for testing and example purposes. When enabled and
# invoked, inserts or deletes z-text at beginning of every line.
[ZzDummy]
enable= False
enable_shell = False
enable_editor = True
z-text= Z
[ZzDummy_cfgBindings]
z-in= <Control-Shift-KeyRelease-Insert>
[ZzDummy_bindings]
z-out= <Control-Shift-KeyRelease-Delete>

View file

@ -0,0 +1,99 @@
# IDLE reads several config files to determine user preferences. This
# file is the default config file for idle highlight theme settings.
[IDLE Classic]
normal-foreground= #000000
normal-background= #ffffff
keyword-foreground= #ff7700
keyword-background= #ffffff
builtin-foreground= #900090
builtin-background= #ffffff
comment-foreground= #dd0000
comment-background= #ffffff
string-foreground= #00aa00
string-background= #ffffff
definition-foreground= #0000ff
definition-background= #ffffff
hilite-foreground= #000000
hilite-background= gray
break-foreground= black
break-background= #ffff55
hit-foreground= #ffffff
hit-background= #000000
error-foreground= #000000
error-background= #ff7777
#cursor (only foreground can be set, restart IDLE)
cursor-foreground= black
#shell window
stdout-foreground= blue
stdout-background= #ffffff
stderr-foreground= red
stderr-background= #ffffff
console-foreground= #770000
console-background= #ffffff
context-foreground= #000000
context-background= lightgray
[IDLE New]
normal-foreground= #000000
normal-background= #ffffff
keyword-foreground= #ff7700
keyword-background= #ffffff
builtin-foreground= #900090
builtin-background= #ffffff
comment-foreground= #dd0000
comment-background= #ffffff
string-foreground= #00aa00
string-background= #ffffff
definition-foreground= #0000ff
definition-background= #ffffff
hilite-foreground= #000000
hilite-background= gray
break-foreground= black
break-background= #ffff55
hit-foreground= #ffffff
hit-background= #000000
error-foreground= #000000
error-background= #ff7777
#cursor (only foreground can be set, restart IDLE)
cursor-foreground= black
#shell window
stdout-foreground= blue
stdout-background= #ffffff
stderr-foreground= red
stderr-background= #ffffff
console-foreground= #770000
console-background= #ffffff
context-foreground= #000000
context-background= lightgray
[IDLE Dark]
comment-foreground = #dd0000
console-foreground = #ff4d4d
error-foreground = #FFFFFF
hilite-background = #7e7e7e
string-foreground = #02ff02
stderr-background = #002240
stderr-foreground = #ffb3b3
console-background = #002240
hit-background = #fbfbfb
string-background = #002240
normal-background = #002240
hilite-foreground = #FFFFFF
keyword-foreground = #ff8000
error-background = #c86464
keyword-background = #002240
builtin-background = #002240
break-background = #808000
builtin-foreground = #ff00ff
definition-foreground = #5e5eff
stdout-foreground = #c2d1fa
definition-background = #002240
normal-foreground = #FFFFFF
cursor-foreground = #ffffff
stdout-background = #002240
hit-foreground = #002240
comment-background = #002240
break-foreground = #FFFFFF
context-foreground= #ffffff
context-background= #454545

View file

@ -0,0 +1,304 @@
# IDLE reads several config files to determine user preferences. This
# file is the default config file for idle key binding settings.
# Where multiple keys are specified for an action: if they are separated
# by a space (eg. action=<key1> <key2>) then the keys are alternatives, if
# there is no space (eg. action=<key1><key2>) then the keys comprise a
# single 'emacs style' multi-keystoke binding. The tk event specifier 'Key'
# is used in all cases, for consistency in auto key conflict checking in the
# configuration gui.
[IDLE Classic Windows]
copy=<Control-Key-c> <Control-Key-C>
cut=<Control-Key-x> <Control-Key-X>
paste=<Control-Key-v> <Control-Key-V>
beginning-of-line= <Key-Home>
center-insert=<Control-Key-l> <Control-Key-L>
close-all-windows=<Control-Key-q> <Control-Key-Q>
close-window=<Alt-Key-F4> <Meta-Key-F4>
do-nothing=<Control-Key-F12>
end-of-file=<Control-Key-d> <Control-Key-D>
python-docs=<Key-F1>
python-context-help=<Shift-Key-F1>
history-next=<Alt-Key-n> <Meta-Key-n> <Alt-Key-N> <Meta-Key-N>
history-previous=<Alt-Key-p> <Meta-Key-p> <Alt-Key-P> <Meta-Key-P>
interrupt-execution=<Control-Key-c> <Control-Key-C>
view-restart=<Key-F6>
restart-shell=<Control-Key-F6>
open-class-browser=<Alt-Key-c> <Meta-Key-c> <Alt-Key-C> <Meta-Key-C>
open-module=<Alt-Key-m> <Meta-Key-m> <Alt-Key-M> <Meta-Key-M>
open-new-window=<Control-Key-n> <Control-Key-N>
open-window-from-file=<Control-Key-o> <Control-Key-O>
plain-newline-and-indent=<Control-Key-j> <Control-Key-J>
print-window=<Control-Key-p> <Control-Key-P>
redo=<Control-Shift-Key-Z> <Control-Shift-Key-z>
remove-selection=<Key-Escape>
save-copy-of-window-as-file=<Alt-Shift-Key-S> <Alt-Shift-Key-s>
save-window-as-file=<Control-Shift-Key-S> <Control-Shift-Key-s>
save-window=<Control-Key-s> <Control-Key-S>
select-all=<Control-Key-a> <Control-Key-A>
toggle-auto-coloring=<Control-Key-slash>
undo=<Control-Key-z> <Control-Key-Z>
find=<Control-Key-f> <Control-Key-F>
find-again=<Control-Key-g> <Key-F3> <Control-Key-G>
find-in-files=<Alt-Key-F3> <Meta-Key-F3>
find-selection=<Control-Key-F3>
replace=<Control-Key-h> <Control-Key-H>
goto-line=<Alt-Key-g> <Meta-Key-g> <Alt-Key-G> <Meta-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=<Alt-Key-3> <Meta-Key-3>
uncomment-region=<Alt-Key-4> <Meta-Key-4>
tabify-region=<Alt-Key-5> <Meta-Key-5>
untabify-region=<Alt-Key-6> <Meta-Key-6>
toggle-tabs=<Alt-Key-t> <Meta-Key-t> <Alt-Key-T> <Meta-Key-T>
change-indentwidth=<Alt-Key-u> <Meta-Key-u> <Alt-Key-U> <Meta-Key-U>
del-word-left=<Control-Key-BackSpace>
del-word-right=<Control-Key-Delete>
force-open-completions= <Control-Key-space>
expand-word= <Alt-Key-slash>
force-open-calltip= <Control-Key-backslash>
format-paragraph= <Alt-Key-q>
flash-paren= <Control-Key-0>
run-module= <Key-F5>
check-module= <Alt-Key-x>
zoom-height= <Alt-Key-2>
[IDLE Classic Unix]
copy=<Alt-Key-w> <Meta-Key-w>
cut=<Control-Key-w>
paste=<Control-Key-y>
beginning-of-line=<Control-Key-a> <Key-Home>
center-insert=<Control-Key-l>
close-all-windows=<Control-Key-x><Control-Key-c>
close-window=<Control-Key-x><Control-Key-0>
do-nothing=<Control-Key-x>
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-x><Control-Key-b>
open-module=<Control-Key-x><Control-Key-m>
open-new-window=<Control-Key-x><Control-Key-n>
open-window-from-file=<Control-Key-x><Control-Key-f>
plain-newline-and-indent=<Control-Key-j>
print-window=<Control-x><Control-Key-p>
python-docs=<Control-Key-h>
python-context-help=<Control-Shift-Key-H>
redo=<Alt-Key-z> <Meta-Key-z>
remove-selection=<Key-Escape>
save-copy-of-window-as-file=<Control-Key-x><Control-Key-y>
save-window-as-file=<Control-Key-x><Control-Key-w>
save-window=<Control-Key-x><Control-Key-s>
select-all=<Alt-Key-a> <Meta-Key-a>
toggle-auto-coloring=<Control-Key-slash>
undo=<Control-Key-z>
find=<Control-Key-u><Control-Key-u><Control-Key-s>
find-again=<Control-Key-u><Control-Key-s>
find-in-files=<Alt-Key-s> <Meta-Key-s>
find-selection=<Control-Key-s>
replace=<Control-Key-r>
goto-line=<Alt-Key-g> <Meta-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=<Alt-Key-3>
uncomment-region=<Alt-Key-4>
tabify-region=<Alt-Key-5>
untabify-region=<Alt-Key-6>
toggle-tabs=<Alt-Key-t>
change-indentwidth=<Alt-Key-u>
del-word-left=<Alt-Key-BackSpace>
del-word-right=<Alt-Key-d>
force-open-completions= <Control-Key-space>
expand-word= <Alt-Key-slash>
force-open-calltip= <Control-Key-backslash>
format-paragraph= <Alt-Key-q>
flash-paren= <Control-Key-0>
run-module= <Key-F5>
check-module= <Alt-Key-x>
zoom-height= <Alt-Key-2>
[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>
force-open-completions= <Control-Key-space>
expand-word= <Alt-Key-slash>
force-open-calltip= <Control-Key-backslash>
format-paragraph= <Alt-Key-q>
flash-paren= <Control-Key-0>
run-module= <Key-F5>
check-module= <Alt-Key-x>
zoom-height= <Alt-Key-2>
[IDLE Classic Mac]
copy=<Command-Key-c>
cut=<Command-Key-x>
paste=<Command-Key-v>
beginning-of-line= <Key-Home>
center-insert=<Control-Key-l>
close-all-windows=<Command-Key-q>
close-window=<Command-Key-w>
do-nothing=<Control-Key-F12>
end-of-file=<Control-Key-d>
python-docs=<Key-F1>
python-context-help=<Shift-Key-F1>
history-next=<Control-Key-n>
history-previous=<Control-Key-p>
interrupt-execution=<Control-Key-c>
view-restart=<Key-F6>
restart-shell=<Control-Key-F6>
open-class-browser=<Command-Key-b>
open-module=<Command-Key-m>
open-new-window=<Command-Key-n>
open-window-from-file=<Command-Key-o>
plain-newline-and-indent=<Control-Key-j>
print-window=<Command-Key-p>
redo=<Shift-Command-Key-Z>
remove-selection=<Key-Escape>
save-window-as-file=<Shift-Command-Key-S>
save-window=<Command-Key-s>
save-copy-of-window-as-file=<Option-Command-Key-s>
select-all=<Command-Key-a>
toggle-auto-coloring=<Control-Key-slash>
undo=<Command-Key-z>
find=<Command-Key-f>
find-again=<Command-Key-g> <Key-F3>
find-in-files=<Command-Key-F3>
find-selection=<Shift-Command-Key-F3>
replace=<Command-Key-r>
goto-line=<Command-Key-j>
smart-backspace=<Key-BackSpace>
newline-and-indent=<Key-Return> <Key-KP_Enter>
smart-indent=<Key-Tab>
indent-region=<Command-Key-bracketright>
dedent-region=<Command-Key-bracketleft>
comment-region=<Control-Key-3>
uncomment-region=<Control-Key-4>
tabify-region=<Control-Key-5>
untabify-region=<Control-Key-6>
toggle-tabs=<Control-Key-t>
change-indentwidth=<Control-Key-u>
del-word-left=<Control-Key-BackSpace>
del-word-right=<Control-Key-Delete>
force-open-completions= <Control-Key-space>
expand-word= <Option-Key-slash>
force-open-calltip= <Control-Key-backslash>
format-paragraph= <Option-Key-q>
flash-paren= <Control-Key-0>
run-module= <Key-F5>
check-module= <Option-Key-x>
zoom-height= <Option-Key-0>
[IDLE Classic OSX]
toggle-tabs = <Control-Key-t>
interrupt-execution = <Control-Key-c>
untabify-region = <Control-Key-6>
remove-selection = <Key-Escape>
print-window = <Command-Key-p>
replace = <Command-Key-r>
goto-line = <Command-Key-j>
plain-newline-and-indent = <Control-Key-j>
history-previous = <Control-Key-p>
beginning-of-line = <Control-Key-Left>
end-of-line = <Control-Key-Right>
comment-region = <Control-Key-3>
redo = <Shift-Command-Key-Z>
close-window = <Command-Key-w>
restart-shell = <Control-Key-F6>
save-window-as-file = <Shift-Command-Key-S>
close-all-windows = <Command-Key-q>
view-restart = <Key-F6>
tabify-region = <Control-Key-5>
find-again = <Command-Key-g> <Key-F3>
find = <Command-Key-f>
toggle-auto-coloring = <Control-Key-slash>
select-all = <Command-Key-a>
smart-backspace = <Key-BackSpace>
change-indentwidth = <Control-Key-u>
do-nothing = <Control-Key-F12>
smart-indent = <Key-Tab>
center-insert = <Control-Key-l>
history-next = <Control-Key-n>
del-word-right = <Option-Key-Delete>
undo = <Command-Key-z>
save-window = <Command-Key-s>
uncomment-region = <Control-Key-4>
cut = <Command-Key-x>
find-in-files = <Command-Key-F3>
dedent-region = <Command-Key-bracketleft>
copy = <Command-Key-c>
paste = <Command-Key-v>
indent-region = <Command-Key-bracketright>
del-word-left = <Option-Key-BackSpace> <Option-Command-Key-BackSpace>
newline-and-indent = <Key-Return> <Key-KP_Enter>
end-of-file = <Control-Key-d>
open-class-browser = <Command-Key-b>
open-new-window = <Command-Key-n>
open-module = <Command-Key-m>
find-selection = <Shift-Command-Key-F3>
python-context-help = <Shift-Key-F1>
save-copy-of-window-as-file = <Option-Command-Key-s>
open-window-from-file = <Command-Key-o>
python-docs = <Key-F1>
force-open-completions= <Control-Key-space>
expand-word= <Option-Key-slash>
force-open-calltip= <Control-Key-backslash>
format-paragraph= <Option-Key-q>
flash-paren= <Control-Key-0>
run-module= <Key-F5>
check-module= <Option-Key-x>
zoom-height= <Option-Key-0>

View file

@ -0,0 +1,91 @@
# IDLE reads several config files to determine user preferences. This
# file is the default config file for general idle settings.
#
# When IDLE starts, it will look in
# the following two sets of files, in order:
#
# default configuration files in idlelib
# --------------------------------------
# config-main.def default general config file
# config-extensions.def default extension config file
# config-highlight.def default highlighting config file
# config-keys.def default keybinding config file
#
# user configuration files in ~/.idlerc
# -------------------------------------
# config-main.cfg user general config file
# config-extensions.cfg user extension config file
# config-highlight.cfg user highlighting config file
# config-keys.cfg user keybinding config file
#
# On Windows, the default location of the home directory ('~' above)
# depends on the version. For Windows 10, it is C:\Users\<username>.
#
# Any options the user saves through the config dialog will be saved to
# the relevant user config file. Reverting any general or extension
# setting to the default causes that entry to be wiped from the user
# file and re-read from the default file. This rule applies to each
# item, except that the three editor font items are saved as a group.
#
# User highlighting themes and keybinding sets must have (section) names
# distinct from the default names. All items are added and saved as a
# group. They are retained unless specifically deleted within the config
# dialog. Choosing one of the default themes or keysets just applies the
# relevant settings from the default file.
#
# Additional help sources are listed in the [HelpFiles] section below
# and should be viewable by a web browser (or the Windows Help viewer in
# the case of .chm files). These sources will be listed on the Help
# menu. The pattern, and two examples, are
#
# <sequence_number = menu item;/path/to/help/source>
# 1 = IDLE;C:/Programs/Python36/Lib/idlelib/help.html
# 2 = Pillow;https://pillow.readthedocs.io/en/latest/
#
# You can't use a semi-colon in a menu item or path. The path will be
# platform specific because of path separators, drive specs etc.
#
# The default files should not be edited except to add new sections to
# config-extensions.def for added extensions . The user files should be
# modified through the Settings dialog.
[General]
editor-on-startup= 0
autosave= 0
print-command-posix=lpr %%s
print-command-win=start /min notepad /p %%s
delete-exitfunc= 1
[EditorWindow]
width= 80
height= 40
font= TkFixedFont
# For TkFixedFont, the actual size and boldness are obtained from tk
# and override 10 and 0. See idlelib.config.IdleConf.GetFont
font-size= 10
font-bold= 0
encoding= none
[PyShell]
auto-squeeze-min-lines= 50
[Indent]
use-spaces= 1
num-spaces= 4
[Theme]
default= 1
name= IDLE Classic
name2=
# name2 set in user config-main.cfg for themes added after 2015 Oct 1
[Keys]
default= 1
name=
name2=
# name2 set in user config-main.cfg for keys added after 2016 July 1
[History]
cyclic=1
[HelpFiles]

930
third_party/python/Lib/idlelib/config.py vendored Normal file
View file

@ -0,0 +1,930 @@
"""idlelib.config -- Manage IDLE configuration information.
The comments at the beginning of config-main.def describe the
configuration files and the design implemented to update user
configuration information. In particular, user configuration choices
which duplicate the defaults will be removed from the user's
configuration files, and if a user file becomes empty, it will be
deleted.
The configuration database maps options to values. Conceptually, the
database keys are tuples (config-type, section, item). As implemented,
there are separate dicts for default and user values. Each has
config-type keys 'main', 'extensions', 'highlight', and 'keys'. The
value for each key is a ConfigParser instance that maps section and item
to values. For 'main' and 'extenstons', user values override
default values. For 'highlight' and 'keys', user sections augment the
default sections (and must, therefore, have distinct names).
Throughout this module there is an emphasis on returning useable defaults
when a problem occurs in returning a requested configuration value back to
idle. This is to allow IDLE to continue to function in spite of errors in
the retrieval of config information. When a default is returned instead of
a requested config value, a message is printed to stderr to aid in
configuration problem notification and resolution.
"""
# TODOs added Oct 2014, tjr
from configparser import ConfigParser
import os
import sys
from tkinter.font import Font
import idlelib
class InvalidConfigType(Exception): pass
class InvalidConfigSet(Exception): pass
class InvalidFgBg(Exception): pass
class InvalidTheme(Exception): pass
class IdleConfParser(ConfigParser):
"""
A ConfigParser specialised for idle configuration file handling
"""
def __init__(self, cfgFile, cfgDefaults=None):
"""
cfgFile - string, fully specified configuration file name
"""
self.file = cfgFile # This is currently '' when testing.
ConfigParser.__init__(self, defaults=cfgDefaults, strict=False)
def Get(self, section, option, type=None, default=None, raw=False):
"""
Get an option value for given section/option or return default.
If type is specified, return as type.
"""
# TODO Use default as fallback, at least if not None
# Should also print Warning(file, section, option).
# Currently may raise ValueError
if not self.has_option(section, option):
return default
if type == 'bool':
return self.getboolean(section, option)
elif type == 'int':
return self.getint(section, option)
else:
return self.get(section, option, raw=raw)
def GetOptionList(self, section):
"Return a list of options for given section, else []."
if self.has_section(section):
return self.options(section)
else: #return a default value
return []
def Load(self):
"Load the configuration file from disk."
if self.file:
self.read(self.file)
class IdleUserConfParser(IdleConfParser):
"""
IdleConfigParser specialised for user configuration handling.
"""
def SetOption(self, section, option, value):
"""Return True if option is added or changed to value, else False.
Add section if required. False means option already had value.
"""
if self.has_option(section, option):
if self.get(section, option) == value:
return False
else:
self.set(section, option, value)
return True
else:
if not self.has_section(section):
self.add_section(section)
self.set(section, option, value)
return True
def RemoveOption(self, section, option):
"""Return True if option is removed from section, else False.
False if either section does not exist or did not have option.
"""
if self.has_section(section):
return self.remove_option(section, option)
return False
def AddSection(self, section):
"If section doesn't exist, add it."
if not self.has_section(section):
self.add_section(section)
def RemoveEmptySections(self):
"Remove any sections that have no options."
for section in self.sections():
if not self.GetOptionList(section):
self.remove_section(section)
def IsEmpty(self):
"Return True if no sections after removing empty sections."
self.RemoveEmptySections()
return not self.sections()
def RemoveFile(self):
"Remove user config file self.file from disk if it exists."
if os.path.exists(self.file):
os.remove(self.file)
def Save(self):
"""Update user configuration file.
If self not empty after removing empty sections, write the file
to disk. Otherwise, remove the file from disk if it exists.
"""
fname = self.file
if fname:
if not self.IsEmpty():
try:
cfgFile = open(fname, 'w')
except OSError:
os.unlink(fname)
cfgFile = open(fname, 'w')
with cfgFile:
self.write(cfgFile)
else:
self.RemoveFile()
class IdleConf:
"""Hold config parsers for all idle config files in singleton instance.
Default config files, self.defaultCfg --
for config_type in self.config_types:
(idle install dir)/config-{config-type}.def
User config files, self.userCfg --
for config_type in self.config_types:
(user home dir)/.idlerc/config-{config-type}.cfg
"""
def __init__(self, _utest=False):
self.config_types = ('main', 'highlight', 'keys', 'extensions')
self.defaultCfg = {}
self.userCfg = {}
self.cfg = {} # TODO use to select userCfg vs defaultCfg
if not _utest:
self.CreateConfigHandlers()
self.LoadCfgFiles()
def CreateConfigHandlers(self):
"Populate default and user config parser dictionaries."
#build idle install path
if __name__ != '__main__': # we were imported
idleDir = os.path.dirname(__file__)
else: # we were exec'ed (for testing only)
idleDir = os.path.abspath(sys.path[0])
self.userdir = userDir = self.GetUserCfgDir()
defCfgFiles = {}
usrCfgFiles = {}
# TODO eliminate these temporaries by combining loops
for cfgType in self.config_types: #build config file names
defCfgFiles[cfgType] = os.path.join(
idleDir, 'config-' + cfgType + '.def')
usrCfgFiles[cfgType] = os.path.join(
userDir, 'config-' + cfgType + '.cfg')
for cfgType in self.config_types: #create config parsers
self.defaultCfg[cfgType] = IdleConfParser(defCfgFiles[cfgType])
self.userCfg[cfgType] = IdleUserConfParser(usrCfgFiles[cfgType])
def GetUserCfgDir(self):
"""Return a filesystem directory for storing user config files.
Creates it if required.
"""
cfgDir = '.idlerc'
userDir = os.path.expanduser('~')
if userDir != '~': # expanduser() found user home dir
if not os.path.exists(userDir):
warn = ('\n Warning: os.path.expanduser("~") points to\n ' +
userDir + ',\n but the path does not exist.')
try:
print(warn, file=sys.stderr)
except OSError:
pass
userDir = '~'
if userDir == "~": # still no path to home!
# traditionally IDLE has defaulted to os.getcwd(), is this adequate?
userDir = os.getcwd()
userDir = os.path.join(userDir, cfgDir)
if not os.path.exists(userDir):
try:
os.mkdir(userDir)
except OSError:
warn = ('\n Warning: unable to create user config directory\n' +
userDir + '\n Check path and permissions.\n Exiting!\n')
if not idlelib.testing:
print(warn, file=sys.stderr)
raise SystemExit
# TODO continue without userDIr instead of exit
return userDir
def GetOption(self, configType, section, option, default=None, type=None,
warn_on_default=True, raw=False):
"""Return a value for configType section option, or default.
If type is not None, return a value of that type. Also pass raw
to the config parser. First try to return a valid value
(including type) from a user configuration. If that fails, try
the default configuration. If that fails, return default, with a
default of None.
Warn if either user or default configurations have an invalid value.
Warn if default is returned and warn_on_default is True.
"""
try:
if self.userCfg[configType].has_option(section, option):
return self.userCfg[configType].Get(section, option,
type=type, raw=raw)
except ValueError:
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)))
_warn(warning, configType, section, option)
try:
if self.defaultCfg[configType].has_option(section,option):
return self.defaultCfg[configType].Get(
section, option, type=type, raw=raw)
except ValueError:
pass
#returning default, print warning
if warn_on_default:
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))
_warn(warning, configType, section, option)
return default
def SetOption(self, configType, section, option, value):
"""Set section option to value in user config file."""
self.userCfg[configType].SetOption(section, option, value)
def GetSectionList(self, configSet, configType):
"""Return sections for configSet configType configuration.
configSet must be either 'user' or 'default'
configType must be in self.config_types.
"""
if not (configType in self.config_types):
raise InvalidConfigType('Invalid configType specified')
if configSet == 'user':
cfgParser = self.userCfg[configType]
elif configSet == 'default':
cfgParser=self.defaultCfg[configType]
else:
raise InvalidConfigSet('Invalid configSet specified')
return cfgParser.sections()
def GetHighlight(self, theme, element, fgBg=None):
"""Return individual theme element highlight color(s).
fgBg - string ('fg' or 'bg') or None.
If None, return a dictionary containing fg and bg colors with
keys 'foreground' and 'background'. Otherwise, only return
fg or bg color, as specified. Colors are intended to be
appropriate for passing to Tkinter in, e.g., a tag_config call).
"""
if self.defaultCfg['highlight'].has_section(theme):
themeDict = self.GetThemeDict('default', theme)
else:
themeDict = self.GetThemeDict('user', theme)
fore = themeDict[element + '-foreground']
if element == 'cursor': # There is no config value for cursor bg
back = themeDict['normal-background']
else:
back = themeDict[element + '-background']
highlight = {"foreground": fore, "background": back}
if not fgBg: # Return dict of both colors
return highlight
else: # Return specified color only
if fgBg == 'fg':
return highlight["foreground"]
if fgBg == 'bg':
return highlight["background"]
else:
raise InvalidFgBg('Invalid fgBg specified')
def GetThemeDict(self, type, themeName):
"""Return {option:value} dict for elements in themeName.
type - string, 'default' or 'user' theme type
themeName - string, theme name
Values are loaded over ultimate fallback defaults to guarantee
that all theme elements are present in a newly created theme.
"""
if type == 'user':
cfgParser = self.userCfg['highlight']
elif type == 'default':
cfgParser = self.defaultCfg['highlight']
else:
raise InvalidTheme('Invalid theme type specified')
# Provide foreground and background colors for each theme
# element (other than cursor) even though some values are not
# yet used by idle, to allow for their use in the future.
# Default values are generally black and white.
# TODO copy theme from a class attribute.
theme ={'normal-foreground':'#000000',
'normal-background':'#ffffff',
'keyword-foreground':'#000000',
'keyword-background':'#ffffff',
'builtin-foreground':'#000000',
'builtin-background':'#ffffff',
'comment-foreground':'#000000',
'comment-background':'#ffffff',
'string-foreground':'#000000',
'string-background':'#ffffff',
'definition-foreground':'#000000',
'definition-background':'#ffffff',
'hilite-foreground':'#000000',
'hilite-background':'gray',
'break-foreground':'#ffffff',
'break-background':'#000000',
'hit-foreground':'#ffffff',
'hit-background':'#000000',
'error-foreground':'#ffffff',
'error-background':'#000000',
#cursor (only foreground can be set)
'cursor-foreground':'#000000',
#shell window
'stdout-foreground':'#000000',
'stdout-background':'#ffffff',
'stderr-foreground':'#000000',
'stderr-background':'#ffffff',
'console-foreground':'#000000',
'console-background':'#ffffff',
'context-foreground':'#000000',
'context-background':'#ffffff',
}
for element in theme:
if not cfgParser.has_option(themeName, element):
# Print warning that will return a default color
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]))
_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 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
[Theme]
default= 1
name= IDLE Classic
name2=
[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 = False,
'name2' may still be set, but it is ignored.
"""
cfgname = 'highlight' if section == 'Theme' else 'keys'
default = self.GetOption('main', section, 'default',
type='bool', default=True)
name = ''
if default:
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 Modern Unix'
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
and optionally only editor or shell extensions.
If active_only False, return all extensions.
"""
extns = self.RemoveKeyBindNames(
self.GetSectionList('default', 'extensions'))
userExtns = self.RemoveKeyBindNames(
self.GetSectionList('user', 'extensions'))
for extn in userExtns:
if extn not in extns: #user has added own extension
extns.append(extn)
for extn in ('AutoComplete','CodeContext',
'FormatParagraph','ParenMatch'):
extns.remove(extn)
# specific exclusions because we are storing config for mainlined old
# extensions in config-extensions.def for backward compatibility
if active_only:
activeExtns = []
for extn in extns:
if self.GetOption('extensions', extn, 'enable', default=True,
type='bool'):
#the extension is enabled
if editor_only or shell_only: # TODO both True contradict
if editor_only:
option = "enable_editor"
else:
option = "enable_shell"
if self.GetOption('extensions', extn,option,
default=True, type='bool',
warn_on_default=False):
activeExtns.append(extn)
else:
activeExtns.append(extn)
return activeExtns
else:
return extns
def RemoveKeyBindNames(self, extnNameList):
"Return extnNameList with keybinding section names removed."
return [n for n in extnNameList if not n.endswith(('_bindings', '_cfgBindings'))]
def GetExtnNameForEvent(self, virtualEvent):
"""Return the name of the extension binding virtualEvent, or None.
virtualEvent - string, name of the virtual event to test for,
without the enclosing '<< >>'
"""
extName = None
vEvent = '<<' + virtualEvent + '>>'
for extn in self.GetExtensions(active_only=0):
for event in self.GetExtensionKeys(extn):
if event == vEvent:
extName = extn # TODO return here?
return extName
def GetExtensionKeys(self, extensionName):
"""Return dict: {configurable extensionName event : active keybinding}.
Events come from default config extension_cfgBindings section.
Keybindings come from GetCurrentKeySet() active key dict,
where previously used bindings are disabled.
"""
keysName = extensionName + '_cfgBindings'
activeKeys = self.GetCurrentKeySet()
extKeys = {}
if self.defaultCfg['extensions'].has_section(keysName):
eventNames = self.defaultCfg['extensions'].GetOptionList(keysName)
for eventName in eventNames:
event = '<<' + eventName + '>>'
binding = activeKeys[event]
extKeys[event] = binding
return extKeys
def __GetRawExtensionKeys(self,extensionName):
"""Return dict {configurable extensionName event : keybinding list}.
Events come from default config extension_cfgBindings section.
Keybindings list come from the splitting of GetOption, which
tries user config before default config.
"""
keysName = extensionName+'_cfgBindings'
extKeys = {}
if self.defaultCfg['extensions'].has_section(keysName):
eventNames = self.defaultCfg['extensions'].GetOptionList(keysName)
for eventName in eventNames:
binding = self.GetOption(
'extensions', keysName, eventName, default='').split()
event = '<<' + eventName + '>>'
extKeys[event] = binding
return extKeys
def GetExtensionBindings(self, extensionName):
"""Return dict {extensionName event : active or defined keybinding}.
Augment self.GetExtensionKeys(extensionName) with mapping of non-
configurable events (from default config) to GetOption splits,
as in self.__GetRawExtensionKeys.
"""
bindsName = extensionName + '_bindings'
extBinds = self.GetExtensionKeys(extensionName)
#add the non-configurable bindings
if self.defaultCfg['extensions'].has_section(bindsName):
eventNames = self.defaultCfg['extensions'].GetOptionList(bindsName)
for eventName in eventNames:
binding = self.GetOption(
'extensions', bindsName, eventName, default='').split()
event = '<<' + eventName + '>>'
extBinds[event] = binding
return extBinds
def GetKeyBinding(self, keySetName, eventStr):
"""Return the keybinding list for keySetName eventStr.
keySetName - name of key binding set (config-keys section).
eventStr - virtual event, including brackets, as in '<<event>>'.
"""
eventName = eventStr[2:-2] #trim off the angle brackets
binding = self.GetOption('keys', keySetName, eventName, default='',
warn_on_default=False).split()
return binding
def GetCurrentKeySet(self):
"Return CurrentKeys with 'darwin' modifications."
result = self.GetKeySet(self.CurrentKeys())
if sys.platform == "darwin":
# macOS (OS X) Tk variants do not support the "Alt"
# keyboard modifier. Replace it with "Option".
# TODO (Ned?): the "Option" modifier does not work properly
# for Cocoa Tk and XQuartz Tk so we should not use it
# in the default 'OSX' keyset.
for k, v in result.items():
v2 = [ x.replace('<Alt-', '<Option-') for x in v ]
if v != v2:
result[k] = v2
return result
def GetKeySet(self, keySetName):
"""Return event-key dict for keySetName core plus active extensions.
If a binding defined in an extension is already in use, the
extension binding is disabled by being set to ''
"""
keySet = self.GetCoreKeys(keySetName)
activeExtns = self.GetExtensions(active_only=1)
for extn in activeExtns:
extKeys = self.__GetRawExtensionKeys(extn)
if extKeys: #the extension defines keybindings
for event in extKeys:
if extKeys[event] in keySet.values():
#the binding is already in use
extKeys[event] = '' #disable this binding
keySet[event] = extKeys[event] #add binding
return keySet
def IsCoreBinding(self, virtualEvent):
"""Return True if the virtual event is one of the core idle key events.
virtualEvent - string, name of the virtual event to test for,
without the enclosing '<< >>'
"""
return ('<<'+virtualEvent+'>>') in self.GetCoreKeys()
# TODO make keyBindins a file or class attribute used for test above
# and copied in function below.
former_extension_events = { # Those with user-configurable keys.
'<<force-open-completions>>', '<<expand-word>>',
'<<force-open-calltip>>', '<<flash-paren>>', '<<format-paragraph>>',
'<<run-module>>', '<<check-module>>', '<<zoom-height>>'}
def GetCoreKeys(self, keySetName=None):
"""Return dict of core virtual-key keybindings for keySetName.
The default keySetName None corresponds to the keyBindings base
dict. If keySetName is not None, bindings from the config
file(s) are loaded _over_ these defaults, so if there is a
problem getting any core binding there will be an 'ultimate last
resort fallback' to the CUA-ish bindings defined here.
"""
keyBindings={
'<<copy>>': ['<Control-c>', '<Control-C>'],
'<<cut>>': ['<Control-x>', '<Control-X>'],
'<<paste>>': ['<Control-v>', '<Control-V>'],
'<<beginning-of-line>>': ['<Control-a>', '<Home>'],
'<<center-insert>>': ['<Control-l>'],
'<<close-all-windows>>': ['<Control-q>'],
'<<close-window>>': ['<Alt-F4>'],
'<<do-nothing>>': ['<Control-x>'],
'<<end-of-file>>': ['<Control-d>'],
'<<python-docs>>': ['<F1>'],
'<<python-context-help>>': ['<Shift-F1>'],
'<<history-next>>': ['<Alt-n>'],
'<<history-previous>>': ['<Alt-p>'],
'<<interrupt-execution>>': ['<Control-c>'],
'<<view-restart>>': ['<F6>'],
'<<restart-shell>>': ['<Control-F6>'],
'<<open-class-browser>>': ['<Alt-c>'],
'<<open-module>>': ['<Alt-m>'],
'<<open-new-window>>': ['<Control-n>'],
'<<open-window-from-file>>': ['<Control-o>'],
'<<plain-newline-and-indent>>': ['<Control-j>'],
'<<print-window>>': ['<Control-p>'],
'<<redo>>': ['<Control-y>'],
'<<remove-selection>>': ['<Escape>'],
'<<save-copy-of-window-as-file>>': ['<Alt-Shift-S>'],
'<<save-window-as-file>>': ['<Alt-s>'],
'<<save-window>>': ['<Control-s>'],
'<<select-all>>': ['<Alt-a>'],
'<<toggle-auto-coloring>>': ['<Control-slash>'],
'<<undo>>': ['<Control-z>'],
'<<find-again>>': ['<Control-g>', '<F3>'],
'<<find-in-files>>': ['<Alt-F3>'],
'<<find-selection>>': ['<Control-F3>'],
'<<find>>': ['<Control-f>'],
'<<replace>>': ['<Control-h>'],
'<<goto-line>>': ['<Alt-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>>': ['<Alt-Key-3>'],
'<<uncomment-region>>': ['<Alt-Key-4>'],
'<<tabify-region>>': ['<Alt-Key-5>'],
'<<untabify-region>>': ['<Alt-Key-6>'],
'<<toggle-tabs>>': ['<Alt-Key-t>'],
'<<change-indentwidth>>': ['<Alt-Key-u>'],
'<<del-word-left>>': ['<Control-Key-BackSpace>'],
'<<del-word-right>>': ['<Control-Key-Delete>'],
'<<force-open-completions>>': ['<Control-Key-space>'],
'<<expand-word>>': ['<Alt-Key-slash>'],
'<<force-open-calltip>>': ['<Control-Key-backslash>'],
'<<flash-paren>>': ['<Control-Key-0>'],
'<<format-paragraph>>': ['<Alt-Key-q>'],
'<<run-module>>': ['<Key-F5>'],
'<<check-module>>': ['<Alt-Key-x>'],
'<<zoom-height>>': ['<Alt-Key-2>'],
}
if keySetName:
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
# Otherwise return default in keyBindings.
elif event not in self.former_extension_events:
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):
"""Return list of extra help sources from a given configSet.
Valid configSets are 'user' or 'default'. Return a list of tuples of
the form (menu_item , path_to_help_file , option), or return the empty
list. 'option' is the sequence number of the help resource. 'option'
values determine the position of the menu items on the Help menu,
therefore the returned list must be sorted by 'option'.
"""
helpSources = []
if configSet == 'user':
cfgParser = self.userCfg['main']
elif configSet == 'default':
cfgParser = self.defaultCfg['main']
else:
raise InvalidConfigSet('Invalid configSet specified')
options=cfgParser.GetOptionList('HelpFiles')
for option in options:
value=cfgParser.Get('HelpFiles', option, default=';')
if value.find(';') == -1: #malformed config entry with no ';'
menuItem = '' #make these empty
helpPath = '' #so value won't be added to list
else: #config entry contains ';' as expected
value=value.split(';')
menuItem=value[0].strip()
helpPath=value[1].strip()
if menuItem and helpPath: #neither are empty strings
helpSources.append( (menuItem,helpPath,option) )
helpSources.sort(key=lambda x: x[2])
return helpSources
def GetAllExtraHelpSourcesList(self):
"""Return a list of the details of all additional help sources.
Tuples in the list are those of GetExtraHelpSourceList.
"""
allHelpSources = (self.GetExtraHelpSourceList('default') +
self.GetExtraHelpSourceList('user') )
return allHelpSources
def GetFont(self, root, configType, section):
"""Retrieve a font from configuration (font, font-size, font-bold)
Intercept the special value 'TkFixedFont' and substitute
the actual font, factoring in some tweaks if needed for
appearance sakes.
The 'root' parameter can normally be any valid Tkinter widget.
Return a tuple (family, size, weight) suitable for passing
to tkinter.Font
"""
family = self.GetOption(configType, section, 'font', default='courier')
size = self.GetOption(configType, section, 'font-size', type='int',
default='10')
bold = self.GetOption(configType, section, 'font-bold', default=0,
type='bool')
if (family == 'TkFixedFont'):
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):
"Load all configuration files."
for key in self.defaultCfg:
self.defaultCfg[key].Load()
self.userCfg[key].Load() #same keys
def SaveUserCfgFiles(self):
"Write all loaded user configuration files to disk."
for key in self.userCfg:
self.userCfg[key].Save()
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)
class ConfigChanges(dict):
"""Manage a user's proposed configuration option changes.
Names used across multiple methods:
page -- one of the 4 top-level dicts representing a
.idlerc/config-x.cfg file.
config_type -- name of a page.
section -- a section within a page/file.
option -- name of an option within a section.
value -- value for the option.
Methods
add_option: Add option and value to changes.
save_option: Save option and value to config parser.
save_all: Save all the changes to the config parser and file.
delete_section: If section exists,
delete from changes, userCfg, and file.
clear: Clear all changes by clearing each page.
"""
def __init__(self):
"Create a page for each configuration file"
self.pages = [] # List of unhashable dicts.
for config_type in idleConf.config_types:
self[config_type] = {}
self.pages.append(self[config_type])
def add_option(self, config_type, section, item, value):
"Add item/value pair for config_type and section."
page = self[config_type]
value = str(value) # Make sure we use a string.
if section not in page:
page[section] = {}
page[section][item] = value
@staticmethod
def save_option(config_type, section, item, value):
"""Return True if the configuration value was added or changed.
Helper for save_all.
"""
if idleConf.defaultCfg[config_type].has_option(section, item):
if idleConf.defaultCfg[config_type].Get(section, item) == value:
# The setting equals a default setting, remove it from user cfg.
return idleConf.userCfg[config_type].RemoveOption(section, item)
# If we got here, set the option.
return idleConf.userCfg[config_type].SetOption(section, item, value)
def save_all(self):
"""Save configuration changes to the user config file.
Clear self in preparation for additional changes.
Return changed for testing.
"""
idleConf.userCfg['main'].Save()
changed = False
for config_type in self:
cfg_type_changed = False
page = self[config_type]
for section in page:
if section == 'HelpFiles': # Remove it for replacement.
idleConf.userCfg['main'].remove_section('HelpFiles')
cfg_type_changed = True
for item, value in page[section].items():
if self.save_option(config_type, section, item, value):
cfg_type_changed = True
if cfg_type_changed:
idleConf.userCfg[config_type].Save()
changed = True
for config_type in ['keys', 'highlight']:
# Save these even if unchanged!
idleConf.userCfg[config_type].Save()
self.clear()
# ConfigDialog caller must add the following call
# self.save_all_changed_extensions() # Uses a different mechanism.
return changed
def delete_section(self, config_type, section):
"""Delete a section from self, userCfg, and file.
Used to delete custom themes and keysets.
"""
if section in self[config_type]:
del self[config_type][section]
configpage = idleConf.userCfg[config_type]
configpage.remove_section(section)
configpage.Save()
def clear(self):
"""Clear all 4 pages.
Called in save_all after saving to idleConf.
XXX Mark window *title* when there are changes; unmark here.
"""
for page in self.pages:
page.clear()
# TODO Revise test output, write expanded unittest
def _dump(): # htest # (not really, but ignore in coverage)
from zlib import crc32
line, crc = 0, 0
def sprint(obj):
global line, crc
txt = str(obj)
line += 1
crc = crc32(txt.encode(encoding='utf-8'), crc)
print(txt)
#print('***', line, crc, '***') # Uncomment for diagnosis.
def dumpCfg(cfg):
print('\n', cfg, '\n') # Cfg has variable '0xnnnnnnnn' address.
for key in sorted(cfg.keys()):
sections = cfg[key].sections()
sprint(key)
sprint(sections)
for section in sections:
options = cfg[key].options(section)
sprint(section)
sprint(options)
for option in options:
sprint(option + ' = ' + cfg[key].Get(section, option))
dumpCfg(idleConf.defaultCfg)
dumpCfg(idleConf.userCfg)
print('\nlines = ', line, ', crc = ', crc, sep='')
if __name__ == '__main__':
from unittest import main
main('idlelib.idle_test.test_config', verbosity=2, exit=False)
# Run revised _dump() as htest?

View file

@ -0,0 +1,300 @@
"""
Dialog for building Tkinter accelerator key bindings
"""
from tkinter import *
from tkinter.ttk import Scrollbar
from tkinter import messagebox
import string
import sys
class GetKeysDialog(Toplevel):
# Dialog title for invalid key sequence
keyerror_title = 'Key Sequence Error'
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.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.keyString.set('')
self.SetModifiersForPlatform() # set self.modifiers, self.modifier_label
self.modifier_vars = []
for modifier in self.modifiers:
variable = StringVar(self)
variable.set('')
self.modifier_vars.append(variable)
self.advanced = False
self.CreateWidgets()
self.LoadFinalKeyList()
self.update_idletasks()
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)
) ) #centre dialog over parent (or below htest box)
if not _utest:
self.deiconify() #geometry set, unhide
self.wait_window()
def showerror(self, *args, **kwargs):
# Make testing easier. Replace in #30751.
messagebox.showerror(*args, **kwargs)
def CreateWidgets(self):
frameMain = Frame(self,borderwidth=2,relief=SUNKEN)
frameMain.pack(side=TOP,expand=TRUE,fill=BOTH)
frameButtons=Frame(self)
frameButtons.pack(side=BOTTOM,fill=X)
self.buttonOK = Button(frameButtons,text='OK',
width=8,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)
self.frameKeySeqBasic = Frame(frameMain)
self.frameKeySeqAdvanced = Frame(frameMain)
self.frameControlsBasic = Frame(frameMain)
self.frameHelpAdvanced = Frame(frameMain)
self.frameKeySeqAdvanced.grid(row=0,column=0,sticky=NSEW,padx=5,pady=5)
self.frameKeySeqBasic.grid(row=0,column=0,sticky=NSEW,padx=5,pady=5)
self.frameKeySeqBasic.lift()
self.frameHelpAdvanced.grid(row=1,column=0,sticky=NSEW,padx=5)
self.frameControlsBasic.grid(row=1,column=0,sticky=NSEW,padx=5)
self.frameControlsBasic.lift()
self.buttonLevel = Button(frameMain,command=self.ToggleLevel,
text='Advanced Key Binding Entry >>')
self.buttonLevel.grid(row=2,column=0,stick=EW,padx=5,pady=5)
labelTitleBasic = Label(self.frameKeySeqBasic,
text="New keys for '"+self.action+"' :")
labelTitleBasic.pack(anchor=W)
labelKeysBasic = Label(self.frameKeySeqBasic,justify=LEFT,
textvariable=self.keyString,relief=GROOVE,borderwidth=2)
labelKeysBasic.pack(ipadx=5,ipady=5,fill=X)
self.modifier_checkbuttons = {}
column = 0
for modifier, variable in zip(self.modifiers, self.modifier_vars):
label = self.modifier_label.get(modifier, modifier)
check=Checkbutton(self.frameControlsBasic,
command=self.BuildKeyString,
text=label,variable=variable,onvalue=modifier,offvalue='')
check.grid(row=0,column=column,padx=2,sticky=W)
self.modifier_checkbuttons[modifier] = check
column += 1
labelFnAdvice=Label(self.frameControlsBasic,justify=LEFT,
text=\
"Select the desired modifier keys\n"+
"above, and the final key from the\n"+
"list on the right.\n\n" +
"Use upper case Symbols when using\n" +
"the Shift modifier. (Letters will be\n" +
"converted automatically.)")
labelFnAdvice.grid(row=1,column=0,columnspan=4,padx=2,sticky=W)
self.listKeysFinal=Listbox(self.frameControlsBasic,width=15,height=10,
selectmode=SINGLE)
self.listKeysFinal.bind('<ButtonRelease-1>',self.FinalKeySelected)
self.listKeysFinal.grid(row=0,column=4,rowspan=4,sticky=NS)
scrollKeysFinal=Scrollbar(self.frameControlsBasic,orient=VERTICAL,
command=self.listKeysFinal.yview)
self.listKeysFinal.config(yscrollcommand=scrollKeysFinal.set)
scrollKeysFinal.grid(row=0,column=5,rowspan=4,sticky=NS)
self.buttonClear=Button(self.frameControlsBasic,
text='Clear Keys',command=self.ClearKeySeq)
self.buttonClear.grid(row=2,column=0,columnspan=4)
labelTitleAdvanced = Label(self.frameKeySeqAdvanced,justify=LEFT,
text="Enter new binding(s) for '"+self.action+"' :\n"+
"(These bindings will not be checked for validity!)")
labelTitleAdvanced.pack(anchor=W)
self.entryKeysAdvanced=Entry(self.frameKeySeqAdvanced,
textvariable=self.keyString)
self.entryKeysAdvanced.pack(fill=X)
labelHelpAdvanced=Label(self.frameHelpAdvanced,justify=LEFT,
text="Key bindings are specified using Tkinter keysyms as\n"+
"in these samples: <Control-f>, <Shift-F2>, <F12>,\n"
"<Control-space>, <Meta-less>, <Control-Alt-Shift-X>.\n"
"Upper case is used when the Shift modifier is present!\n\n" +
"'Emacs style' multi-keystroke bindings are specified as\n" +
"follows: <Control-x><Control-y>, where the first key\n" +
"is the 'do-nothing' keybinding.\n\n" +
"Multiple separate bindings for one action should be\n"+
"separated by a space, eg., <Alt-v> <Meta-v>." )
labelHelpAdvanced.grid(row=0,column=0,sticky=NSEW)
def SetModifiersForPlatform(self):
"""Determine list of names of key modifiers for this platform.
The names are used to build Tk bindings -- it doesn't matter if the
keyboard has these keys, it matters if Tk understands them. The
order is also important: key binding equality depends on it, so
config-keys.def must use the same ordering.
"""
if sys.platform == "darwin":
self.modifiers = ['Shift', 'Control', 'Option', 'Command']
else:
self.modifiers = ['Control', 'Alt', 'Shift']
self.modifier_label = {'Control': 'Ctrl'} # short name
def ToggleLevel(self):
if self.buttonLevel.cget('text')[:8]=='Advanced':
self.ClearKeySeq()
self.buttonLevel.config(text='<< Basic Key Binding Entry')
self.frameKeySeqAdvanced.lift()
self.frameHelpAdvanced.lift()
self.entryKeysAdvanced.focus_set()
self.advanced = True
else:
self.ClearKeySeq()
self.buttonLevel.config(text='Advanced Key Binding Entry >>')
self.frameKeySeqBasic.lift()
self.frameControlsBasic.lift()
self.advanced = False
def FinalKeySelected(self,event):
self.BuildKeyString()
def BuildKeyString(self):
keyList = modifiers = self.GetModifiers()
finalKey = self.listKeysFinal.get(ANCHOR)
if finalKey:
finalKey = self.TranslateKey(finalKey, modifiers)
keyList.append(finalKey)
self.keyString.set('<' + '-'.join(keyList) + '>')
def GetModifiers(self):
modList = [variable.get() for variable in self.modifier_vars]
return [mod for mod in modList if mod]
def ClearKeySeq(self):
self.listKeysFinal.select_clear(0,END)
self.listKeysFinal.yview(MOVETO, '0.0')
for variable in self.modifier_vars:
variable.set('')
self.keyString.set('')
def LoadFinalKeyList(self):
#these tuples are also available for use in validity checks
self.functionKeys=('F1','F2','F3','F4','F5','F6','F7','F8','F9',
'F10','F11','F12')
self.alphanumKeys=tuple(string.ascii_lowercase+string.digits)
self.punctuationKeys=tuple('~!@#%^&*()_-+={}[]|;:,.<>/?')
self.whitespaceKeys=('Tab','Space','Return')
self.editKeys=('BackSpace','Delete','Insert')
self.moveKeys=('Home','End','Page Up','Page Down','Left Arrow',
'Right Arrow','Up Arrow','Down Arrow')
#make a tuple of most of the useful common 'final' keys
keys=(self.alphanumKeys+self.punctuationKeys+self.functionKeys+
self.whitespaceKeys+self.editKeys+self.moveKeys)
self.listKeysFinal.insert(END, *keys)
def TranslateKey(self, key, modifiers):
"Translate from keycap symbol to the Tkinter keysym"
translateDict = {'Space':'space',
'~':'asciitilde','!':'exclam','@':'at','#':'numbersign',
'%':'percent','^':'asciicircum','&':'ampersand','*':'asterisk',
'(':'parenleft',')':'parenright','_':'underscore','-':'minus',
'+':'plus','=':'equal','{':'braceleft','}':'braceright',
'[':'bracketleft',']':'bracketright','|':'bar',';':'semicolon',
':':'colon',',':'comma','.':'period','<':'less','>':'greater',
'/':'slash','?':'question','Page Up':'Prior','Page Down':'Next',
'Left Arrow':'Left','Right Arrow':'Right','Up Arrow':'Up',
'Down Arrow': 'Down', 'Tab':'Tab'}
if key in translateDict:
key = translateDict[key]
if 'Shift' in modifiers and key in string.ascii_lowercase:
key = key.upper()
key = 'Key-' + key
return key
def OK(self, event=None):
keys = self.keyString.get().strip()
if not keys:
self.showerror(title=self.keyerror_title, parent=self,
message="No key specified.")
return
if (self.advanced or self.KeysOK(keys)) and self.bind_ok(keys):
self.result = keys
self.grab_release()
self.destroy()
def Cancel(self, event=None):
self.result=''
self.grab_release()
self.destroy()
def KeysOK(self, keys):
'''Validity check on user's 'basic' keybinding selection.
Doesn't check the string produced by the advanced dialog because
'modifiers' isn't set.
'''
finalKey = self.listKeysFinal.get(ANCHOR)
modifiers = self.GetModifiers()
keysOK = False
title = self.keyerror_title
key_sequences = [key for keylist in self.currentKeySequences
for key in keylist]
if not keys.endswith('>'):
self.showerror(title, parent=self,
message='Missing the final Key')
elif (not modifiers
and finalKey not in self.functionKeys + self.moveKeys):
self.showerror(title=title, parent=self,
message='No modifier key(s) specified.')
elif (modifiers == ['Shift']) \
and (finalKey not in
self.functionKeys + self.moveKeys + ('Tab', 'Space')):
msg = 'The shift modifier by itself may not be used with'\
' this key symbol.'
self.showerror(title=title, parent=self, message=msg)
elif keys in key_sequences:
msg = 'This key combination is already in use.'
self.showerror(title=title, parent=self, message=msg)
else:
keysOK = True
return keysOK
def bind_ok(self, keys):
"Return True if Tcl accepts the new keys else show message."
try:
binding = self.bind(keys, lambda: None)
except TclError as err:
self.showerror(
title=self.keyerror_title, parent=self,
message=(f'The entered key sequence is not accepted.\n\n'
f'Error: {err}'))
return False
else:
self.unbind(keys, binding)
return True
if __name__ == '__main__':
from unittest import main
main('idlelib.idle_test.test_config_key', verbosity=2, exit=False)
from idlelib.idle_test.htest import run
run(GetKeysDialog)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,550 @@
import bdb
import os
from tkinter import *
from tkinter.ttk import Scrollbar
from idlelib import macosx
from idlelib.scrolledlist import ScrolledList
from idlelib.window import ListedToplevel
class Idb(bdb.Bdb):
def __init__(self, gui):
self.gui = gui # An instance of Debugger or proxy of remote.
bdb.Bdb.__init__(self)
def user_line(self, frame):
if self.in_rpc_code(frame):
self.set_step()
return
message = self.__frame2message(frame)
try:
self.gui.interaction(message, frame)
except TclError: # When closing debugger window with [x] in 3.x
pass
def user_exception(self, frame, info):
if self.in_rpc_code(frame):
self.set_step()
return
message = self.__frame2message(frame)
self.gui.interaction(message, frame, info)
def in_rpc_code(self, frame):
if frame.f_code.co_filename.count('rpc.py'):
return True
else:
prev_frame = frame.f_back
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)
def __frame2message(self, frame):
code = frame.f_code
filename = code.co_filename
lineno = frame.f_lineno
basename = os.path.basename(filename)
message = "%s:%s" % (basename, lineno)
if code.co_name != "?":
message = "%s: %s()" % (message, code.co_name)
return message
class Debugger:
vstack = vsource = vlocals = vglobals = None
def __init__(self, pyshell, idb=None):
if idb is None:
idb = Idb(self)
self.pyshell = pyshell
self.idb = idb # If passed, a proxy of remote instance.
self.frame = None
self.make_gui()
self.interacting = 0
self.nesting_level = 0
def run(self, *args):
# Deal with the scenario where we've already got a program running
# in the debugger and we want to start another. If that is the case,
# our second 'run' was invoked from an event dispatched not from
# the main event loop, but from the nested event loop in 'interaction'
# below. So our stack looks something like this:
# outer main event loop
# run()
# <running program with traces>
# callback to debugger's interaction()
# nested event loop
# run() for second command
#
# This kind of nesting of event loops causes all kinds of problems
# (see e.g. issue #24455) especially when dealing with running as a
# subprocess, where there's all kinds of extra stuff happening in
# there - insert a traceback.print_stack() to check it out.
#
# By this point, we've already called restart_subprocess() in
# ScriptBinding. However, we also need to unwind the stack back to
# that outer event loop. To accomplish this, we:
# - return immediately from the nested run()
# - abort_loop ensures the nested event loop will terminate
# - the debugger's interaction routine completes normally
# - the restart_subprocess() will have taken care of stopping
# the running program, which will also let the outer run complete
#
# That leaves us back at the outer main event loop, at which point our
# after event can fire, and we'll come back to this routine with a
# clean stack.
if self.nesting_level > 0:
self.abort_loop()
self.root.after(100, lambda: self.run(*args))
return
try:
self.interacting = 1
return self.idb.run(*args)
finally:
self.interacting = 0
def close(self, event=None):
try:
self.quit()
except Exception:
pass
if self.interacting:
self.top.bell()
return
if self.stackviewer:
self.stackviewer.close(); self.stackviewer = None
# Clean up pyshell if user clicked debugger control close widget.
# (Causes a harmless extra cycle through close_debugger() if user
# toggled debugger from pyshell Debug menu)
self.pyshell.close_debugger()
# Now close the debugger control window....
self.top.destroy()
def make_gui(self):
pyshell = self.pyshell
self.flist = pyshell.flist
self.root = root = pyshell.root
self.top = top = ListedToplevel(root)
self.top.wm_title("Debug Control")
self.top.wm_iconname("Debug")
top.wm_protocol("WM_DELETE_WINDOW", self.close)
self.top.bind("<Escape>", self.close)
#
self.bframe = bframe = Frame(top)
self.bframe.pack(anchor="w")
self.buttons = bl = []
#
self.bcont = b = Button(bframe, text="Go", command=self.cont)
bl.append(b)
self.bstep = b = Button(bframe, text="Step", command=self.step)
bl.append(b)
self.bnext = b = Button(bframe, text="Over", command=self.next)
bl.append(b)
self.bret = b = Button(bframe, text="Out", command=self.ret)
bl.append(b)
self.bret = b = Button(bframe, text="Quit", command=self.quit)
bl.append(b)
#
for b in bl:
b.configure(state="disabled")
b.pack(side="left")
#
self.cframe = cframe = Frame(bframe)
self.cframe.pack(side="left")
#
if not self.vstack:
self.__class__.vstack = BooleanVar(top)
self.vstack.set(1)
self.bstack = Checkbutton(cframe,
text="Stack", command=self.show_stack, variable=self.vstack)
self.bstack.grid(row=0, column=0)
if not self.vsource:
self.__class__.vsource = BooleanVar(top)
self.bsource = Checkbutton(cframe,
text="Source", command=self.show_source, variable=self.vsource)
self.bsource.grid(row=0, column=1)
if not self.vlocals:
self.__class__.vlocals = BooleanVar(top)
self.vlocals.set(1)
self.blocals = Checkbutton(cframe,
text="Locals", command=self.show_locals, variable=self.vlocals)
self.blocals.grid(row=1, column=0)
if not self.vglobals:
self.__class__.vglobals = BooleanVar(top)
self.bglobals = Checkbutton(cframe,
text="Globals", command=self.show_globals, variable=self.vglobals)
self.bglobals.grid(row=1, column=1)
#
self.status = Label(top, anchor="w")
self.status.pack(anchor="w")
self.error = Label(top, anchor="w")
self.error.pack(anchor="w", fill="x")
self.errorbg = self.error.cget("background")
#
self.fstack = Frame(top, height=1)
self.fstack.pack(expand=1, fill="both")
self.flocals = Frame(top)
self.flocals.pack(expand=1, fill="both")
self.fglobals = Frame(top, height=1)
self.fglobals.pack(expand=1, fill="both")
#
if self.vstack.get():
self.show_stack()
if self.vlocals.get():
self.show_locals()
if self.vglobals.get():
self.show_globals()
def interaction(self, message, frame, info=None):
self.frame = frame
self.status.configure(text=message)
#
if info:
type, value, tb = info
try:
m1 = type.__name__
except AttributeError:
m1 = "%s" % str(type)
if value is not None:
try:
m1 = "%s: %s" % (m1, str(value))
except:
pass
bg = "yellow"
else:
m1 = ""
tb = None
bg = self.errorbg
self.error.configure(text=m1, background=bg)
#
sv = self.stackviewer
if sv:
stack, i = self.idb.get_stack(self.frame, tb)
sv.load_stack(stack, i)
#
self.show_variables(1)
#
if self.vsource.get():
self.sync_source_line()
#
for b in self.buttons:
b.configure(state="normal")
#
self.top.wakeup()
# Nested main loop: Tkinter's main loop is not reentrant, so use
# Tcl's vwait facility, which reenters the event loop until an
# event handler sets the variable we're waiting on
self.nesting_level += 1
self.root.tk.call('vwait', '::idledebugwait')
self.nesting_level -= 1
#
for b in self.buttons:
b.configure(state="disabled")
self.status.configure(text="")
self.error.configure(text="", background=self.errorbg)
self.frame = None
def sync_source_line(self):
frame = self.frame
if not frame:
return
filename, lineno = self.__frame2fileline(frame)
if filename[:1] + filename[-1:] != "<>" and os.path.exists(filename):
self.flist.gotofileline(filename, lineno)
def __frame2fileline(self, frame):
code = frame.f_code
filename = code.co_filename
lineno = frame.f_lineno
return filename, lineno
def cont(self):
self.idb.set_continue()
self.abort_loop()
def step(self):
self.idb.set_step()
self.abort_loop()
def next(self):
self.idb.set_next(self.frame)
self.abort_loop()
def ret(self):
self.idb.set_return(self.frame)
self.abort_loop()
def quit(self):
self.idb.set_quit()
self.abort_loop()
def abort_loop(self):
self.root.tk.call('set', '::idledebugwait', '1')
stackviewer = None
def show_stack(self):
if not self.stackviewer and self.vstack.get():
self.stackviewer = sv = StackViewer(self.fstack, self.flist, self)
if self.frame:
stack, i = self.idb.get_stack(self.frame, None)
sv.load_stack(stack, i)
else:
sv = self.stackviewer
if sv and not self.vstack.get():
self.stackviewer = None
sv.close()
self.fstack['height'] = 1
def show_source(self):
if self.vsource.get():
self.sync_source_line()
def show_frame(self, stackitem):
self.frame = stackitem[0] # lineno is stackitem[1]
self.show_variables()
localsviewer = None
globalsviewer = None
def show_locals(self):
lv = self.localsviewer
if self.vlocals.get():
if not lv:
self.localsviewer = NamespaceViewer(self.flocals, "Locals")
else:
if lv:
self.localsviewer = None
lv.close()
self.flocals['height'] = 1
self.show_variables()
def show_globals(self):
gv = self.globalsviewer
if self.vglobals.get():
if not gv:
self.globalsviewer = NamespaceViewer(self.fglobals, "Globals")
else:
if gv:
self.globalsviewer = None
gv.close()
self.fglobals['height'] = 1
self.show_variables()
def show_variables(self, force=0):
lv = self.localsviewer
gv = self.globalsviewer
frame = self.frame
if not frame:
ldict = gdict = None
else:
ldict = frame.f_locals
gdict = frame.f_globals
if lv and gv and ldict is gdict:
ldict = None
if lv:
lv.load_dict(ldict, force, self.pyshell.interp.rpcclt)
if gv:
gv.load_dict(gdict, force, self.pyshell.interp.rpcclt)
def set_breakpoint_here(self, filename, lineno):
self.idb.set_break(filename, lineno)
def clear_breakpoint_here(self, filename, lineno):
self.idb.clear_break(filename, lineno)
def clear_file_breaks(self, filename):
self.idb.clear_all_file_breaks(filename)
def load_breakpoints(self):
"Load PyShellEditorWindow breakpoints into subprocess debugger"
for editwin in self.pyshell.flist.inversedict:
filename = editwin.io.filename
try:
for lineno in editwin.breakpoints:
self.set_breakpoint_here(filename, lineno)
except AttributeError:
continue
class StackViewer(ScrolledList):
def __init__(self, master, flist, gui):
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.
ScrolledList.__init__(self, master)
else:
ScrolledList.__init__(self, master, width=80)
self.flist = flist
self.gui = gui
self.stack = []
def load_stack(self, stack, index=None):
self.stack = stack
self.clear()
for i in range(len(stack)):
frame, lineno = stack[i]
try:
modname = frame.f_globals["__name__"]
except:
modname = "?"
code = frame.f_code
filename = code.co_filename
funcname = code.co_name
import linecache
sourceline = linecache.getline(filename, lineno)
sourceline = sourceline.strip()
if funcname in ("?", "", None):
item = "%s, line %d: %s" % (modname, lineno, sourceline)
else:
item = "%s.%s(), line %d: %s" % (modname, funcname,
lineno, sourceline)
if i == index:
item = "> " + item
self.append(item)
if index is not None:
self.select(index)
def popup_event(self, event):
"override base method"
if self.stack:
return ScrolledList.popup_event(self, event)
def fill_menu(self):
"override base method"
menu = self.menu
menu.add_command(label="Go to source line",
command=self.goto_source_line)
menu.add_command(label="Show stack frame",
command=self.show_stack_frame)
def on_select(self, index):
"override base method"
if 0 <= index < len(self.stack):
self.gui.show_frame(self.stack[index])
def on_double(self, index):
"override base method"
self.show_source(index)
def goto_source_line(self):
index = self.listbox.index("active")
self.show_source(index)
def show_stack_frame(self):
index = self.listbox.index("active")
if 0 <= index < len(self.stack):
self.gui.show_frame(self.stack[index])
def show_source(self, index):
if not (0 <= index < len(self.stack)):
return
frame, lineno = self.stack[index]
code = frame.f_code
filename = code.co_filename
if os.path.isfile(filename):
edit = self.flist.open(filename)
if edit:
edit.gotoline(lineno)
class NamespaceViewer:
def __init__(self, master, title, dict=None):
width = 0
height = 40
if dict:
height = 20*len(dict) # XXX 20 == observed height of Entry widget
self.master = master
self.title = title
import reprlib
self.repr = reprlib.Repr()
self.repr.maxstring = 60
self.repr.maxother = 60
self.frame = frame = Frame(master)
self.frame.pack(expand=1, fill="both")
self.label = Label(frame, text=title, borderwidth=2, relief="groove")
self.label.pack(fill="x")
self.vbar = vbar = Scrollbar(frame, name="vbar")
vbar.pack(side="right", fill="y")
self.canvas = canvas = Canvas(frame,
height=min(300, max(40, height)),
scrollregion=(0, 0, width, height))
canvas.pack(side="left", fill="both", expand=1)
vbar["command"] = canvas.yview
canvas["yscrollcommand"] = vbar.set
self.subframe = subframe = Frame(canvas)
self.sfid = canvas.create_window(0, 0, window=subframe, anchor="nw")
self.load_dict(dict)
dict = -1
def load_dict(self, dict, force=0, rpc_client=None):
if dict is self.dict and not force:
return
subframe = self.subframe
frame = self.frame
for c in list(subframe.children.values()):
c.destroy()
self.dict = None
if not dict:
l = Label(subframe, text="None")
l.grid(row=0, column=0)
else:
#names = sorted(dict)
###
# Because of (temporary) limitations on the dict_keys type (not yet
# public or pickleable), have the subprocess to send a list of
# keys, not a dict_keys object. sorted() will take a dict_keys
# (no subprocess) or a list.
#
# 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 debugger_r.DictProxy.
###
keys_list = dict.keys()
names = sorted(keys_list)
###
row = 0
for name in names:
value = dict[name]
svalue = self.repr.repr(value) # repr(value)
# Strip extra quotes caused by calling repr on the (already)
# repr'd value sent across the RPC interface:
if rpc_client:
svalue = svalue[1:-1]
l = Label(subframe, text=name)
l.grid(row=row, column=0, sticky="nw")
l = Entry(subframe, width=0, borderwidth=0)
l.insert(0, svalue)
l.grid(row=row, column=1, sticky="nw")
row = row+1
self.dict = dict
# XXX Could we use a <Configure> callback for the following?
subframe.update_idletasks() # Alas!
width = subframe.winfo_reqwidth()
height = subframe.winfo_reqheight()
canvas = self.canvas
self.canvas["scrollregion"] = (0, 0, width, height)
if height > 300:
canvas["height"] = 300
frame.pack(expand=1)
else:
canvas["height"] = height
frame.pack(expand=0)
def close(self):
self.frame.destroy()
if __name__ == "__main__":
from unittest import main
main('idlelib.idle_test.test_debugger', verbosity=2, exit=False)
# TODO: htest?

View file

@ -0,0 +1,393 @@
"""Support for remote Python debugging.
Some ASCII art to describe the structure:
IN PYTHON SUBPROCESS # IN IDLE PROCESS
#
# oid='gui_adapter'
+----------+ # +------------+ +-----+
| GUIProxy |--remote#call-->| GUIAdapter |--calls-->| GUI |
+-----+--calls-->+----------+ # +------------+ +-----+
| Idb | # /
+-----+<-calls--+------------+ # +----------+<--calls-/
| IdbAdapter |<--remote#call--| IdbProxy |
+------------+ # +----------+
oid='idb_adapter' #
The purpose of the Proxy and Adapter classes is to translate certain
arguments and return values that cannot be transported through the RPC
barrier, in particular frame and traceback objects.
"""
import types
from idlelib import debugger
debugging = 0
idb_adap_oid = "idb_adapter"
gui_adap_oid = "gui_adapter"
#=======================================
#
# In the PYTHON subprocess:
frametable = {}
dicttable = {}
codetable = {}
tracebacktable = {}
def wrap_frame(frame):
fid = id(frame)
frametable[fid] = frame
return fid
def wrap_info(info):
"replace info[2], a traceback instance, by its ID"
if info is None:
return None
else:
traceback = info[2]
assert isinstance(traceback, types.TracebackType)
traceback_id = id(traceback)
tracebacktable[traceback_id] = traceback
modified_info = (info[0], info[1], traceback_id)
return modified_info
class GUIProxy:
def __init__(self, conn, gui_adap_oid):
self.conn = conn
self.oid = gui_adap_oid
def interaction(self, message, frame, info=None):
# calls rpc.SocketIO.remotecall() via run.MyHandler instance
# pass frame and traceback object IDs instead of the objects themselves
self.conn.remotecall(self.oid, "interaction",
(message, wrap_frame(frame), wrap_info(info)),
{})
class IdbAdapter:
def __init__(self, idb):
self.idb = idb
#----------called by an IdbProxy----------
def set_step(self):
self.idb.set_step()
def set_quit(self):
self.idb.set_quit()
def set_continue(self):
self.idb.set_continue()
def set_next(self, fid):
frame = frametable[fid]
self.idb.set_next(frame)
def set_return(self, fid):
frame = frametable[fid]
self.idb.set_return(frame)
def get_stack(self, fid, tbid):
frame = frametable[fid]
if tbid is None:
tb = None
else:
tb = tracebacktable[tbid]
stack, i = self.idb.get_stack(frame, tb)
stack = [(wrap_frame(frame2), k) for frame2, k in stack]
return stack, i
def run(self, cmd):
import __main__
self.idb.run(cmd, __main__.__dict__)
def set_break(self, filename, lineno):
msg = self.idb.set_break(filename, lineno)
return msg
def clear_break(self, filename, lineno):
msg = self.idb.clear_break(filename, lineno)
return msg
def clear_all_file_breaks(self, filename):
msg = self.idb.clear_all_file_breaks(filename)
return msg
#----------called by a FrameProxy----------
def frame_attr(self, fid, name):
frame = frametable[fid]
return getattr(frame, name)
def frame_globals(self, fid):
frame = frametable[fid]
dict = frame.f_globals
did = id(dict)
dicttable[did] = dict
return did
def frame_locals(self, fid):
frame = frametable[fid]
dict = frame.f_locals
did = id(dict)
dicttable[did] = dict
return did
def frame_code(self, fid):
frame = frametable[fid]
code = frame.f_code
cid = id(code)
codetable[cid] = code
return cid
#----------called by a CodeProxy----------
def code_name(self, cid):
code = codetable[cid]
return code.co_name
def code_filename(self, cid):
code = codetable[cid]
return code.co_filename
#----------called by a DictProxy----------
def dict_keys(self, did):
raise NotImplementedError("dict_keys not public or pickleable")
## dict = dicttable[did]
## return dict.keys()
### Needed until dict_keys is type is finished and pickealable.
### Will probably need to extend rpc.py:SocketIO._proxify at that time.
def dict_keys_list(self, did):
dict = dicttable[did]
return list(dict.keys())
def dict_item(self, did, key):
dict = dicttable[did]
value = dict[key]
value = repr(value) ### can't pickle module 'builtins'
return value
#----------end class IdbAdapter----------
def start_debugger(rpchandler, gui_adap_oid):
"""Start the debugger and its RPC link in the Python subprocess
Start the subprocess side of the split debugger and set up that side of the
RPC link by instantiating the GUIProxy, Idb debugger, and IdbAdapter
objects and linking them together. Register the IdbAdapter with the
RPCServer to handle RPC requests from the split debugger GUI via the
IdbProxy.
"""
gui_proxy = GUIProxy(rpchandler, gui_adap_oid)
idb = debugger.Idb(gui_proxy)
idb_adap = IdbAdapter(idb)
rpchandler.register(idb_adap_oid, idb_adap)
return idb_adap_oid
#=======================================
#
# In the IDLE process:
class FrameProxy:
def __init__(self, conn, fid):
self._conn = conn
self._fid = fid
self._oid = "idb_adapter"
self._dictcache = {}
def __getattr__(self, name):
if name[:1] == "_":
raise AttributeError(name)
if name == "f_code":
return self._get_f_code()
if name == "f_globals":
return self._get_f_globals()
if name == "f_locals":
return self._get_f_locals()
return self._conn.remotecall(self._oid, "frame_attr",
(self._fid, name), {})
def _get_f_code(self):
cid = self._conn.remotecall(self._oid, "frame_code", (self._fid,), {})
return CodeProxy(self._conn, self._oid, cid)
def _get_f_globals(self):
did = self._conn.remotecall(self._oid, "frame_globals",
(self._fid,), {})
return self._get_dict_proxy(did)
def _get_f_locals(self):
did = self._conn.remotecall(self._oid, "frame_locals",
(self._fid,), {})
return self._get_dict_proxy(did)
def _get_dict_proxy(self, did):
if did in self._dictcache:
return self._dictcache[did]
dp = DictProxy(self._conn, self._oid, did)
self._dictcache[did] = dp
return dp
class CodeProxy:
def __init__(self, conn, oid, cid):
self._conn = conn
self._oid = oid
self._cid = cid
def __getattr__(self, name):
if name == "co_name":
return self._conn.remotecall(self._oid, "code_name",
(self._cid,), {})
if name == "co_filename":
return self._conn.remotecall(self._oid, "code_filename",
(self._cid,), {})
class DictProxy:
def __init__(self, conn, oid, did):
self._conn = conn
self._oid = oid
self._did = did
## def keys(self):
## return self._conn.remotecall(self._oid, "dict_keys", (self._did,), {})
# 'temporary' until dict_keys is a pickleable built-in type
def keys(self):
return self._conn.remotecall(self._oid,
"dict_keys_list", (self._did,), {})
def __getitem__(self, key):
return self._conn.remotecall(self._oid, "dict_item",
(self._did, key), {})
def __getattr__(self, name):
##print("*** Failed DictProxy.__getattr__:", name)
raise AttributeError(name)
class GUIAdapter:
def __init__(self, conn, gui):
self.conn = conn
self.gui = gui
def interaction(self, message, fid, modified_info):
##print("*** Interaction: (%s, %s, %s)" % (message, fid, modified_info))
frame = FrameProxy(self.conn, fid)
self.gui.interaction(message, frame, modified_info)
class IdbProxy:
def __init__(self, conn, shell, oid):
self.oid = oid
self.conn = conn
self.shell = shell
def call(self, methodname, *args, **kwargs):
##print("*** IdbProxy.call %s %s %s" % (methodname, args, kwargs))
value = self.conn.remotecall(self.oid, methodname, args, kwargs)
##print("*** IdbProxy.call %s returns %r" % (methodname, value))
return value
def run(self, cmd, locals):
# Ignores locals on purpose!
seq = self.conn.asyncqueue(self.oid, "run", (cmd,), {})
self.shell.interp.active_seq = seq
def get_stack(self, frame, tbid):
# passing frame and traceback IDs, not the objects themselves
stack, i = self.call("get_stack", frame._fid, tbid)
stack = [(FrameProxy(self.conn, fid), k) for fid, k in stack]
return stack, i
def set_continue(self):
self.call("set_continue")
def set_step(self):
self.call("set_step")
def set_next(self, frame):
self.call("set_next", frame._fid)
def set_return(self, frame):
self.call("set_return", frame._fid)
def set_quit(self):
self.call("set_quit")
def set_break(self, filename, lineno):
msg = self.call("set_break", filename, lineno)
return msg
def clear_break(self, filename, lineno):
msg = self.call("clear_break", filename, lineno)
return msg
def clear_all_file_breaks(self, filename):
msg = self.call("clear_all_file_breaks", filename)
return msg
def start_remote_debugger(rpcclt, pyshell):
"""Start the subprocess debugger, initialize the debugger GUI and RPC link
Request the RPCServer start the Python subprocess debugger and link. Set
up the Idle side of the split debugger by instantiating the IdbProxy,
debugger GUI, and debugger GUIAdapter objects and linking them together.
Register the GUIAdapter with the RPCClient to handle debugger GUI
interaction requests coming from the subprocess debugger via the GUIProxy.
The IdbAdapter will pass execution and environment requests coming from the
Idle debugger GUI to the subprocess debugger via the IdbProxy.
"""
global idb_adap_oid
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_adap = GUIAdapter(rpcclt, gui)
rpcclt.register(gui_adap_oid, gui_adap)
return gui
def close_remote_debugger(rpcclt):
"""Shut down subprocess debugger and Idle side of debugger RPC link
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().)
"""
close_subprocess_debugger(rpcclt)
rpcclt.unregister(gui_adap_oid)
def close_subprocess_debugger(rpcclt):
rpcclt.remotecall("exec", "stop_the_debugger", (idb_adap_oid,), {})
def restart_subprocess_debugger(rpcclt):
idb_adap_oid_ret = rpcclt.remotecall("exec", "start_the_debugger",\
(gui_adap_oid,), {})
assert idb_adap_oid_ret == idb_adap_oid, 'Idb restarted with different oid'
if __name__ == "__main__":
from unittest import main
main('idlelib.idle_test.test_debugger', verbosity=2, exit=False)

View file

@ -0,0 +1,142 @@
# XXX TO DO:
# - popup menu
# - support partial or total redisplay
# - more doc strings
# - tooltips
# object browser
# XXX TO DO:
# - for classes/modules, add "open source" to object browser
from reprlib import Repr
from idlelib.tree import TreeItem, TreeNode, ScrolledCanvas
myrepr = Repr()
myrepr.maxstring = 100
myrepr.maxother = 100
class ObjectTreeItem(TreeItem):
def __init__(self, labeltext, object, setfunction=None):
self.labeltext = labeltext
self.object = object
self.setfunction = setfunction
def GetLabelText(self):
return self.labeltext
def GetText(self):
return myrepr.repr(self.object)
def GetIconName(self):
if not self.IsExpandable():
return "python"
def IsEditable(self):
return self.setfunction is not None
def SetText(self, text):
try:
value = eval(text)
self.setfunction(value)
except:
pass
else:
self.object = value
def IsExpandable(self):
return not not dir(self.object)
def GetSubList(self):
keys = dir(self.object)
sublist = []
for key in keys:
try:
value = getattr(self.object, key)
except AttributeError:
continue
item = make_objecttreeitem(
str(key) + " =",
value,
lambda value, key=key, object=self.object:
setattr(object, key, value))
sublist.append(item)
return sublist
class ClassTreeItem(ObjectTreeItem):
def IsExpandable(self):
return True
def GetSubList(self):
sublist = ObjectTreeItem.GetSubList(self)
if len(self.object.__bases__) == 1:
item = make_objecttreeitem("__bases__[0] =",
self.object.__bases__[0])
else:
item = make_objecttreeitem("__bases__ =", self.object.__bases__)
sublist.insert(0, item)
return sublist
class AtomicObjectTreeItem(ObjectTreeItem):
def IsExpandable(self):
return False
class SequenceTreeItem(ObjectTreeItem):
def IsExpandable(self):
return len(self.object) > 0
def keys(self):
return range(len(self.object))
def GetSubList(self):
sublist = []
for key in self.keys():
try:
value = self.object[key]
except KeyError:
continue
def setfunction(value, key=key, object=self.object):
object[key] = value
item = make_objecttreeitem("%r:" % (key,), value, setfunction)
sublist.append(item)
return sublist
class DictTreeItem(SequenceTreeItem):
def keys(self):
keys = list(self.object.keys())
try:
keys.sort()
except:
pass
return keys
dispatch = {
int: AtomicObjectTreeItem,
float: AtomicObjectTreeItem,
str: AtomicObjectTreeItem,
tuple: SequenceTreeItem,
list: SequenceTreeItem,
dict: DictTreeItem,
type: ClassTreeItem,
}
def make_objecttreeitem(labeltext, object, setfunction=None):
t = type(object)
if t in dispatch:
c = dispatch[t]
else:
c = ObjectTreeItem
return c(labeltext, object, setfunction)
def _object_browser(parent): # htest #
import sys
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()
if __name__ == '__main__':
from unittest import main
main('idlelib.idle_test.test_debugobj', verbosity=2, exit=False)
from idlelib.idle_test.htest import run
run(_object_browser)

View file

@ -0,0 +1,41 @@
from idlelib import rpc
def remote_object_tree_item(item):
wrapper = WrappedObjectTreeItem(item)
oid = id(wrapper)
rpc.objecttable[oid] = wrapper
return oid
class WrappedObjectTreeItem:
# Lives in PYTHON subprocess
def __init__(self, item):
self.__item = item
def __getattr__(self, name):
value = getattr(self.__item, name)
return value
def _GetSubList(self):
sub_list = self.__item._GetSubList()
return list(map(remote_object_tree_item, sub_list))
class StubObjectTreeItem:
# Lives in IDLE process
def __init__(self, sockio, oid):
self.sockio = sockio
self.oid = oid
def __getattr__(self, name):
value = rpc.MethodProxy(self.sockio, self.oid, name)
return value
def _GetSubList(self):
sub_list = self.sockio.remotecall(self.oid, "_GetSubList", (), {})
return [StubObjectTreeItem(self.sockio, oid) for oid in sub_list]
if __name__ == '__main__':
from unittest import main
main('idlelib.idle_test.test_debugobj_r', verbosity=2)

View file

@ -0,0 +1,33 @@
class Delegator:
def __init__(self, delegate=None):
self.delegate = delegate
self.__cache = set()
# Cache is used to only remove added attributes
# when changing the delegate.
def __getattr__(self, name):
attr = getattr(self.delegate, name) # May raise AttributeError
setattr(self, name, attr)
self.__cache.add(name)
return attr
def resetcache(self):
"Removes added attributes while leaving original attributes."
# Function is really about resetting delagator dict
# to original state. Cache is just a means
for key in self.__cache:
try:
delattr(self, key)
except AttributeError:
pass
self.__cache.clear()
def setdelegate(self, delegate):
"Reset attributes and change delegate."
self.resetcache()
self.delegate = delegate
if __name__ == '__main__':
from unittest import main
main('idlelib.idle_test.test_delegator', verbosity=2)

View file

@ -0,0 +1,58 @@
"""
OptionMenu widget modified to allow dynamic menu reconfiguration
and setting of highlightthickness
"""
import copy
from tkinter import OptionMenu, _setit, StringVar, Button
class DynOptionMenu(OptionMenu):
"""
unlike OptionMenu, our kwargs can include highlightthickness
"""
def __init__(self, master, variable, value, *values, **kwargs):
# TODO copy value instead of whole dict
kwargsCopy=copy.copy(kwargs)
if 'highlightthickness' in list(kwargs.keys()):
del(kwargs['highlightthickness'])
OptionMenu.__init__(self, master, variable, value, *values, **kwargs)
self.config(highlightthickness=kwargsCopy.get('highlightthickness'))
#self.menu=self['menu']
self.variable=variable
self.command=kwargs.get('command')
def SetMenu(self,valueList,value=None):
"""
clear and reload the menu with a new set of options.
valueList - list of new options
value - initial value to set the optionmenu's menubutton to
"""
self['menu'].delete(0,'end')
for item in valueList:
self['menu'].add_command(label=item,
command=_setit(self.variable,item,self.command))
if value:
self.variable.set(value)
def _dyn_option_menu(parent): # htest #
from tkinter import Toplevel # + StringVar, Button
top = Toplevel(parent)
top.title("Tets dynamic option menu")
x, y = map(int, parent.geometry().split('+')[1:])
top.geometry("200x100+%d+%d" % (x + 250, y + 175))
top.focus_set()
var = StringVar(top)
var.set("Old option set") #Set the default value
dyn = DynOptionMenu(top,var, "old1","old2","old3","old4")
dyn.pack()
def update():
dyn.SetMenu(["new1","new2","new3","new4"], value="new option set")
button = Button(top, text="Change option set", command=update)
button.pack()
if __name__ == '__main__':
from idlelib.idle_test.htest import run
run(_dyn_option_menu)

1725
third_party/python/Lib/idlelib/editor.py vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,83 @@
Writing an IDLE extension
=========================
An IDLE extension can define new key bindings and menu entries for IDLE
edit windows. There is a simple mechanism to load extensions when IDLE
starts up and to attach them to each edit window. (It is also possible
to make other changes to IDLE, but this must be done by editing the IDLE
source code.)
The list of extensions loaded at startup time is configured by editing
the file config-extensions.def. See below for details.
An IDLE extension is defined by a class. Methods of the class define
actions that are invoked by event bindings or menu entries. Class (or
instance) variables define the bindings and menu additions; these are
automatically applied by IDLE when the extension is linked to an edit
window.
An IDLE extension class is instantiated with a single argument,
`editwin', an EditorWindow instance. The extension cannot assume much
about this argument, but it is guaranteed to have the following instance
variables:
text a Text instance (a widget)
io an IOBinding instance (more about this later)
flist the FileList instance (shared by all edit windows)
(There are a few more, but they are rarely useful.)
The extension class must not directly bind Window Manager (e.g. X) events.
Rather, it must define one or more virtual events, e.g. <<zoom-height>>, and
corresponding methods, e.g. zoom_height_event(). The virtual events will be
bound to the corresponding methods, and Window Manager events can then be bound
to the virtual events. (This indirection is done so that the key bindings can
easily be changed, and so that other sources of virtual events can exist, such
as menu entries.)
An extension can define menu entries. This is done with a class or instance
variable named menudefs; it should be a list of pairs, where each pair is a
menu name (lowercase) and a list of menu entries. Each menu entry is either
None (to insert a separator entry) or a pair of strings (menu_label,
virtual_event). Here, menu_label is the label of the menu entry, and
virtual_event is the virtual event to be generated when the entry is selected.
An underscore in the menu label is removed; the character following the
underscore is displayed underlined, to indicate the shortcut character (for
Windows).
At the moment, extensions cannot define whole new menus; they must define
entries in existing menus. Some menus are not present on some windows; such
entry definitions are then ignored, but key bindings are still applied. (This
should probably be refined in the future.)
Extensions are not required to define menu entries for all the events they
implement. (They are also not required to create keybindings, but in that
case there must be empty bindings in cofig-extensions.def)
Here is a complete example:
class ZoomHeight:
menudefs = [
('edit', [
None, # Separator
('_Zoom Height', '<<zoom-height>>'),
])
]
def __init__(self, editwin):
self.editwin = editwin
def zoom_height_event(self, event):
"...Do what you want here..."
The final piece of the puzzle is the file "config-extensions.def", which is
used to configure the loading of extensions and to establish key (or, more
generally, event) bindings to the virtual events defined in the extensions.
See the comments at the top of config-extensions.def for information. It's
currently necessary to manually modify that file to change IDLE's extension
loading or extension key bindings.
For further information on binding refer to the Tkinter Resources web page at
python.org and to the Tk Command "bind" man page.

View file

@ -0,0 +1,131 @@
"idlelib.filelist"
import os
from tkinter import messagebox as tkMessageBox
class FileList:
# N.B. this import overridden in PyShellFileList.
from idlelib.editor import EditorWindow
def __init__(self, root):
self.root = root
self.dict = {}
self.inversedict = {}
self.vars = {} # For EditorWindow.getrawvar (shared Tcl variables)
def open(self, filename, action=None):
assert filename
filename = self.canonize(filename)
if os.path.isdir(filename):
# This can happen when bad filename is passed on command line:
tkMessageBox.showerror(
"File Error",
"%r is a directory." % (filename,),
master=self.root)
return None
key = os.path.normcase(filename)
if key in self.dict:
edit = self.dict[key]
edit.top.wakeup()
return edit
if action:
# Don't create window, perform 'action', e.g. open in same window
return action(filename)
else:
edit = self.EditorWindow(self, filename, key)
if edit.good_load:
return edit
else:
edit._close()
return None
def gotofileline(self, filename, lineno=None):
edit = self.open(filename)
if edit is not None and lineno is not None:
edit.gotoline(lineno)
def new(self, filename=None):
return self.EditorWindow(self, filename)
def close_all_callback(self, *args, **kwds):
for edit in list(self.inversedict):
reply = edit.close()
if reply == "cancel":
break
return "break"
def unregister_maybe_terminate(self, edit):
try:
key = self.inversedict[edit]
except KeyError:
print("Don't know this EditorWindow object. (close)")
return
if key:
del self.dict[key]
del self.inversedict[edit]
if not self.inversedict:
self.root.quit()
def filename_changed_edit(self, edit):
edit.saved_change_hook()
try:
key = self.inversedict[edit]
except KeyError:
print("Don't know this EditorWindow object. (rename)")
return
filename = edit.io.filename
if not filename:
if key:
del self.dict[key]
self.inversedict[edit] = None
return
filename = self.canonize(filename)
newkey = os.path.normcase(filename)
if newkey == key:
return
if newkey in self.dict:
conflict = self.dict[newkey]
self.inversedict[conflict] = None
tkMessageBox.showerror(
"Name Conflict",
"You now have multiple edit windows open for %r" % (filename,),
master=self.root)
self.dict[newkey] = edit
self.inversedict[edit] = newkey
if key:
try:
del self.dict[key]
except KeyError:
pass
def canonize(self, filename):
if not os.path.isabs(filename):
try:
pwd = os.getcwd()
except OSError:
pass
else:
filename = os.path.join(pwd, filename)
return os.path.normpath(filename)
def _test(): # TODO check and convert to htest
from tkinter import Tk
from idlelib.editor import fixwordbreaks
from idlelib.run import fix_scaling
root = Tk()
fix_scaling(root)
fixwordbreaks(root)
root.withdraw()
flist = FileList(root)
flist.new()
if flist.inversedict:
root.mainloop()
if __name__ == '__main__':
from unittest import main
main('idlelib.idle_test.test_filelist', verbosity=2)
# _test()

200
third_party/python/Lib/idlelib/grep.py vendored Normal file
View file

@ -0,0 +1,200 @@
"""Grep dialog for Find in Files functionality.
Inherits from SearchDialogBase for GUI and uses searchengine
to prepare search pattern.
"""
import fnmatch
import os
import sys
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):
"""Create or find singleton GrepDialog instance.
Args:
text: Text widget that contains the selected text for
default search phrase.
io: iomenu.IOBinding instance with default path to search.
flist: filelist.FileList instance for OutputWindow parent.
"""
root = text._root()
engine = searchengine.get(root)
if not hasattr(engine, "_grepdialog"):
engine._grepdialog = GrepDialog(root, engine, flist)
dialog = engine._grepdialog
searchphrase = text.get("sel.first", "sel.last")
dialog.open(text, searchphrase, io)
class GrepDialog(SearchDialogBase):
"Dialog for searching multiple files."
title = "Find in Files Dialog"
icon = "Grep"
needwrapbutton = 0
def __init__(self, root, engine, flist):
"""Create search dialog for searching for a phrase in the file system.
Uses SearchDialogBase as the basis for the GUI and a
searchengine instance to prepare the search.
Attributes:
globvar: Value of Text Entry widget for path to search.
recvar: Boolean value of Checkbutton widget
for traversing through subdirectories.
"""
SearchDialogBase.__init__(self, root, engine)
self.flist = flist
self.globvar = StringVar(root)
self.recvar = BooleanVar(root)
def open(self, text, searchphrase, io=None):
"Make dialog visible on top of others and ready to use."
SearchDialogBase.open(self, text, searchphrase)
if io:
path = io.filename or ""
else:
path = ""
dir, base = os.path.split(path)
head, tail = os.path.splitext(base)
if not tail:
tail = ".py"
self.globvar.set(os.path.join(dir, "*" + tail))
def create_entries(self):
"Create base entry widgets and add widget for search path."
SearchDialogBase.create_entries(self)
self.globent = self.make_entry("In files:", self.globvar)[0]
def create_other_buttons(self):
"Add check button to recurse down subdirectories."
btn = Checkbutton(
self.make_frame()[0], variable=self.recvar,
text="Recurse down subdirectories")
btn.pack(side="top", fill="both")
def create_command_buttons(self):
"Create base command buttons and add button for search."
SearchDialogBase.create_command_buttons(self)
self.make_button("Search Files", self.default_command, 1)
def default_command(self, event=None):
"""Grep for search pattern in file path. The default command is bound
to <Return>.
If entry values are populated, set OutputWindow as stdout
and perform search. The search dialog is closed automatically
when the search begins.
"""
prog = self.engine.getprog()
if not prog:
return
path = self.globvar.get()
if not path:
self.top.bell()
return
from idlelib.outwin import OutputWindow # leave here!
save = sys.stdout
try:
sys.stdout = OutputWindow(self.flist)
self.grep_it(prog, path)
finally:
sys.stdout = save
def grep_it(self, prog, path):
"""Search for prog within the lines of the files in path.
For the each file in the path directory, open the file and
search each line for the matching pattern. If the pattern is
found, write the file and line information to stdout (which
is an OutputWindow).
"""
dir, base = os.path.split(path)
list = self.findfiles(dir, base, self.recvar.get())
list.sort()
self.close()
pat = self.engine.getpat()
print(f"Searching {pat!r} in {path} ...")
hits = 0
try:
for fn in list:
try:
with open(fn, errors='replace') as f:
for lineno, line in enumerate(f, 1):
if line[-1:] == '\n':
line = line[:-1]
if prog.search(line):
sys.stdout.write(f"{fn}: {lineno}: {line}\n")
hits += 1
except OSError as msg:
print(msg)
print(f"Hits found: {hits}\n(Hint: right-click to open locations.)"
if hits else "No hits.")
except AttributeError:
# Tk window has been closed, OutputWindow.text = None,
# so in OW.write, OW.text.insert fails.
pass
def findfiles(self, dir, base, rec):
"""Return list of files in the dir that match the base pattern.
If rec is True, recursively iterate through subdirectories.
"""
try:
names = os.listdir(dir or os.curdir)
except OSError as msg:
print(msg)
return []
list = []
subdirs = []
for name in names:
fn = os.path.join(dir, name)
if os.path.isdir(fn):
subdirs.append(fn)
else:
if fnmatch.fnmatch(name, base):
list.append(fn)
if rec:
for subdir in subdirs:
list.extend(self.findfiles(subdir, base, rec))
return list
def _grep_dialog(parent): # htest #
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(f"+{x}+{y + 175}")
flist = PyShellFileList(top)
text = Text(top, height=5)
text.pack()
def show_grep_dialog():
text.tag_add(SEL, "1.0", END)
grep(text, flist=flist)
text.tag_remove(SEL, "1.0", END)
button = Button(top, text="Show GrepDialog", command=show_grep_dialog)
button.pack()
if __name__ == "__main__":
from unittest import main
main('idlelib.idle_test.test_grep', verbosity=2, exit=False)
from idlelib.idle_test.htest import run
run(_grep_dialog)

910
third_party/python/Lib/idlelib/help.html vendored Normal file
View file

@ -0,0 +1,910 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>IDLE &#8212; Python 3.8.0a0 documentation</title>
<link rel="stylesheet" href="../_static/pydoctheme.css" type="text/css" />
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" />
<script type="text/javascript" id="documentation_options" data-url_root="../" src="../_static/documentation_options.js"></script>
<script type="text/javascript" src="../_static/jquery.js"></script>
<script type="text/javascript" src="../_static/underscore.js"></script>
<script type="text/javascript" src="../_static/doctools.js"></script>
<script async="async" type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<script type="text/javascript" src="../_static/sidebar.js"></script>
<link rel="search" type="application/opensearchdescription+xml"
title="Search within Python 3.8.0a0 documentation"
href="../_static/opensearch.xml"/>
<link rel="author" title="About these documents" href="../about.html" />
<link rel="index" title="Index" href="../genindex.html" />
<link rel="search" title="Search" href="../search.html" />
<link rel="copyright" title="Copyright" href="../copyright.html" />
<link rel="next" title="Other Graphical User Interface Packages" href="othergui.html" />
<link rel="prev" title="tkinter.scrolledtext — Scrolled Text Widget" href="tkinter.scrolledtext.html" />
<link rel="canonical" href="https://docs.python.org/3/library/idle.html" />
<style>
@media only screen {
table.full-width-table {
width: 100%;
}
}
</style>
<link rel="shortcut icon" type="image/png" href="../_static/py.png" />
<script type="text/javascript" src="../_static/copybutton.js"></script>
</head><body>
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="../genindex.html" title="General Index"
accesskey="I">index</a></li>
<li class="right" >
<a href="../py-modindex.html" title="Python Module Index"
>modules</a> |</li>
<li class="right" >
<a href="othergui.html" title="Other Graphical User Interface Packages"
accesskey="N">next</a> |</li>
<li class="right" >
<a href="tkinter.scrolledtext.html" title="tkinter.scrolledtext — Scrolled Text Widget"
accesskey="P">previous</a> |</li>
<li><img src="../_static/py.png" alt=""
style="vertical-align: middle; margin-top: -1px"/></li>
<li><a href="https://www.python.org/">Python</a> &#187;</li>
<li>
<a href="../index.html">3.8.0a0 Documentation</a> &#187;
</li>
<li class="nav-item nav-item-1"><a href="index.html" >The Python Standard Library</a> &#187;</li>
<li class="nav-item nav-item-2"><a href="tk.html" accesskey="U">Graphical User Interfaces with Tk</a> &#187;</li>
<li class="right">
<div class="inline-search" style="display: none" role="search">
<form class="inline-search" action="../search.html" method="get">
<input placeholder="Quick search" type="text" name="q" />
<input type="submit" value="Go" />
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
</div>
<script type="text/javascript">$('.inline-search').show(0);</script>
|
</li>
</ul>
</div>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body" role="main">
<div class="section" id="idle">
<span id="id1"></span><h1>IDLE<a class="headerlink" href="#idle" title="Permalink to this headline"></a></h1>
<p><strong>Source code:</strong> <a class="reference external" href="https://github.com/python/cpython/tree/master/Lib/idlelib/">Lib/idlelib/</a></p>
<hr class="docutils" id="index-0" />
<p>IDLE is Pythons Integrated Development and Learning Environment.</p>
<p>IDLE has the following features:</p>
<ul class="simple">
<li>coded in 100% pure Python, using the <a class="reference internal" href="tkinter.html#module-tkinter" title="tkinter: Interface to Tcl/Tk for graphical user interfaces"><code class="xref py py-mod docutils literal notranslate"><span class="pre">tkinter</span></code></a> GUI toolkit</li>
<li>cross-platform: works mostly the same on Windows, Unix, and macOS</li>
<li>Python shell window (interactive interpreter) with colorizing
of code input, output, and error messages</li>
<li>multi-window text editor with multiple undo, Python colorizing,
smart indent, call tips, auto completion, and other features</li>
<li>search within any window, replace within editor windows, and search
through multiple files (grep)</li>
<li>debugger with persistent breakpoints, stepping, and viewing
of global and local namespaces</li>
<li>configuration, browsers, and other dialogs</li>
</ul>
<div class="section" id="menus">
<h2>Menus<a class="headerlink" href="#menus" title="Permalink to this headline"></a></h2>
<p>IDLE has two main window types, the Shell window and the Editor window. It is
possible to have multiple editor windows simultaneously. On Windows and
Linux, each has its own top menu. Each menu documented below indicates
which window type it is associated with.</p>
<p>Output windows, such as used for Edit =&gt; Find in Files, are a subtype of editor
window. They currently have the same top menu but a different
default title and context menu.</p>
<p>On macOS, there is one application menu. It dynamically changes according
to the window currently selected. It has an IDLE menu, and some entries
described below are moved around to conform to Apple guidlines.</p>
<div class="section" id="file-menu-shell-and-editor">
<h3>File menu (Shell and Editor)<a class="headerlink" href="#file-menu-shell-and-editor" title="Permalink to this headline"></a></h3>
<dl class="docutils">
<dt>New File</dt>
<dd>Create a new file editing window.</dd>
<dt>Open…</dt>
<dd>Open an existing file with an Open dialog.</dd>
<dt>Recent Files</dt>
<dd>Open a list of recent files. Click one to open it.</dd>
<dt>Open Module…</dt>
<dd>Open an existing module (searches sys.path).</dd>
</dl>
<dl class="docutils" id="index-1">
<dt>Class Browser</dt>
<dd>Show functions, classes, and methods in the current Editor file in a
tree structure. In the shell, open a module first.</dd>
<dt>Path Browser</dt>
<dd>Show sys.path directories, modules, functions, classes and methods in a
tree structure.</dd>
<dt>Save</dt>
<dd>Save the current window to the associated file, if there is one. Windows
that have been changed since being opened or last saved have a * before
and after the window title. If there is no associated file,
do Save As instead.</dd>
<dt>Save As…</dt>
<dd>Save the current window with a Save As dialog. The file saved becomes the
new associated file for the window.</dd>
<dt>Save Copy As…</dt>
<dd>Save the current window to different file without changing the associated
file.</dd>
<dt>Print Window</dt>
<dd>Print the current window to the default printer.</dd>
<dt>Close</dt>
<dd>Close the current window (ask to save if unsaved).</dd>
<dt>Exit</dt>
<dd>Close all windows and quit IDLE (ask to save unsaved windows).</dd>
</dl>
</div>
<div class="section" id="edit-menu-shell-and-editor">
<h3>Edit menu (Shell and Editor)<a class="headerlink" href="#edit-menu-shell-and-editor" title="Permalink to this headline"></a></h3>
<dl class="docutils">
<dt>Undo</dt>
<dd>Undo the last change to the current window. A maximum of 1000 changes may
be undone.</dd>
<dt>Redo</dt>
<dd>Redo the last undone change to the current window.</dd>
<dt>Cut</dt>
<dd>Copy selection into the system-wide clipboard; then delete the selection.</dd>
<dt>Copy</dt>
<dd>Copy selection into the system-wide clipboard.</dd>
<dt>Paste</dt>
<dd>Insert contents of the system-wide clipboard into the current window.</dd>
</dl>
<p>The clipboard functions are also available in context menus.</p>
<dl class="docutils">
<dt>Select All</dt>
<dd>Select the entire contents of the current window.</dd>
<dt>Find…</dt>
<dd>Open a search dialog with many options</dd>
<dt>Find Again</dt>
<dd>Repeat the last search, if there is one.</dd>
<dt>Find Selection</dt>
<dd>Search for the currently selected string, if there is one.</dd>
<dt>Find in Files…</dt>
<dd>Open a file search dialog. Put results in a new output window.</dd>
<dt>Replace…</dt>
<dd>Open a search-and-replace dialog.</dd>
<dt>Go to Line</dt>
<dd>Move cursor to the line number requested and make that line visible.</dd>
<dt>Show Completions</dt>
<dd>Open a scrollable list allowing selection of keywords and attributes. See
Completions in the Tips sections below.</dd>
<dt>Expand Word</dt>
<dd>Expand a prefix you have typed to match a full word in the same window;
repeat to get a different expansion.</dd>
<dt>Show call tip</dt>
<dd>After an unclosed parenthesis for a function, open a small window with
function parameter hints.</dd>
<dt>Show surrounding parens</dt>
<dd>Highlight the surrounding parenthesis.</dd>
</dl>
</div>
<div class="section" id="format-menu-editor-window-only">
<h3>Format menu (Editor window only)<a class="headerlink" href="#format-menu-editor-window-only" title="Permalink to this headline"></a></h3>
<dl class="docutils">
<dt>Indent Region</dt>
<dd>Shift selected lines right by the indent width (default 4 spaces).</dd>
<dt>Dedent Region</dt>
<dd>Shift selected lines left by the indent width (default 4 spaces).</dd>
<dt>Comment Out Region</dt>
<dd>Insert ## in front of selected lines.</dd>
<dt>Uncomment Region</dt>
<dd>Remove leading # or ## from selected lines.</dd>
<dt>Tabify Region</dt>
<dd>Turn <em>leading</em> stretches of spaces into tabs. (Note: We recommend using
4 space blocks to indent Python code.)</dd>
<dt>Untabify Region</dt>
<dd>Turn <em>all</em> tabs into the correct number of spaces.</dd>
<dt>Toggle Tabs</dt>
<dd>Open a dialog to switch between indenting with spaces and tabs.</dd>
<dt>New Indent Width</dt>
<dd>Open a dialog to change indent width. The accepted default by the Python
community is 4 spaces.</dd>
<dt>Format Paragraph</dt>
<dd>Reformat the current blank-line-delimited paragraph in comment block or
multiline string or selected line in a string. All lines in the
paragraph will be formatted to less than N columns, where N defaults to 72.</dd>
<dt>Strip trailing whitespace</dt>
<dd>Remove trailing space and other whitespace characters after the last
non-whitespace character of a line by applying str.rstrip to each line,
including lines within multiline strings.</dd>
</dl>
</div>
<div class="section" id="run-menu-editor-window-only">
<span id="index-2"></span><h3>Run menu (Editor window only)<a class="headerlink" href="#run-menu-editor-window-only" title="Permalink to this headline"></a></h3>
<dl class="docutils">
<dt>Python Shell</dt>
<dd>Open or wake up the Python Shell window.</dd>
<dt>Check Module</dt>
<dd>Check the syntax of the module currently open in the Editor window. If the
module has not been saved IDLE will either prompt the user to save or
autosave, as selected in the General tab of the Idle Settings dialog. If
there is a syntax error, the approximate location is indicated in the
Editor window.</dd>
<dt>Run Module</dt>
<dd>Do Check Module (above). If no error, restart the shell to clean the
environment, then execute the module. Output is displayed in the Shell
window. Note that output requires use of <code class="docutils literal notranslate"><span class="pre">print</span></code> or <code class="docutils literal notranslate"><span class="pre">write</span></code>.
When execution is complete, the Shell retains focus and displays a prompt.
At this point, one may interactively explore the result of execution.
This is similar to executing a file with <code class="docutils literal notranslate"><span class="pre">python</span> <span class="pre">-i</span> <span class="pre">file</span></code> at a command
line.</dd>
</dl>
</div>
<div class="section" id="shell-menu-shell-window-only">
<h3>Shell menu (Shell window only)<a class="headerlink" href="#shell-menu-shell-window-only" title="Permalink to this headline"></a></h3>
<dl class="docutils">
<dt>View Last Restart</dt>
<dd>Scroll the shell window to the last Shell restart.</dd>
<dt>Restart Shell</dt>
<dd>Restart the shell to clean the environment.</dd>
<dt>Interrupt Execution</dt>
<dd>Stop a running program.</dd>
</dl>
</div>
<div class="section" id="debug-menu-shell-window-only">
<h3>Debug menu (Shell window only)<a class="headerlink" href="#debug-menu-shell-window-only" title="Permalink to this headline"></a></h3>
<dl class="docutils">
<dt>Go to File/Line</dt>
<dd>Look on the current line. with the cursor, and the line above for a filename
and line number. If found, open the file if not already open, and show the
line. Use this to view source lines referenced in an exception traceback
and lines found by Find in Files. Also available in the context menu of
the Shell window and Output windows.</dd>
</dl>
<dl class="docutils" id="index-3">
<dt>Debugger (toggle)</dt>
<dd>When activated, code entered in the Shell or run from an Editor will run
under the debugger. In the Editor, breakpoints can be set with the context
menu. This feature is still incomplete and somewhat experimental.</dd>
<dt>Stack Viewer</dt>
<dd>Show the stack traceback of the last exception in a tree widget, with
access to locals and globals.</dd>
<dt>Auto-open Stack Viewer</dt>
<dd>Toggle automatically opening the stack viewer on an unhandled exception.</dd>
</dl>
</div>
<div class="section" id="options-menu-shell-and-editor">
<h3>Options menu (Shell and Editor)<a class="headerlink" href="#options-menu-shell-and-editor" title="Permalink to this headline"></a></h3>
<dl class="docutils">
<dt>Configure IDLE</dt>
<dd><p class="first">Open a configuration dialog and change preferences for the following:
fonts, indentation, keybindings, text color themes, startup windows and
size, additional help sources, and extensions (see below). On macOS,
open the configuration dialog by selecting Preferences in the application
menu. To use a new built-in color theme (IDLE Dark) with older IDLEs,
save it as a new custom theme.</p>
<p class="last">Non-default user settings are saved in a .idlerc directory in the users
home directory. Problems caused by bad user configuration files are solved
by editing or deleting one or more of the files in .idlerc.</p>
</dd>
<dt>Code Context (toggle)(Editor Window only)</dt>
<dd>Open a pane at the top of the edit window which shows the block context
of the code which has scrolled above the top of the window. Clicking a
line in this pane exposes that line at the top of the editor.</dd>
</dl>
</div>
<div class="section" id="window-menu-shell-and-editor">
<h3>Window menu (Shell and Editor)<a class="headerlink" href="#window-menu-shell-and-editor" title="Permalink to this headline"></a></h3>
<dl class="docutils">
<dt>Zoom Height</dt>
<dd>Toggles the window between normal size and maximum height. The initial size
defaults to 40 lines by 80 chars unless changed on the General tab of the
Configure IDLE dialog.</dd>
</dl>
<p>The rest of this menu lists the names of all open windows; select one to bring
it to the foreground (deiconifying it if necessary).</p>
</div>
<div class="section" id="help-menu-shell-and-editor">
<h3>Help menu (Shell and Editor)<a class="headerlink" href="#help-menu-shell-and-editor" title="Permalink to this headline"></a></h3>
<dl class="docutils">
<dt>About IDLE</dt>
<dd>Display version, copyright, license, credits, and more.</dd>
<dt>IDLE Help</dt>
<dd>Display this IDLE document, detailing the menu options, basic editing and
navigation, and other tips.</dd>
<dt>Python Docs</dt>
<dd>Access local Python documentation, if installed, or start a web browser
and open docs.python.org showing the latest Python documentation.</dd>
<dt>Turtle Demo</dt>
<dd>Run the turtledemo module with example Python code and turtle drawings.</dd>
</dl>
<p>Additional help sources may be added here with the Configure IDLE dialog under
the General tab. See the “Help sources” subsection below for more
on Help menu choices.</p>
</div>
<div class="section" id="context-menus">
<span id="index-4"></span><h3>Context Menus<a class="headerlink" href="#context-menus" title="Permalink to this headline"></a></h3>
<p>Open a context menu by right-clicking in a window (Control-click on macOS).
Context menus have the standard clipboard functions also on the Edit menu.</p>
<dl class="docutils">
<dt>Cut</dt>
<dd>Copy selection into the system-wide clipboard; then delete the selection.</dd>
<dt>Copy</dt>
<dd>Copy selection into the system-wide clipboard.</dd>
<dt>Paste</dt>
<dd>Insert contents of the system-wide clipboard into the current window.</dd>
</dl>
<p>Editor windows also have breakpoint functions. Lines with a breakpoint set are
specially marked. Breakpoints only have an effect when running under the
debugger. Breakpoints for a file are saved in the users .idlerc directory.</p>
<dl class="docutils">
<dt>Set Breakpoint</dt>
<dd>Set a breakpoint on the current line.</dd>
<dt>Clear Breakpoint</dt>
<dd>Clear the breakpoint on that line.</dd>
</dl>
<p>Shell and Output windows also have the following.</p>
<dl class="docutils">
<dt>Go to file/line</dt>
<dd>Same as in Debug menu.</dd>
</dl>
<p>The Shell window also has an output squeezing facility explained in the
the <em>Python Shell window</em> subsection below.</p>
<dl class="docutils">
<dt>Squeeze</dt>
<dd>If the cursor is over an output line, squeeze all the output between
the code above and the prompt below down to a Squeezed text label.</dd>
</dl>
</div>
</div>
<div class="section" id="editing-and-navigation">
<h2>Editing and navigation<a class="headerlink" href="#editing-and-navigation" title="Permalink to this headline"></a></h2>
<div class="section" id="editor-windows">
<h3>Editor windows<a class="headerlink" href="#editor-windows" title="Permalink to this headline"></a></h3>
<p>IDLE may open editor windows when it starts, depending on settings
and how you start IDLE. Thereafter, use the File menu. There can be only
one open editor window for a given file.</p>
<p>The title bar contains the name of the file, the full path, and the version
of Python and IDLE running the window. The status bar contains the line
number (Ln) and column number (Col). Line numbers start with 1;
column numbers with 0.</p>
<p>IDLE assumes that files with a known .py* extension contain Python code
and that other files do not. Run Python code with the Run menu.</p>
</div>
<div class="section" id="key-bindings">
<h3>Key bindings<a class="headerlink" href="#key-bindings" title="Permalink to this headline"></a></h3>
<p>In this section, C refers to the <kbd class="kbd docutils literal notranslate">Control</kbd> key on Windows and Unix and
the <kbd class="kbd docutils literal notranslate">Command</kbd> key on macOS.</p>
<ul>
<li><p class="first"><kbd class="kbd docutils literal notranslate">Backspace</kbd> deletes to the left; <kbd class="kbd docutils literal notranslate">Del</kbd> deletes to the right</p>
</li>
<li><p class="first"><kbd class="kbd docutils literal notranslate">C-Backspace</kbd> delete word left; <kbd class="kbd docutils literal notranslate">C-Del</kbd> delete word to the right</p>
</li>
<li><p class="first">Arrow keys and <kbd class="kbd docutils literal notranslate">Page Up</kbd>/<kbd class="kbd docutils literal notranslate">Page Down</kbd> to move around</p>
</li>
<li><p class="first"><kbd class="kbd docutils literal notranslate">C-LeftArrow</kbd> and <kbd class="kbd docutils literal notranslate">C-RightArrow</kbd> moves by words</p>
</li>
<li><p class="first"><kbd class="kbd docutils literal notranslate">Home</kbd>/<kbd class="kbd docutils literal notranslate">End</kbd> go to begin/end of line</p>
</li>
<li><p class="first"><kbd class="kbd docutils literal notranslate">C-Home</kbd>/<kbd class="kbd docutils literal notranslate">C-End</kbd> go to begin/end of file</p>
</li>
<li><p class="first">Some useful Emacs bindings are inherited from Tcl/Tk:</p>
<blockquote>
<div><ul class="simple">
<li><kbd class="kbd docutils literal notranslate">C-a</kbd> beginning of line</li>
<li><kbd class="kbd docutils literal notranslate">C-e</kbd> end of line</li>
<li><kbd class="kbd docutils literal notranslate">C-k</kbd> kill line (but doesnt put it in clipboard)</li>
<li><kbd class="kbd docutils literal notranslate">C-l</kbd> center window around the insertion point</li>
<li><kbd class="kbd docutils literal notranslate">C-b</kbd> go backward one character without deleting (usually you can
also use the cursor key for this)</li>
<li><kbd class="kbd docutils literal notranslate">C-f</kbd> go forward one character without deleting (usually you can
also use the cursor key for this)</li>
<li><kbd class="kbd docutils literal notranslate">C-p</kbd> go up one line (usually you can also use the cursor key for
this)</li>
<li><kbd class="kbd docutils literal notranslate">C-d</kbd> delete next character</li>
</ul>
</div></blockquote>
</li>
</ul>
<p>Standard keybindings (like <kbd class="kbd docutils literal notranslate">C-c</kbd> to copy and <kbd class="kbd docutils literal notranslate">C-v</kbd> to paste)
may work. Keybindings are selected in the Configure IDLE dialog.</p>
</div>
<div class="section" id="automatic-indentation">
<h3>Automatic indentation<a class="headerlink" href="#automatic-indentation" title="Permalink to this headline"></a></h3>
<p>After a block-opening statement, the next line is indented by 4 spaces (in the
Python Shell window by one tab). After certain keywords (break, return etc.)
the next line is dedented. In leading indentation, <kbd class="kbd docutils literal notranslate">Backspace</kbd> deletes up
to 4 spaces if they are there. <kbd class="kbd docutils literal notranslate">Tab</kbd> inserts spaces (in the Python
Shell window one tab), number depends on Indent width. Currently, tabs
are restricted to four spaces due to Tcl/Tk limitations.</p>
<p>See also the indent/dedent region commands in the edit menu.</p>
</div>
<div class="section" id="completions">
<h3>Completions<a class="headerlink" href="#completions" title="Permalink to this headline"></a></h3>
<p>Completions are supplied for functions, classes, and attributes of classes,
both built-in and user-defined. Completions are also provided for
filenames.</p>
<p>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.</p>
<p>If there is only one possible completion for the characters entered, a
<kbd class="kbd docutils literal notranslate">Tab</kbd> will supply that completion without opening the ACW.</p>
<p>Show Completions will force open a completions window, by default the
<kbd class="kbd docutils literal notranslate">C-space</kbd> 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 namespaces, plus any modules imported. If some
characters have been entered, the ACW will attempt to be more specific.</p>
<p>If a string of characters is typed, the ACW selection will jump to the
entry most closely matching those characters. Entering a <kbd class="kbd docutils literal notranslate">tab</kbd> will
cause the longest non-ambiguous match to be entered in the Editor window or
Shell. Two <kbd class="kbd docutils literal notranslate">tab</kbd> in a row will supply the current ACW selection, as
will return or a double click. Cursor keys, Page Up/Down, mouse selection,
and the scroll wheel all operate on the ACW.</p>
<p>“Hidden” attributes can be accessed by typing the beginning of hidden
name after a ., e.g. _. This allows access to modules with
<code class="docutils literal notranslate"><span class="pre">__all__</span></code> set, or to class-private attributes.</p>
<p>Completions and the Expand Word facility can save a lot of typing!</p>
<p>Completions are currently limited to those in the namespaces. Names in
an Editor window which are not via <code class="docutils literal notranslate"><span class="pre">__main__</span></code> and <a class="reference internal" href="sys.html#sys.modules" title="sys.modules"><code class="xref py py-data docutils literal notranslate"><span class="pre">sys.modules</span></code></a> will
not be found. Run the module once with your imports to correct this situation.
Note that IDLE itself places quite a few modules in sys.modules, so
much can be found by default, e.g. the re module.</p>
<p>If you dont like the ACW popping up unbidden, simply make the delay
longer or disable the extension.</p>
</div>
<div class="section" id="calltips">
<h3>Calltips<a class="headerlink" href="#calltips" title="Permalink to this headline"></a></h3>
<p>A calltip is shown when one types <kbd class="kbd docutils literal notranslate">(</kbd> after the name of an <em>accessible</em>
function. A name expression may include dots and subscripts. A calltip
remains until it is clicked, the cursor is moved out of the argument area,
or <kbd class="kbd docutils literal notranslate">)</kbd> is typed. When the cursor is in the argument part of a definition,
the menu or shortcut display a calltip.</p>
<p>A calltip consists of the function signature and the first line of the
docstring. For builtins without an accessible signature, the calltip
consists of all lines up the fifth line or the first blank line. These
details may change.</p>
<p>The set of <em>accessible</em> functions depends on what modules have been imported
into the user process, including those imported by Idle itself,
and what definitions have been run, all since the last restart.</p>
<p>For example, restart the Shell and enter <code class="docutils literal notranslate"><span class="pre">itertools.count(</span></code>. A calltip
appears because Idle imports itertools into the user process for its own use.
(This could change.) Enter <code class="docutils literal notranslate"><span class="pre">turtle.write(</span></code> and nothing appears. Idle does
not import turtle. The menu or shortcut do nothing either. Enter
<code class="docutils literal notranslate"><span class="pre">import</span> <span class="pre">turtle</span></code> and then <code class="docutils literal notranslate"><span class="pre">turtle.write(</span></code> will work.</p>
<p>In an editor, import statements have no effect until one runs the file. One
might want to run a file after writing the import statements at the top,
or immediately run an existing file before editing.</p>
</div>
<div class="section" id="python-shell-window">
<h3>Python Shell window<a class="headerlink" href="#python-shell-window" title="Permalink to this headline"></a></h3>
<p>With IDLEs Shell, one enters, edits, and recalls complete statements.
Most consoles and terminals only work with a single physical line at a time.</p>
<p>When one pastes code into Shell, it is not compiled and possibly executed
until one hits <kbd class="kbd docutils literal notranslate">Return</kbd>. One may edit pasted code first.
If one pastes more that one statement into Shell, the result will be a
<a class="reference internal" href="exceptions.html#SyntaxError" title="SyntaxError"><code class="xref py py-exc docutils literal notranslate"><span class="pre">SyntaxError</span></code></a> when multiple statements are compiled as if they were one.</p>
<p>The editing features described in previous subsections work when entering
code interactively. IDLEs Shell window also responds to the following keys.</p>
<ul>
<li><p class="first"><kbd class="kbd docutils literal notranslate">C-c</kbd> interrupts executing command</p>
</li>
<li><p class="first"><kbd class="kbd docutils literal notranslate">C-d</kbd> sends end-of-file; closes window if typed at a <code class="docutils literal notranslate"><span class="pre">&gt;&gt;&gt;</span></code> prompt</p>
</li>
<li><p class="first"><kbd class="kbd docutils literal notranslate">Alt-/</kbd> (Expand word) is also useful to reduce typing</p>
<p>Command history</p>
<ul class="simple">
<li><kbd class="kbd docutils literal notranslate">Alt-p</kbd> retrieves previous command matching what you have typed. On
macOS use <kbd class="kbd docutils literal notranslate">C-p</kbd>.</li>
<li><kbd class="kbd docutils literal notranslate">Alt-n</kbd> retrieves next. On macOS use <kbd class="kbd docutils literal notranslate">C-n</kbd>.</li>
<li><kbd class="kbd docutils literal notranslate">Return</kbd> while on any previous command retrieves that command</li>
</ul>
</li>
</ul>
</div>
<div class="section" id="text-colors">
<h3>Text colors<a class="headerlink" href="#text-colors" title="Permalink to this headline"></a></h3>
<p>Idle defaults to black on white text, but colors text with special meanings.
For the shell, these are shell output, shell error, user output, and
user error. For Python code, at the shell prompt or in an editor, these are
keywords, builtin class and function names, names following <code class="docutils literal notranslate"><span class="pre">class</span></code> and
<code class="docutils literal notranslate"><span class="pre">def</span></code>, strings, and comments. For any text window, these are the cursor (when
present), found text (when possible), and selected text.</p>
<p>Text coloring is done in the background, so uncolorized text is occasionally
visible. To change the color scheme, use the Configure IDLE dialog
Highlighting tab. The marking of debugger breakpoint lines in the editor and
text in popups and dialogs is not user-configurable.</p>
</div>
</div>
<div class="section" id="startup-and-code-execution">
<h2>Startup and code execution<a class="headerlink" href="#startup-and-code-execution" title="Permalink to this headline"></a></h2>
<p>Upon startup with the <code class="docutils literal notranslate"><span class="pre">-s</span></code> option, IDLE will execute the file referenced by
the environment variables <span class="target" id="index-5"></span><code class="xref std std-envvar docutils literal notranslate"><span class="pre">IDLESTARTUP</span></code> or <span class="target" id="index-6"></span><a class="reference internal" href="../using/cmdline.html#envvar-PYTHONSTARTUP"><code class="xref std std-envvar docutils literal notranslate"><span class="pre">PYTHONSTARTUP</span></code></a>.
IDLE first checks for <code class="docutils literal notranslate"><span class="pre">IDLESTARTUP</span></code>; if <code class="docutils literal notranslate"><span class="pre">IDLESTARTUP</span></code> is present the file
referenced is run. If <code class="docutils literal notranslate"><span class="pre">IDLESTARTUP</span></code> is not present, IDLE checks for
<code class="docutils literal notranslate"><span class="pre">PYTHONSTARTUP</span></code>. Files referenced by these environment variables are
convenient places to store functions that are used frequently from the IDLE
shell, or for executing import statements to import common modules.</p>
<p>In addition, <code class="docutils literal notranslate"><span class="pre">Tk</span></code> also loads a startup file if it is present. Note that the
Tk file is loaded unconditionally. This additional file is <code class="docutils literal notranslate"><span class="pre">.Idle.py</span></code> and is
looked for in the users home directory. Statements in this file will be
executed in the Tk namespace, so this file is not useful for importing
functions to be used from IDLEs Python shell.</p>
<div class="section" id="command-line-usage">
<h3>Command line usage<a class="headerlink" href="#command-line-usage" title="Permalink to this headline"></a></h3>
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>idle.py [-c command] [-d] [-e] [-h] [-i] [-r file] [-s] [-t title] [-] [arg] ...
-c command run command in the shell window
-d enable debugger and open shell window
-e open editor window
-h print help message with legal combinations and exit
-i open shell window
-r file run file in shell window
-s run $IDLESTARTUP or $PYTHONSTARTUP first, in shell window
-t title set title of shell window
- run stdin in shell (- must be last option before args)
</pre></div>
</div>
<p>If there are arguments:</p>
<ul class="simple">
<li>If <code class="docutils literal notranslate"><span class="pre">-</span></code>, <code class="docutils literal notranslate"><span class="pre">-c</span></code>, or <code class="docutils literal notranslate"><span class="pre">r</span></code> is used, all arguments are placed in
<code class="docutils literal notranslate"><span class="pre">sys.argv[1:...]</span></code> and <code class="docutils literal notranslate"><span class="pre">sys.argv[0]</span></code> is set to <code class="docutils literal notranslate"><span class="pre">''</span></code>, <code class="docutils literal notranslate"><span class="pre">'-c'</span></code>,
or <code class="docutils literal notranslate"><span class="pre">'-r'</span></code>. No editor window is opened, even if that is the default
set in the Options dialog.</li>
<li>Otherwise, arguments are files opened for editing and
<code class="docutils literal notranslate"><span class="pre">sys.argv</span></code> reflects the arguments passed to IDLE itself.</li>
</ul>
</div>
<div class="section" id="startup-failure">
<h3>Startup failure<a class="headerlink" href="#startup-failure" title="Permalink to this headline"></a></h3>
<p>IDLE uses a socket to communicate between the IDLE GUI process and the user
code execution process. A connection must be established whenever the Shell
starts or restarts. (The latter is indicated by a divider line that says
RESTART). If the user process fails to connect to the GUI process, it
displays a <code class="docutils literal notranslate"><span class="pre">Tk</span></code> error box with a cannot connect message that directs the
user here. It then exits.</p>
<p>A common cause of failure is a user-written file with the same name as a
standard library module, such as <em>random.py</em> and <em>tkinter.py</em>. When such a
file is located in the same directory as a file that is about to be run,
IDLE cannot import the stdlib file. The current fix is to rename the
user file.</p>
<p>Though less common than in the past, an antivirus or firewall program may
stop the connection. If the program cannot be taught to allow the
connection, then it must be turned off for IDLE to work. It is safe to
allow this internal connection because no data is visible on external
ports. A similar problem is a network mis-configuration that blocks
connections.</p>
<p>Python installation issues occasionally stop IDLE: multiple versions can
clash, or a single installation might need admin access. If one undo the
clash, or cannot or does not want to run as admin, it might be easiest to
completely remove Python and start over.</p>
<p>A zombie pythonw.exe process could be a problem. On Windows, use Task
Manager to detect and stop one. Sometimes a restart initiated by a program
crash or Keyboard Interrupt (control-C) may fail to connect. Dismissing
the error box or Restart Shell on the Shell menu may fix a temporary problem.</p>
<p>When IDLE first starts, it attempts to read user configuration files in
~/.idlerc/ (~ is ones home directory). If there is a problem, an error
message should be displayed. Leaving aside random disk glitches, this can
be prevented by never editing the files by hand, using the configuration
dialog, under Options, instead Options. Once it happens, the solution may
be to delete one or more of the configuration files.</p>
<p>If IDLE quits with no message, and it was not started from a console, try
starting from a console (<code class="docutils literal notranslate"><span class="pre">python</span> <span class="pre">-m</span> <span class="pre">idlelib)</span></code> and see if a message appears.</p>
</div>
<div class="section" id="running-user-code">
<h3>Running user code<a class="headerlink" href="#running-user-code" title="Permalink to this headline"></a></h3>
<p>With rare exceptions, the result of executing Python code with IDLE is
intended to be the same as executing the same code by the default method,
directly with Python in a text-mode system console or terminal window.
However, the different interface and operation occasionally affect
visible results. For instance, <code class="docutils literal notranslate"><span class="pre">sys.modules</span></code> starts with more entries,
and <code class="docutils literal notranslate"><span class="pre">threading.activeCount()</span></code> returns 2 instead of 1.</p>
<p>By default, IDLE runs user code in a separate OS process rather than in
the user interface process that runs the shell and editor. In the execution
process, it replaces <code class="docutils literal notranslate"><span class="pre">sys.stdin</span></code>, <code class="docutils literal notranslate"><span class="pre">sys.stdout</span></code>, and <code class="docutils literal notranslate"><span class="pre">sys.stderr</span></code>
with objects that get input from and send output to the Shell window.
The original values stored in <code class="docutils literal notranslate"><span class="pre">sys.__stdin__</span></code>, <code class="docutils literal notranslate"><span class="pre">sys.__stdout__</span></code>, and
<code class="docutils literal notranslate"><span class="pre">sys.__stderr__</span></code> are not touched, but may be <code class="docutils literal notranslate"><span class="pre">None</span></code>.</p>
<p>When Shell has the focus, it controls the keyboard and screen. This is
normally transparent, but functions that directly access the keyboard
and screen will not work. These include system-specific functions that
determine whether a key has been pressed and if so, which.</p>
<p>IDLEs standard stream replacements are not inherited by subprocesses
created in the execution process, whether directly by user code or by modules
such as multiprocessing. If such subprocess use <code class="docutils literal notranslate"><span class="pre">input</span></code> from sys.stdin
or <code class="docutils literal notranslate"><span class="pre">print</span></code> or <code class="docutils literal notranslate"><span class="pre">write</span></code> to sys.stdout or sys.stderr,
IDLE should be started in a command line window. The secondary subprocess
will then be attached to that window for input and output.</p>
<p>If <code class="docutils literal notranslate"><span class="pre">sys</span></code> is reset by user code, such as with <code class="docutils literal notranslate"><span class="pre">importlib.reload(sys)</span></code>,
IDLEs changes are lost and input from the keyboard and output to the screen
will not work correctly.</p>
</div>
<div class="section" id="user-output-in-shell">
<h3>User output in Shell<a class="headerlink" href="#user-output-in-shell" title="Permalink to this headline"></a></h3>
<p>When a program outputs text, the result is determined by the
corresponding output device. When IDLE executes user code, <code class="docutils literal notranslate"><span class="pre">sys.stdout</span></code>
and <code class="docutils literal notranslate"><span class="pre">sys.stderr</span></code> are connected to the display area of IDLEs Shell. Some of
its features are inherited from the underlying Tk Text widget. Others
are programmed additions. Where it matters, Shell is designed for development
rather than production runs.</p>
<p>For instance, Shell never throws away output. A program that sends unlimited
output to Shell will eventually fill memory, resulting in a memory error.
In contrast, some system text windows only keep the last n lines of output.
A Windows console, for instance, keeps a user-settable 1 to 9999 lines,
with 300 the default.</p>
<p>Text widgets display a subset of Unicode, the Basic Multilingual Plane (BMP).
Which characters get a proper glyph instead of a replacement box depends on
the operating system and installed fonts. Newline characters cause following
text to appear on a new line, but other control characters are either
replaced with a box or deleted. However, <code class="docutils literal notranslate"><span class="pre">repr()</span></code>, which is used for
interactive echo of expression values, replaces control characters,
some BMP codepoints, and all non-BMP characters with escape codes
before they are output.</p>
<p>Normal and error output are generally kept separate (on separate lines)
from code input and each other. They each get different highlight colors.</p>
<p>For SyntaxError tracebacks, the normal ^ marking where the error was
detected is replaced by coloring the text with an error highlight.
When code run from a file causes other exceptions, one may right click
on a traceback line to jump to the corresponding line in an IDLE editor.
The file will be opened if necessary.</p>
<p>Shell has a special facility for squeezing output lines down to a
Squeezed text label. This is done automatically
for output over N lines (N = 50 by default).
N can be changed in the PyShell section of the General
page of the Settings dialog. Output with fewer lines can be squeezed by
right clicking on the output. This can be useful lines long enough to slow
down scrolling.</p>
<p>Squeezed output is expanded in place by double-clicking the label.
It can also be sent to the clipboard or a separate view window by
right-clicking the label.</p>
</div>
<div class="section" id="developing-tkinter-applications">
<h3>Developing tkinter applications<a class="headerlink" href="#developing-tkinter-applications" title="Permalink to this headline"></a></h3>
<p>IDLE is intentionally different from standard Python in order to
facilitate development of tkinter programs. Enter <code class="docutils literal notranslate"><span class="pre">import</span> <span class="pre">tkinter</span> <span class="pre">as</span> <span class="pre">tk;</span>
<span class="pre">root</span> <span class="pre">=</span> <span class="pre">tk.Tk()</span></code> in standard Python and nothing appears. Enter the same
in IDLE and a tk window appears. In standard Python, one must also enter
<code class="docutils literal notranslate"><span class="pre">root.update()</span></code> to see the window. IDLE does the equivalent in the
background, about 20 times a second, which is about every 50 milleseconds.
Next enter <code class="docutils literal notranslate"><span class="pre">b</span> <span class="pre">=</span> <span class="pre">tk.Button(root,</span> <span class="pre">text='button');</span> <span class="pre">b.pack()</span></code>. Again,
nothing visibly changes in standard Python until one enters <code class="docutils literal notranslate"><span class="pre">root.update()</span></code>.</p>
<p>Most tkinter programs run <code class="docutils literal notranslate"><span class="pre">root.mainloop()</span></code>, which usually does not
return until the tk app is destroyed. If the program is run with
<code class="docutils literal notranslate"><span class="pre">python</span> <span class="pre">-i</span></code> or from an IDLE editor, a <code class="docutils literal notranslate"><span class="pre">&gt;&gt;&gt;</span></code> shell prompt does not
appear until <code class="docutils literal notranslate"><span class="pre">mainloop()</span></code> returns, at which time there is nothing left
to interact with.</p>
<p>When running a tkinter program from an IDLE editor, one can comment out
the mainloop call. One then gets a shell prompt immediately and can
interact with the live application. One just has to remember to
re-enable the mainloop call when running in standard Python.</p>
</div>
<div class="section" id="running-without-a-subprocess">
<h3>Running without a subprocess<a class="headerlink" href="#running-without-a-subprocess" title="Permalink to this headline"></a></h3>
<p>By default, IDLE executes user code in a separate subprocess via a socket,
which uses the internal loopback interface. This connection is not
externally visible and no data is sent to or received from the Internet.
If firewall software complains anyway, you can ignore it.</p>
<p>If the attempt to make the socket connection fails, Idle will notify you.
Such failures are sometimes transient, but if persistent, the problem
may be either a firewall blocking the connection or misconfiguration of
a particular system. Until the problem is fixed, one can run Idle with
the -n command line switch.</p>
<p>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.</p>
<div class="deprecated">
<p><span class="versionmodified">Deprecated since version 3.4.</span></p>
</div>
</div>
</div>
<div class="section" id="help-and-preferences">
<h2>Help and preferences<a class="headerlink" href="#help-and-preferences" title="Permalink to this headline"></a></h2>
<div class="section" id="help-sources">
<h3>Help sources<a class="headerlink" href="#help-sources" title="Permalink to this headline"></a></h3>
<p>Help menu entry “IDLE Help” displays a formatted html version of the
IDLE chapter of the Library Reference. The result, in a read-only
tkinter text window, is close to what one sees in a web browser.
Navigate through the text with a mousewheel,
the scrollbar, or up and down arrow keys held down.
Or click the TOC (Table of Contents) button and select a section
header in the opened box.</p>
<p>Help menu entry “Python Docs” opens the extensive sources of help,
including tutorials, available at docs.python.org/x.y, where x.y
is the currently running Python version. If your system
has an off-line copy of the docs (this may be an installation option),
that will be opened instead.</p>
<p>Selected URLs can be added or removed from the help menu at any time using the
General tab of the Configure IDLE dialog .</p>
</div>
<div class="section" id="setting-preferences">
<h3>Setting preferences<a class="headerlink" href="#setting-preferences" title="Permalink to this headline"></a></h3>
<p>The font preferences, highlighting, keys, and general preferences can be
changed via Configure IDLE on the Option menu. 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.</p>
</div>
<div class="section" id="idle-on-macos">
<h3>IDLE on macOS<a class="headerlink" href="#idle-on-macos" title="Permalink to this headline"></a></h3>
<p>Under System Preferences: Dock, one can set “Prefer tabs when opening
documents” to “Always”. This setting is not compatible with the tk/tkinter
GUI framework used by IDLE, and it breaks a few IDLE features.</p>
</div>
<div class="section" id="extensions">
<h3>Extensions<a class="headerlink" href="#extensions" title="Permalink to this headline"></a></h3>
<p>IDLE contains an extension facility. Preferences for extensions can be
changed with the Extensions tab of the preferences dialog. See the
beginning of config-extensions.def in the idlelib directory for further
information. The only current default extension is zzdummy, an example
also used for testing.</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
<div class="sphinxsidebarwrapper">
<h3><a href="../contents.html">Table of Contents</a></h3>
<ul>
<li><a class="reference internal" href="#">IDLE</a><ul>
<li><a class="reference internal" href="#menus">Menus</a><ul>
<li><a class="reference internal" href="#file-menu-shell-and-editor">File menu (Shell and Editor)</a></li>
<li><a class="reference internal" href="#edit-menu-shell-and-editor">Edit menu (Shell and Editor)</a></li>
<li><a class="reference internal" href="#format-menu-editor-window-only">Format menu (Editor window only)</a></li>
<li><a class="reference internal" href="#run-menu-editor-window-only">Run menu (Editor window only)</a></li>
<li><a class="reference internal" href="#shell-menu-shell-window-only">Shell menu (Shell window only)</a></li>
<li><a class="reference internal" href="#debug-menu-shell-window-only">Debug menu (Shell window only)</a></li>
<li><a class="reference internal" href="#options-menu-shell-and-editor">Options menu (Shell and Editor)</a></li>
<li><a class="reference internal" href="#window-menu-shell-and-editor">Window menu (Shell and Editor)</a></li>
<li><a class="reference internal" href="#help-menu-shell-and-editor">Help menu (Shell and Editor)</a></li>
<li><a class="reference internal" href="#context-menus">Context Menus</a></li>
</ul>
</li>
<li><a class="reference internal" href="#editing-and-navigation">Editing and navigation</a><ul>
<li><a class="reference internal" href="#editor-windows">Editor windows</a></li>
<li><a class="reference internal" href="#key-bindings">Key bindings</a></li>
<li><a class="reference internal" href="#automatic-indentation">Automatic indentation</a></li>
<li><a class="reference internal" href="#completions">Completions</a></li>
<li><a class="reference internal" href="#calltips">Calltips</a></li>
<li><a class="reference internal" href="#python-shell-window">Python Shell window</a></li>
<li><a class="reference internal" href="#text-colors">Text colors</a></li>
</ul>
</li>
<li><a class="reference internal" href="#startup-and-code-execution">Startup and code execution</a><ul>
<li><a class="reference internal" href="#command-line-usage">Command line usage</a></li>
<li><a class="reference internal" href="#startup-failure">Startup failure</a></li>
<li><a class="reference internal" href="#running-user-code">Running user code</a></li>
<li><a class="reference internal" href="#user-output-in-shell">User output in Shell</a></li>
<li><a class="reference internal" href="#developing-tkinter-applications">Developing tkinter applications</a></li>
<li><a class="reference internal" href="#running-without-a-subprocess">Running without a subprocess</a></li>
</ul>
</li>
<li><a class="reference internal" href="#help-and-preferences">Help and preferences</a><ul>
<li><a class="reference internal" href="#help-sources">Help sources</a></li>
<li><a class="reference internal" href="#setting-preferences">Setting preferences</a></li>
<li><a class="reference internal" href="#idle-on-macos">IDLE on macOS</a></li>
<li><a class="reference internal" href="#extensions">Extensions</a></li>
</ul>
</li>
</ul>
</li>
</ul>
<h4>Previous topic</h4>
<p class="topless"><a href="tkinter.scrolledtext.html"
title="previous chapter"><code class="docutils literal notranslate"><span class="pre">tkinter.scrolledtext</span></code> — Scrolled Text Widget</a></p>
<h4>Next topic</h4>
<p class="topless"><a href="othergui.html"
title="next chapter">Other Graphical User Interface Packages</a></p>
<div role="note" aria-label="source link">
<h3>This Page</h3>
<ul class="this-page-menu">
<li><a href="../bugs.html">Report a Bug</a></li>
<li>
<a href="https://github.com/python/cpython/blob/master/Doc/library/idle.rst"
rel="nofollow">Show Source
</a>
</li>
</ul>
</div>
</div>
</div>
<div class="clearer"></div>
</div>
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="../genindex.html" title="General Index"
>index</a></li>
<li class="right" >
<a href="../py-modindex.html" title="Python Module Index"
>modules</a> |</li>
<li class="right" >
<a href="othergui.html" title="Other Graphical User Interface Packages"
>next</a> |</li>
<li class="right" >
<a href="tkinter.scrolledtext.html" title="tkinter.scrolledtext — Scrolled Text Widget"
>previous</a> |</li>
<li><img src="../_static/py.png" alt=""
style="vertical-align: middle; margin-top: -1px"/></li>
<li><a href="https://www.python.org/">Python</a> &#187;</li>
<li>
<a href="../index.html">3.8.0a0 Documentation</a> &#187;
</li>
<li class="nav-item nav-item-1"><a href="index.html" >The Python Standard Library</a> &#187;</li>
<li class="nav-item nav-item-2"><a href="tk.html" >Graphical User Interfaces with Tk</a> &#187;</li>
<li class="right">
<div class="inline-search" style="display: none" role="search">
<form class="inline-search" action="../search.html" method="get">
<input placeholder="Quick search" type="text" name="q" />
<input type="submit" value="Go" />
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
</div>
<script type="text/javascript">$('.inline-search').show(0);</script>
|
</li>
</ul>
</div>
<div class="footer">
&copy; <a href="../copyright.html">Copyright</a> 2001-2018, Python Software Foundation.
<br />
The Python Software Foundation is a non-profit corporation.
<a href="https://www.python.org/psf/donations/">Please donate.</a>
<br />
<br />
Last updated on Nov 12, 2018.
<a href="https://docs.python.org/3/bugs.html">Found a bug</a>?
<br />
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.8.1.
</div>
</body>
</html>

285
third_party/python/Lib/idlelib/help.py vendored Normal file
View file

@ -0,0 +1,285 @@
""" help.py: Implement the Idle help menu.
Contents are subject to revision at any time, without notice.
Help => About IDLE: diplay About Idle dialog
<to be moved here from help_about.py>
Help => IDLE Help: Display help.html with proper formatting.
Doc/library/idle.rst (Sphinx)=> Doc/build/html/library/idle.html
(help.copy_strip)=> Lib/idlelib/help.html
HelpParser - Parse help.html and render to tk Text.
HelpText - Display formatted help.html.
HelpFrame - Contain text, scrollbar, and table-of-contents.
(This will be needed for display in a future tabbed window.)
HelpWindow - Display HelpFrame in a standalone window.
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, isfile, join
from platform import python_version
from tkinter import Toplevel, Frame, Text, Menu
from tkinter.ttk import Menubutton, Scrollbar
from tkinter import font as tkfont
from idlelib.config import idleConf
## About IDLE ##
## IDLE Help ##
class HelpParser(HTMLParser):
"""Render help.html into a text widget.
The overridden handle_xyz methods handle a subset of html tags.
The supplied text should have the needed tag configurations.
The behavior for unsupported tags, such as table, is undefined.
If the tags generated by Sphinx change, this class, especially
the handle_starttag and handle_endtags methods, might have to also.
"""
def __init__(self, text):
HTMLParser.__init__(self, convert_charrefs=True)
self.text = text # text widget we're rendering into
self.tags = '' # current block level text tags to apply
self.chartags = '' # current character level text tags
self.show = False # used so we exclude page navigation
self.hdrlink = False # used so we don't show header links
self.level = 0 # indentation level
self.pre = False # displaying preformatted text
self.hprefix = '' # prefix such as '25.5' to strip from headings
self.nested_dl = False # if we're in a nested <dl>
self.simplelist = False # simple list (no double spacing)
self.toc = [] # pair headers with text indexes for toc
self.header = '' # text within header tags for toc
def indent(self, amt=1):
self.level += amt
self.tags = '' if self.level == 0 else 'l'+str(self.level)
def handle_starttag(self, tag, attrs):
"Handle starttags in help.html."
class_ = ''
for a, v in attrs:
if a == 'class':
class_ = v
s = ''
if tag == 'div' and class_ == 'section':
self.show = True # start of main content
elif tag == 'div' and class_ == 'sphinxsidebar':
self.show = False # end of main content
elif tag == 'p' and class_ != 'first':
s = '\n\n'
elif tag == 'span' and class_ == 'pre':
self.chartags = 'pre'
elif tag == 'span' and class_ == 'versionmodified':
self.chartags = 'em'
elif tag == 'em':
self.chartags = 'em'
elif tag in ['ul', 'ol']:
if class_.find('simple') != -1:
s = '\n'
self.simplelist = True
else:
self.simplelist = False
self.indent()
elif tag == 'dl':
if self.level > 0:
self.nested_dl = True
elif tag == 'li':
s = '\n* ' if self.simplelist else '\n\n* '
elif tag == 'dt':
s = '\n\n' if not self.nested_dl else '\n' # avoid extra line
self.nested_dl = False
elif tag == 'dd':
self.indent()
s = '\n'
elif tag == 'pre':
self.pre = True
if self.show:
self.text.insert('end', '\n\n')
self.tags = 'preblock'
elif tag == 'a' and class_ == 'headerlink':
self.hdrlink = True
elif tag == 'h1':
self.tags = tag
elif tag in ['h2', 'h3']:
if self.show:
self.header = ''
self.text.insert('end', '\n\n')
self.tags = tag
if self.show:
self.text.insert('end', s, (self.tags, self.chartags))
def handle_endtag(self, tag):
"Handle endtags in help.html."
if tag in ['h1', 'h2', 'h3']:
self.indent(0) # clear tag, reset indent
if self.show:
indent = (' ' if tag == 'h3' else
' ' if tag == 'h2' else
'')
self.toc.append((indent+self.header, self.text.index('insert')))
elif tag in ['span', 'em']:
self.chartags = ''
elif tag == 'a':
self.hdrlink = False
elif tag == 'pre':
self.pre = False
self.tags = ''
elif tag in ['ul', 'dd', 'ol']:
self.indent(amt=-1)
def handle_data(self, data):
"Handle date segments in help.html."
if self.show and not self.hdrlink:
d = data if self.pre else data.replace('\n', ' ')
if self.tags == 'h1':
try:
self.hprefix = d[0:d.index(' ')]
except ValueError:
self.hprefix = ''
if self.tags in ['h1', 'h2', 'h3']:
if (self.hprefix != '' and
d[0:len(self.hprefix)] == self.hprefix):
d = d[len(self.hprefix):]
self.header += d.strip()
self.text.insert('end', d, (self.tags, self.chartags))
class HelpText(Text):
"Display help.html."
def __init__(self, parent, filename):
"Configure tags and feed file to parser."
uwide = idleConf.GetOption('main', 'EditorWindow', 'width', type='int')
uhigh = idleConf.GetOption('main', 'EditorWindow', 'height', type='int')
uhigh = 3 * uhigh // 4 # lines average 4/3 of editor line height
Text.__init__(self, parent, wrap='word', highlightthickness=0,
padx=5, borderwidth=0, width=uwide, height=uhigh)
normalfont = self.findfont(['TkDefaultFont', 'arial', 'helvetica'])
fixedfont = self.findfont(['TkFixedFont', 'monaco', 'courier'])
self['font'] = (normalfont, 12)
self.tag_configure('em', font=(normalfont, 12, 'italic'))
self.tag_configure('h1', font=(normalfont, 20, 'bold'))
self.tag_configure('h2', font=(normalfont, 18, 'bold'))
self.tag_configure('h3', font=(normalfont, 15, 'bold'))
self.tag_configure('pre', font=(fixedfont, 12), background='#f6f6ff')
self.tag_configure('preblock', font=(fixedfont, 10), lmargin1=25,
borderwidth=1, relief='solid', background='#eeffcc')
self.tag_configure('l1', lmargin1=25, lmargin2=25)
self.tag_configure('l2', lmargin1=50, lmargin2=50)
self.tag_configure('l3', lmargin1=75, lmargin2=75)
self.tag_configure('l4', lmargin1=100, lmargin2=100)
self.parser = HelpParser(self)
with open(filename, encoding='utf-8') as f:
contents = f.read()
self.parser.feed(contents)
self['state'] = 'disabled'
def findfont(self, names):
"Return name of first font family derived from names."
for name in names:
if name.lower() in (x.lower() for x in tkfont.names(root=self)):
font = tkfont.Font(name=name, exists=True, root=self)
return font.actual()['family']
elif name.lower() in (x.lower()
for x in tkfont.families(root=self)):
return name
class HelpFrame(Frame):
"Display html text, scrollbar, and toc."
def __init__(self, parent, filename):
Frame.__init__(self, parent)
# keep references to widgets for test access.
self.text = text = HelpText(self, filename)
self['background'] = text['background']
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
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."
toc = Menubutton(self, text='TOC')
drop = Menu(toc, tearoff=False)
for lbl, dex in text.parser.toc:
drop.add_command(label=lbl, command=lambda dex=dex:text.yview(dex))
toc['menu'] = drop
return toc
class HelpWindow(Toplevel):
"Display frame with rendered html."
def __init__(self, parent, filename, title):
Toplevel.__init__(self, parent)
self.wm_title(title)
self.protocol("WM_DELETE_WINDOW", self.destroy)
HelpFrame(self, filename).grid(column=0, row=0, sticky='nsew')
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=1)
def copy_strip():
"""Copy idle.html to idlelib/help.html, stripping trailing whitespace.
Files with trailing whitespace cannot be pushed to the git cpython
repository. For 3.x (on Windows), help.html is generated, after
editing idle.rst on the master branch, with
sphinx-build -bhtml . build/html
python_d.exe -c "from idlelib.help import copy_strip; copy_strip()"
Check build/html/library/idle.html, the help.html diff, and the text
displayed by Help => IDLE Help. Add a blurb and create a PR.
It can be worthwhile to occasionally generate help.html without
touching idle.rst. Changes to the master version and to the doc
build system may result in changes that should not changed
the displayed text, but might break HelpParser.
As long as master and maintenance versions of idle.rst remain the
same, help.html can be backported. The internal Python version
number is not displayed. If maintenance idle.rst diverges from
the master version, then instead of backporting help.html from
master, repeat the proceedure above to generate a maintenance
version.
"""
src = join(abspath(dirname(dirname(dirname(__file__)))),
'Doc', 'build', 'html', 'library', 'idle.html')
dst = join(abspath(dirname(__file__)), 'help.html')
with open(src, 'rb') as inn,\
open(dst, 'wb') as out:
for line in inn:
out.write(line.rstrip() + b'\n')
print(f'{src} copied to {dst}')
def show_idlehelp(parent):
"Create HelpWindow; called from Idle Help event handler."
filename = join(abspath(dirname(__file__)), 'help.html')
if not isfile(filename):
# try copy_strip, present message
return
HelpWindow(parent, filename, 'IDLE Help (%s)' % python_version())
if __name__ == '__main__':
from unittest import main
main('idlelib.idle_test.test_help', verbosity=2, exit=False)
from idlelib.idle_test.htest import run
run(show_idlehelp)

View file

@ -0,0 +1,207 @@
"""About Dialog for IDLE
"""
import os
import sys
from platform import python_version, architecture
from tkinter import Toplevel, Frame, Label, Button, PhotoImage
from tkinter import SUNKEN, TOP, BOTTOM, LEFT, X, BOTH, W, EW, NSEW, E
from idlelib import textview
def build_bits():
"Return bits for platform."
if sys.platform == 'darwin':
return '64' if sys.maxsize > 2**32 else '32'
else:
return architecture()[0][:2]
class AboutDialog(Toplevel):
"""Modal about dialog for idle
"""
def __init__(self, parent, title=None, *, _htest=False, _utest=False):
"""Create popup, do not return until tk widget destroyed.
parent - parent of this dialog
title - string which is title of popup dialog
_htest - bool, change box location when running htest
_utest - bool, don't wait_window when running unittest
"""
Toplevel.__init__(self, parent)
self.configure(borderwidth=5)
# place dialog below parent if running htest
self.geometry("+%d+%d" % (
parent.winfo_rootx()+30,
parent.winfo_rooty()+(30 if not _htest else 100)))
self.bg = "#bbbbbb"
self.fg = "#000000"
self.create_widgets()
self.resizable(height=False, width=False)
self.title(title or
f'About IDLE {python_version()} ({build_bits()} bit)')
self.transient(parent)
self.grab_set()
self.protocol("WM_DELETE_WINDOW", self.ok)
self.parent = parent
self.button_ok.focus_set()
self.bind('<Return>', self.ok) # dismiss dialog
self.bind('<Escape>', self.ok) # dismiss dialog
self._current_textview = None
self._utest = _utest
if not _utest:
self.deiconify()
self.wait_window()
def create_widgets(self):
frame = Frame(self, borderwidth=2, relief=SUNKEN)
frame_buttons = Frame(self)
frame_buttons.pack(side=BOTTOM, fill=X)
frame.pack(side=TOP, expand=True, fill=BOTH)
self.button_ok = Button(frame_buttons, text='Close',
command=self.ok)
self.button_ok.pack(padx=5, pady=5)
frame_background = Frame(frame, bg=self.bg)
frame_background.pack(expand=True, fill=BOTH)
header = Label(frame_background, text='IDLE', fg=self.fg,
bg=self.bg, font=('courier', 24, 'bold'))
header.grid(row=0, column=0, sticky=E, padx=10, pady=10)
tk_patchlevel = self.tk.call('info', 'patchlevel')
ext = '.png' if tk_patchlevel >= '8.6' else '.gif'
icon = os.path.join(os.path.abspath(os.path.dirname(__file__)),
'Icons', f'idle_48{ext}')
self.icon_image = PhotoImage(master=self._root(), file=icon)
logo = Label(frame_background, image=self.icon_image, bg=self.bg)
logo.grid(row=0, column=0, sticky=W, rowspan=2, padx=10, pady=10)
byline_text = "Python's Integrated Development\nand Learning Environment" + 5*'\n'
byline = Label(frame_background, text=byline_text, justify=LEFT,
fg=self.fg, bg=self.bg)
byline.grid(row=2, column=0, sticky=W, columnspan=3, padx=10, pady=5)
email = Label(frame_background, text='email: idle-dev@python.org',
justify=LEFT, fg=self.fg, bg=self.bg)
email.grid(row=6, column=0, columnspan=2, sticky=W, padx=10, pady=0)
docs = Label(frame_background, text='https://docs.python.org/' +
python_version()[:3] + '/library/idle.html',
justify=LEFT, fg=self.fg, bg=self.bg)
docs.grid(row=7, column=0, columnspan=2, sticky=W, padx=10, pady=0)
Frame(frame_background, borderwidth=1, relief=SUNKEN,
height=2, bg=self.bg).grid(row=8, column=0, sticky=EW,
columnspan=3, padx=5, pady=5)
pyver = Label(frame_background,
text='Python version: ' + python_version(),
fg=self.fg, bg=self.bg)
pyver.grid(row=9, column=0, sticky=W, padx=10, pady=0)
tkver = Label(frame_background, text='Tk version: ' + tk_patchlevel,
fg=self.fg, bg=self.bg)
tkver.grid(row=9, column=1, sticky=W, padx=2, pady=0)
py_buttons = Frame(frame_background, bg=self.bg)
py_buttons.grid(row=10, column=0, columnspan=2, sticky=NSEW)
self.py_license = Button(py_buttons, text='License', width=8,
highlightbackground=self.bg,
command=self.show_py_license)
self.py_license.pack(side=LEFT, padx=10, pady=10)
self.py_copyright = Button(py_buttons, text='Copyright', width=8,
highlightbackground=self.bg,
command=self.show_py_copyright)
self.py_copyright.pack(side=LEFT, padx=10, pady=10)
self.py_credits = Button(py_buttons, text='Credits', width=8,
highlightbackground=self.bg,
command=self.show_py_credits)
self.py_credits.pack(side=LEFT, padx=10, pady=10)
Frame(frame_background, borderwidth=1, relief=SUNKEN,
height=2, bg=self.bg).grid(row=11, column=0, sticky=EW,
columnspan=3, padx=5, pady=5)
idlever = Label(frame_background,
text='IDLE version: ' + python_version(),
fg=self.fg, bg=self.bg)
idlever.grid(row=12, column=0, sticky=W, padx=10, pady=0)
idle_buttons = Frame(frame_background, bg=self.bg)
idle_buttons.grid(row=13, column=0, columnspan=3, sticky=NSEW)
self.readme = Button(idle_buttons, text='README', width=8,
highlightbackground=self.bg,
command=self.show_readme)
self.readme.pack(side=LEFT, padx=10, pady=10)
self.idle_news = Button(idle_buttons, text='NEWS', width=8,
highlightbackground=self.bg,
command=self.show_idle_news)
self.idle_news.pack(side=LEFT, padx=10, pady=10)
self.idle_credits = Button(idle_buttons, text='Credits', width=8,
highlightbackground=self.bg,
command=self.show_idle_credits)
self.idle_credits.pack(side=LEFT, padx=10, pady=10)
# License, copyright, and credits are of type _sitebuiltins._Printer
def show_py_license(self):
"Handle License button event."
self.display_printer_text('About - License', license)
def show_py_copyright(self):
"Handle Copyright button event."
self.display_printer_text('About - Copyright', copyright)
def show_py_credits(self):
"Handle Python Credits button event."
self.display_printer_text('About - Python Credits', credits)
# Encode CREDITS.txt to utf-8 for proper version of Loewis.
# Specify others as ascii until need utf-8, so catch errors.
def show_idle_credits(self):
"Handle Idle Credits button event."
self.display_file_text('About - Credits', 'CREDITS.txt', 'utf-8')
def show_readme(self):
"Handle Readme button event."
self.display_file_text('About - Readme', 'README.txt', 'ascii')
def show_idle_news(self):
"Handle News button event."
self.display_file_text('About - NEWS', 'NEWS.txt', 'utf-8')
def display_printer_text(self, title, printer):
"""Create textview for built-in constants.
Built-in constants have type _sitebuiltins._Printer. The
text is extracted from the built-in and then sent to a text
viewer with self as the parent and title as the title of
the popup.
"""
printer._Printer__setup()
text = '\n'.join(printer._Printer__lines)
self._current_textview = textview.view_text(
self, title, text, _utest=self._utest)
def display_file_text(self, title, filename, encoding=None):
"""Create textview for filename.
The filename needs to be in the current directory. The path
is sent to a text viewer with self as the parent, title as
the title of the popup, and the file encoding.
"""
fn = os.path.join(os.path.abspath(os.path.dirname(__file__)), filename)
self._current_textview = textview.view_file(
self, title, fn, encoding, _utest=self._utest)
def ok(self, event=None):
"Dismiss help_about dialog."
self.grab_release()
self.destroy()
if __name__ == '__main__':
from unittest import main
main('idlelib.idle_test.test_help_about', verbosity=2, exit=False)
from idlelib.idle_test.htest import run
run(AboutDialog)

View file

@ -0,0 +1,106 @@
"Implement Idle Shell history mechanism with History class"
from idlelib.config import idleConf
class History:
''' Implement Idle Shell history mechanism.
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).
'''
def __init__(self, text):
'''Initialize data attributes and bind event methods.
.text - Idle wrapper of tk Text widget, with .bell().
.history - source statements, possibly with multiple lines.
.prefix - source already entered at prompt; filters history list.
.pointer - index into history.
.cyclic - wrap around history list (or not).
'''
self.text = text
self.history = []
self.prefix = None
self.pointer = None
self.cyclic = idleConf.GetOption("main", "History", "cyclic", 1, "bool")
text.bind("<<history-previous>>", self.history_prev)
text.bind("<<history-next>>", self.history_next)
def history_next(self, event):
"Fetch later statement; start with ealiest if cyclic."
self.fetch(reverse=False)
return "break"
def history_prev(self, event):
"Fetch earlier statement; start with most recent."
self.fetch(reverse=True)
return "break"
def fetch(self, reverse):
'''Fetch statememt and replace current line in text widget.
Set prefix and pointer as needed for successive fetches.
Reset them to None, None when returning to the start line.
Sound bell when return to start line or cannot leave a line
because cyclic is False.
'''
nhist = len(self.history)
pointer = self.pointer
prefix = self.prefix
if pointer is not None and prefix is not None:
if self.text.compare("insert", "!=", "end-1c") or \
self.text.get("iomark", "end-1c") != self.history[pointer]:
pointer = prefix = None
self.text.mark_set("insert", "end-1c") # != after cursor move
if pointer is None or prefix is None:
prefix = self.text.get("iomark", "end-1c")
if reverse:
pointer = nhist # will be decremented
else:
if self.cyclic:
pointer = -1 # will be incremented
else: # abort history_next
self.text.bell()
return
nprefix = len(prefix)
while 1:
pointer += -1 if reverse else 1
if pointer < 0 or pointer >= nhist:
self.text.bell()
if not self.cyclic and pointer < 0: # abort history_prev
return
else:
if self.text.get("iomark", "end-1c") != prefix:
self.text.delete("iomark", "end-1c")
self.text.insert("iomark", prefix)
pointer = prefix = None
break
item = self.history[pointer]
if item[:nprefix] == prefix and len(item) > nprefix:
self.text.delete("iomark", "end-1c")
self.text.insert("iomark", item)
break
self.text.see("insert")
self.text.tag_remove("sel", "1.0", "end")
self.pointer = pointer
self.prefix = prefix
def store(self, source):
"Store Shell input statement into history list."
source = source.strip()
if len(source) > 2:
# avoid duplicates
try:
self.history.remove(source)
except ValueError:
pass
self.history.append(source)
self.pointer = None
self.prefix = None
if __name__ == "__main__":
from unittest import main
main('idlelib.idle_test.test_history', verbosity=2, exit=False)

View file

@ -0,0 +1,312 @@
"""Provide advanced parsing abilities for ParenMatch and other extensions.
HyperParser uses PyParser. PyParser mostly gives information on the
proper indentation of code. HyperParser gives additional information on
the structure of code.
"""
from keyword import iskeyword
import string
from idlelib import pyparse
# all ASCII chars that may be in an identifier
_ASCII_ID_CHARS = frozenset(string.ascii_letters + string.digits + "_")
# all ASCII chars that may be the first char of an identifier
_ASCII_ID_FIRST_CHARS = frozenset(string.ascii_letters + "_")
# lookup table for whether 7-bit ASCII chars are valid in a Python identifier
_IS_ASCII_ID_CHAR = [(chr(x) in _ASCII_ID_CHARS) for x in range(128)]
# lookup table for whether 7-bit ASCII chars are valid as the first
# char in a Python identifier
_IS_ASCII_ID_FIRST_CHAR = \
[(chr(x) in _ASCII_ID_FIRST_CHARS) for x in range(128)]
class HyperParser:
def __init__(self, editwin, index):
"To initialize, analyze the surroundings of the given index."
self.editwin = editwin
self.text = text = editwin.text
parser = pyparse.Parser(editwin.indentwidth, editwin.tabwidth)
def index2line(index):
return int(float(index))
lno = index2line(text.index(index))
if not editwin.context_use_ps1:
for context in editwin.num_context_lines:
startat = max(lno - context, 1)
startatindex = repr(startat) + ".0"
stopatindex = "%d.end" % lno
# We add the newline because PyParse requires a newline
# at end. We add a space so that index won't be at end
# of line, so that its status will be the same as the
# char before it, if should.
parser.set_code(text.get(startatindex, stopatindex)+' \n')
bod = parser.find_good_parse_start(
editwin._build_char_in_string_func(startatindex))
if bod is not None or startat == 1:
break
parser.set_lo(bod or 0)
else:
r = text.tag_prevrange("console", index)
if r:
startatindex = r[1]
else:
startatindex = "1.0"
stopatindex = "%d.end" % lno
# We add the newline because PyParse requires it. We add a
# space so that index won't be at end of line, so that its
# status will be the same as the char before it, if should.
parser.set_code(text.get(startatindex, stopatindex)+' \n')
parser.set_lo(0)
# We want what the parser has, minus the last newline and space.
self.rawtext = parser.code[:-2]
# Parser.code apparently preserves the statement we are in, so
# that stopatindex can be used to synchronize the string with
# the text box indices.
self.stopatindex = stopatindex
self.bracketing = parser.get_last_stmt_bracketing()
# find which pairs of bracketing are openers. These always
# correspond to a character of rawtext.
self.isopener = [i>0 and self.bracketing[i][1] >
self.bracketing[i-1][1]
for i in range(len(self.bracketing))]
self.set_index(index)
def set_index(self, index):
"""Set the index to which the functions relate.
The index must be in the same statement.
"""
indexinrawtext = (len(self.rawtext) -
len(self.text.get(index, self.stopatindex)))
if indexinrawtext < 0:
raise ValueError("Index %s precedes the analyzed statement"
% index)
self.indexinrawtext = indexinrawtext
# find the rightmost bracket to which index belongs
self.indexbracket = 0
while (self.indexbracket < len(self.bracketing)-1 and
self.bracketing[self.indexbracket+1][0] < self.indexinrawtext):
self.indexbracket += 1
if (self.indexbracket < len(self.bracketing)-1 and
self.bracketing[self.indexbracket+1][0] == self.indexinrawtext and
not self.isopener[self.indexbracket+1]):
self.indexbracket += 1
def is_in_string(self):
"""Is the index given to the HyperParser in a string?"""
# The bracket to which we belong should be an opener.
# If it's an opener, it has to have a character.
return (self.isopener[self.indexbracket] and
self.rawtext[self.bracketing[self.indexbracket][0]]
in ('"', "'"))
def is_in_code(self):
"""Is the index given to the HyperParser in normal code?"""
return (not self.isopener[self.indexbracket] or
self.rawtext[self.bracketing[self.indexbracket][0]]
not in ('#', '"', "'"))
def get_surrounding_brackets(self, openers='([{', mustclose=False):
"""Return bracket indexes or None.
If the index given to the HyperParser is surrounded by a
bracket defined in openers (or at least has one before it),
return the indices of the opening bracket and the closing
bracket (or the end of line, whichever comes first).
If it is not surrounded by brackets, or the end of line comes
before the closing bracket and mustclose is True, returns None.
"""
bracketinglevel = self.bracketing[self.indexbracket][1]
before = self.indexbracket
while (not self.isopener[before] or
self.rawtext[self.bracketing[before][0]] not in openers or
self.bracketing[before][1] > bracketinglevel):
before -= 1
if before < 0:
return None
bracketinglevel = min(bracketinglevel, self.bracketing[before][1])
after = self.indexbracket + 1
while (after < len(self.bracketing) and
self.bracketing[after][1] >= bracketinglevel):
after += 1
beforeindex = self.text.index("%s-%dc" %
(self.stopatindex, len(self.rawtext)-self.bracketing[before][0]))
if (after >= len(self.bracketing) or
self.bracketing[after][0] > len(self.rawtext)):
if mustclose:
return None
afterindex = self.stopatindex
else:
# We are after a real char, so it is a ')' and we give the
# index before it.
afterindex = self.text.index(
"%s-%dc" % (self.stopatindex,
len(self.rawtext)-(self.bracketing[after][0]-1)))
return beforeindex, afterindex
# the set of built-in identifiers which are also keywords,
# i.e. keyword.iskeyword() returns True for them
_ID_KEYWORDS = frozenset({"True", "False", "None"})
@classmethod
def _eat_identifier(cls, str, limit, pos):
"""Given a string and pos, return the number of chars in the
identifier which ends at pos, or 0 if there is no such one.
This ignores non-identifier eywords are not identifiers.
"""
is_ascii_id_char = _IS_ASCII_ID_CHAR
# Start at the end (pos) and work backwards.
i = pos
# Go backwards as long as the characters are valid ASCII
# identifier characters. This is an optimization, since it
# is faster in the common case where most of the characters
# are ASCII.
while i > limit and (
ord(str[i - 1]) < 128 and
is_ascii_id_char[ord(str[i - 1])]
):
i -= 1
# If the above loop ended due to reaching a non-ASCII
# character, continue going backwards using the most generic
# test for whether a string contains only valid identifier
# characters.
if i > limit and ord(str[i - 1]) >= 128:
while i - 4 >= limit and ('a' + str[i - 4:pos]).isidentifier():
i -= 4
if i - 2 >= limit and ('a' + str[i - 2:pos]).isidentifier():
i -= 2
if i - 1 >= limit and ('a' + str[i - 1:pos]).isidentifier():
i -= 1
# The identifier candidate starts here. If it isn't a valid
# identifier, don't eat anything. At this point that is only
# possible if the first character isn't a valid first
# character for an identifier.
if not str[i:pos].isidentifier():
return 0
elif i < pos:
# All characters in str[i:pos] are valid ASCII identifier
# characters, so it is enough to check that the first is
# valid as the first character of an identifier.
if not _IS_ASCII_ID_FIRST_CHAR[ord(str[i])]:
return 0
# All keywords are valid identifiers, but should not be
# considered identifiers here, except for True, False and None.
if i < pos and (
iskeyword(str[i:pos]) and
str[i:pos] not in cls._ID_KEYWORDS
):
return 0
return pos - i
# This string includes all chars that may be in a white space
_whitespace_chars = " \t\n\\"
def get_expression(self):
"""Return a string with the Python expression which ends at the
given index, which is empty if there is no real one.
"""
if not self.is_in_code():
raise ValueError("get_expression should only be called "
"if index is inside a code.")
rawtext = self.rawtext
bracketing = self.bracketing
brck_index = self.indexbracket
brck_limit = bracketing[brck_index][0]
pos = self.indexinrawtext
last_identifier_pos = pos
postdot_phase = True
while 1:
# Eat whitespaces, comments, and if postdot_phase is False - a dot
while 1:
if pos>brck_limit and rawtext[pos-1] in self._whitespace_chars:
# Eat a whitespace
pos -= 1
elif (not postdot_phase and
pos > brck_limit and rawtext[pos-1] == '.'):
# Eat a dot
pos -= 1
postdot_phase = True
# The next line will fail if we are *inside* a comment,
# but we shouldn't be.
elif (pos == brck_limit and brck_index > 0 and
rawtext[bracketing[brck_index-1][0]] == '#'):
# Eat a comment
brck_index -= 2
brck_limit = bracketing[brck_index][0]
pos = bracketing[brck_index+1][0]
else:
# If we didn't eat anything, quit.
break
if not postdot_phase:
# We didn't find a dot, so the expression end at the
# last identifier pos.
break
ret = self._eat_identifier(rawtext, brck_limit, pos)
if ret:
# There is an identifier to eat
pos = pos - ret
last_identifier_pos = pos
# Now, to continue the search, we must find a dot.
postdot_phase = False
# (the loop continues now)
elif pos == brck_limit:
# We are at a bracketing limit. If it is a closing
# bracket, eat the bracket, otherwise, stop the search.
level = bracketing[brck_index][1]
while brck_index > 0 and bracketing[brck_index-1][1] > level:
brck_index -= 1
if bracketing[brck_index][0] == brck_limit:
# We were not at the end of a closing bracket
break
pos = bracketing[brck_index][0]
brck_index -= 1
brck_limit = bracketing[brck_index][0]
last_identifier_pos = pos
if rawtext[pos] in "([":
# [] and () may be used after an identifier, so we
# continue. postdot_phase is True, so we don't allow a dot.
pass
else:
# We can't continue after other types of brackets
if rawtext[pos] in "'\"":
# Scan a string prefix
while pos > 0 and rawtext[pos - 1] in "rRbBuU":
pos -= 1
last_identifier_pos = pos
break
else:
# We've found an operator or something.
break
return rawtext[last_identifier_pos:self.indexinrawtext]
if __name__ == '__main__':
from unittest import main
main('idlelib.idle_test.test_hyperparser', verbosity=2)

4
third_party/python/Lib/idlelib/idle.bat vendored Executable file
View file

@ -0,0 +1,4 @@
@echo off
rem Start IDLE using the appropriate Python interpreter
set CURRDIR=%~dp0
start "IDLE" "%CURRDIR%..\..\pythonw.exe" "%CURRDIR%idle.pyw" %1 %2 %3 %4 %5 %6 %7 %8 %9

14
third_party/python/Lib/idlelib/idle.py vendored Normal file
View file

@ -0,0 +1,14 @@
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,
# removal should require deprecation.
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
main()

17
third_party/python/Lib/idlelib/idle.pyw vendored Normal file
View file

@ -0,0 +1,17 @@
try:
import idlelib.pyshell
except ImportError:
# 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__))
if idledir != os.getcwd():
# We're not in the IDLE directory, help the subprocess find run.py
pypath = os.environ.get('PYTHONPATH', '')
if pypath:
os.environ['PYTHONPATH'] = pypath + ':' + idledir
else:
os.environ['PYTHONPATH'] = idledir
pyshell.main()
else:
idlelib.pyshell.main()

View file

@ -0,0 +1,238 @@
README FOR IDLE TESTS IN IDLELIB.IDLE_TEST
0. Quick Start
Automated unit tests were added in 3.3 for Python 3.x.
To run the tests from a command line:
python -m test.test_idle
Human-mediated tests were added later in 3.4.
python -m idlelib.idle_test.htest
1. Test Files
The idle directory, idlelib, has over 60 xyz.py files. The idle_test
subdirectory contains test_xyz.py for each implementation file xyz.py.
To add a test for abc.py, open idle_test/template.py and immediately
Save As test_abc.py. Insert 'abc' on the first line, and replace
'zzdummy' with 'abc.
Remove the imports of requires and tkinter if not needed. Otherwise,
add to the tkinter imports as needed.
Add a prefix to 'Test' for the initial test class. The template class
contains code needed or possibly needed for gui tests. See the next
section if doing gui tests. If not, and not needed for further classes,
this code can be removed.
Add the following at the end of abc.py. If an htest was added first,
insert the import and main lines before the htest lines.
if __name__ == "__main__":
from unittest import main
main('idlelib.idle_test.test_abc', verbosity=2, exit=False)
The ', exit=False' is only needed if an htest follows.
2. GUI Tests
When run as part of the Python test suite, Idle GUI tests need to run
test.support.requires('gui'). A test is a GUI test if it creates a
tkinter.Tk root or master object either directly or indirectly by
instantiating a tkinter or idle class. GUI tests cannot run in test
processes that either have no graphical environment available or are not
allowed to use it.
To guard a module consisting entirely of GUI tests, start with
from test.support import requires
requires('gui')
To guard a test class, put "requires('gui')" in its setUpClass function.
The template.py file does this.
To avoid interfering with other GUI tests, all GUI objects must be
destroyed and deleted by the end of the test. The Tk root created in a
setUpX function should be destroyed in the corresponding tearDownX and
the module or class attribute deleted. Others widgets should descend
from the single root and the attributes deleted BEFORE root is
destroyed. See https://bugs.python.org/issue20567.
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = tk.Tk()
cls.text = tk.Text(root)
@classmethod
def tearDownClass(cls):
del cls.text
cls.root.update_idletasks()
cls.root.destroy()
del cls.root
The update_idletasks call is sometimes needed to prevent the following
warning either when running a test alone or as part of the test suite
(#27196). It should not hurt if not needed.
can't invoke "event" command: application has been destroyed
...
"ttk::ThemeChanged"
If a test creates instance 'e' of EditorWindow, call 'e._close()' before
or as the first part of teardown. The effect of omitting this depends
on the later shutdown. Then enable the after_cancel loop in the
template. This prevents messages like the following.
bgerror failed to handle background error.
Original error: invalid command name "106096696timer_event"
Error in bgerror: can't invoke "tk" command: application has been destroyed
Requires('gui') causes the test(s) it guards to be skipped if any of
these conditions are met:
- The tests are being run by regrtest.py, and it was started without
enabling the "gui" resource with the "-u" command line option.
- The tests are being run on Windows by a service that is not allowed
to interact with the graphical environment.
- The tests are being run on Linux and X Windows is not available.
- The tests are being run on Mac OSX in a process that cannot make a
window manager connection.
- tkinter.Tk cannot be successfully instantiated for some reason.
- test.support.use_resources has been set by something other than
regrtest.py and does not contain "gui".
Tests of non-GUI operations should avoid creating tk widgets. Incidental
uses of tk variables and messageboxes can be replaced by the mock
classes in idle_test/mock_tk.py. The mock text handles some uses of the
tk Text widget.
3. Running Unit Tests
Assume that xyz.py and test_xyz.py both end with a unittest.main() call.
Running either from an Idle editor runs all tests in the test_xyz file
with the version of Python running Idle. Test output appears in the
Shell window. The 'verbosity=2' option lists all test methods in the
file, which is appropriate when developing tests. The 'exit=False'
option is needed in xyx.py files when an htest follows.
The following command lines also run all test methods, including
GUI tests, in test_xyz.py. (Both '-m idlelib' and '-m idlelib.idle'
start Idle and so cannot run tests.)
python -m idlelib.xyz
python -m idlelib.idle_test.test_xyz
The following runs all idle_test/test_*.py tests interactively.
>>> import unittest
>>> unittest.main('idlelib.idle_test', verbosity=2)
The following run all Idle tests at a command line. Option '-v' is the
same as 'verbosity=2'.
python -m unittest -v idlelib.idle_test
python -m test -v -ugui test_idle
python -m test.test_idle
The idle tests are 'discovered' by
idlelib.idle_test.__init__.load_tests, which is also imported into
test.test_idle. Normally, neither file should be changed when working on
individual test modules. The third command runs unittest indirectly
through regrtest. The same happens when the entire test suite is run
with 'python -m test'. So that command must work for buildbots to stay
green. Idle tests must not disturb the environment in a way that makes
other tests fail (issue 18081).
To run an individual Testcase or test method, extend the dotted name
given to unittest on the command line or use the test -m option. The
latter allows use of other regrtest options. When using the latter,
all components of the pattern must be present, but any can be replaced
by '*'.
python -m unittest -v idlelib.idle_test.test_xyz.Test_case.test_meth
python -m test -m idlelib.idle_test.text_xyz.Test_case.test_meth test_idle
The test suite can be run in an IDLE user process from Shell.
>>> import test.autotest # Issue 25588, 2017/10/13, 3.6.4, 3.7.0a2.
There are currently failures not usually present, and this does not
work when run from the editor.
4. Human-mediated Tests
Human-mediated tests are widget tests that cannot be automated but need
human verification. They are contained in idlelib/idle_test/htest.py,
which has instructions. (Some modules need an auxiliary function,
identified with "# htest # on the header line.) The set is about
complete, though some tests need improvement. To run all htests, run the
htest file from an editor or from the command line with:
python -m idlelib.idle_test.htest
5. Test Coverage
Install the coverage package into your Python 3.6 site-packages
directory. (Its exact location depends on the OS).
> python3 -m pip install coverage
(On Windows, replace 'python3 with 'py -3.6' or perhaps just 'python'.)
The problem with running coverage with repository python is that
coverage uses absolute imports for its submodules, hence it needs to be
in a directory in sys.path. One solution: copy the package to the
directory containing the cpython repository. Call it 'dev'. Then run
coverage either directly or from a script in that directory so that
'dev' is prepended to sys.path.
Either edit or add dev/.coveragerc so it looks something like this.
---
# .coveragerc sets coverage options.
[run]
branch = True
[report]
# Regexes for lines to exclude from consideration
exclude_lines =
# Don't complain if non-runnable code isn't run:
if 0:
if __name__ == .__main__.:
.*# htest #
if not _utest:
if _htest:
---
The additions for IDLE are 'branch = True', to test coverage both ways,
and the last three exclude lines, to exclude things peculiar to IDLE
that are not executed during tests.
A script like the following cover.bat (for Windows) is very handy.
---
@echo off
rem Usage: cover filename [test_ suffix] # proper case required by coverage
rem filename without .py, 2nd parameter if test is not test_filename
setlocal
set py=f:\dev\3x\pcbuild\win32\python_d.exe
set src=idlelib.%1
if "%2" EQU "" set tst=f:/dev/3x/Lib/idlelib/idle_test/test_%1.py
if "%2" NEQ "" set tst=f:/dev/ex/Lib/idlelib/idle_test/test_%2.py
%py% -m coverage run --pylib --source=%src% %tst%
%py% -m coverage report --show-missing
%py% -m coverage html
start htmlcov\3x_Lib_idlelib_%1_py.html
rem Above opens new report; htmlcov\index.html displays report index
---
The second parameter was added for tests of module x not named test_x.
(There were several before modules were renamed, now only one is left.)

View file

@ -0,0 +1,17 @@
'''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.
'''
from os.path import dirname
def load_tests(loader, standard_tests, pattern):
this_dir = dirname(__file__)
top_dir = dirname(dirname(this_dir))
package_tests = loader.discover(start_dir=this_dir, pattern='test*.py',
top_level_dir=top_dir)
standard_tests.addTests(package_tests)
return standard_tests

View file

@ -0,0 +1,415 @@
'''Run human tests of Idle's window, dialog, and popup widgets.
run(*tests)
Create a master Tk window. Within that, run each callable in tests
after finding the matching test spec in this file. If tests is empty,
run an htest for each spec dict in this file after finding the matching
callable in the module named in the spec. Close the window to skip or
end the test.
In a tested module, let X be a global name bound to a callable (class
or function) whose .__name__ attribute is also X (the usual situation).
The first parameter of X must be 'parent'. When called, the parent
argument will be the root window. X must create a child Toplevel
window (or subclass thereof). The Toplevel may be a test widget or
dialog, in which case the callable is the corresonding class. Or the
Toplevel may contain the widget to be tested or set up a context in
which a test widget is invoked. In this latter case, the callable is a
wrapper function that sets up the Toplevel and other objects. Wrapper
function names, such as _editor_window', should start with '_'.
End the module with
if __name__ == '__main__':
<unittest, if there is one>
from idlelib.idle_test.htest import run
run(X)
To have wrapper functions and test invocation code ignored by coveragepy
reports, put '# htest #' on the def statement header line.
def _wrapper(parent): # htest #
Also make sure that the 'if __name__' line matches the above. Then have
make sure that .coveragerc includes the following.
[report]
exclude_lines =
.*# htest #
if __name__ == .__main__.:
(The "." instead of "'" is intentional and necessary.)
To run any X, this file must contain a matching instance of the
following template, with X.__name__ prepended to '_spec'.
When all tests are run, the prefix is use to get X.
_spec = {
'file': '',
'kwds': {'title': ''},
'msg': ""
}
file (no .py): run() imports file.py.
kwds: augmented with {'parent':root} and passed to X as **kwds.
title: an example kwd; some widgets need this, delete if not.
msg: master window hints about testing the widget.
Modules and classes not being tested at the moment:
pyshell.PyShellEditorWindow
debugger.Debugger
autocomplete_w.AutoCompleteWindow
outwin.OutputWindow (indirectly being tested with grep test)
'''
import idlelib.pyshell # Set Windows DPI awareness before Tk().
from importlib import import_module
import tkinter as tk
from tkinter.ttk import Scrollbar
tk.NoDefaultRoot()
AboutDialog_spec = {
'file': 'help_about',
'kwds': {'title': 'help_about test',
'_htest': True,
},
'msg': "Test every button. Ensure Python, TK and IDLE versions "
"are correctly displayed.\n [Close] to exit.",
}
# TODO implement ^\; adding '<Control-Key-\\>' to function does not work.
_calltip_window_spec = {
'file': 'calltip_w',
'kwds': {},
'msg': "Typing '(' should display a calltip.\n"
"Typing ') should hide the calltip.\n"
"So should moving cursor out of argument area.\n"
"Force-open-calltip does not work here.\n"
}
_module_browser_spec = {
'file': 'browser',
'kwds': {},
'msg': "Inspect names of module, class(with superclass if "
"applicable), methods and functions.\nToggle nested items.\n"
"Double clicking on items prints a traceback for an exception "
"that is ignored."
}
_color_delegator_spec = {
'file': 'colorizer',
'kwds': {},
'msg': "The text is sample Python code.\n"
"Ensure components like comments, keywords, builtins,\n"
"string, definitions, and break are correctly colored.\n"
"The default color scheme is in idlelib/config-highlight.def"
}
ConfigDialog_spec = {
'file': 'configdialog',
'kwds': {'title': 'ConfigDialogTest',
'_htest': True,},
'msg': "IDLE preferences dialog.\n"
"In the 'Fonts/Tabs' tab, changing font face, should update the "
"font face of the text in the area below it.\nIn the "
"'Highlighting' tab, try different color schemes. Clicking "
"items in the sample program should update the choices above it."
"\nIn the 'Keys', 'General' and 'Extensions' tabs, test settings "
"of interest."
"\n[Ok] to close the dialog.[Apply] to apply the settings and "
"and [Cancel] to revert all changes.\nRe-run the test to ensure "
"changes made have persisted."
}
# TODO Improve message
_dyn_option_menu_spec = {
'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"
"Select one of the many options in the 'new option set'."
}
# TODO edit wrapper
_editor_window_spec = {
'file': 'editor',
'kwds': {},
'msg': "Test editor functions of interest.\n"
"Best to close editor first."
}
# Update once issue21519 is resolved.
GetKeysDialog_spec = {
'file': 'config_key',
'kwds': {'title': 'Test keybindings',
'action': 'find-again',
'currentKeySequences': [''] ,
'_htest': True,
},
'msg': "Test for different key modifier sequences.\n"
"<nothing> is invalid.\n"
"No modifier key is invalid.\n"
"Shift key with [a-z],[0-9], function key, move key, tab, space "
"is invalid.\nNo validity checking if advanced key binding "
"entry is used."
}
_grep_dialog_spec = {
'file': 'grep',
'kwds': {},
'msg': "Click the 'Show GrepDialog' button.\n"
"Test the various 'Find-in-files' functions.\n"
"The results should be displayed in a new '*Output*' window.\n"
"'Right-click'->'Go to file/line' anywhere in the search results "
"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': 'iomenu',
'kwds': {},
'msg': "Test the following bindings.\n"
"<Control-o> to open file from dialog.\n"
"Edit the file.\n"
"<Control-p> to print the file.\n"
"<Control-s> to save the file.\n"
"<Alt-s> to save-as another file.\n"
"<Control-c> to save-copy-as another file.\n"
"Check that changes were saved by opening the file elsewhere."
}
_multi_call_spec = {
'file': 'multicall',
'kwds': {},
'msg': "The following actions should trigger a print to console or IDLE"
" Shell.\nEntering and leaving the text area, key entry, "
"<Control-Key>,\n<Alt-Key-a>, <Control-Key-a>, "
"<Alt-Control-Key-a>, \n<Control-Button-1>, <Alt-Button-1> and "
"focusing out of the window\nare sequences to be tested."
}
_multistatus_bar_spec = {
'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': 'debugobj',
'kwds': {},
'msg': "Double click on items upto the lowest level.\n"
"Attributes of the objects and related information "
"will be displayed side-by-side at each level."
}
_path_browser_spec = {
'file': 'pathbrowser',
'kwds': {},
'msg': "Test for correct display of all paths in sys.path.\n"
"Toggle nested items upto the lowest level.\n"
"Double clicking on an item prints a traceback\n"
"for an exception that is ignored."
}
_percolator_spec = {
'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 "
"output to the console or to the IDLE shell.\n"
"If both the tracers are 'on', the output from the tracer which "
"was switched 'on' later, should be printed first\n"
"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': 'replace',
'kwds': {},
'msg': "Click the 'Replace' button.\n"
"Test various replace options in the 'Replace dialog'.\n"
"Click [Close] or [X] to close the 'Replace Dialog'."
}
_search_dialog_spec = {
'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',
'kwds': {},
'msg': "You should see a scrollable list of items\n"
"Selecting (clicking) or double clicking an item "
"prints the name to the console or Idle shell.\n"
"Right clicking an item will display a popup."
}
show_idlehelp_spec = {
'file': 'help',
'kwds': {},
'msg': "If the help text displays, this works.\n"
"Text is selectable. Window is scrollable."
}
_stack_viewer_spec = {
'file': 'stackviewer',
'kwds': {},
'msg': "A stacktrace for a NameError exception.\n"
"Expand 'idlelib ...' and '<locals>'.\n"
"Check that exc_value, exc_tb, and exc_type are correct.\n"
}
_tooltip_spec = {
'file': 'tooltip',
'kwds': {},
'msg': "Place mouse cursor over both the buttons\n"
"A tooltip should appear with some text."
}
_tree_widget_spec = {
'file': 'tree',
'kwds': {},
'msg': "The canvas is scrollable.\n"
"Click on folders upto to the lowest level."
}
_undo_delegator_spec = {
'file': 'undo',
'kwds': {},
'msg': "Click [Undo] to undo any action.\n"
"Click [Redo] to redo any action.\n"
"Click [Dump] to dump the current state "
"by printing to the console or the IDLE shell.\n"
}
ViewWindow_spec = {
'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"
"Select text, scroll window, close"
}
_widget_redirector_spec = {
'file': 'redirector',
'kwds': {},
'msg': "Every text insert should be printed to the console "
"or the IDLE shell."
}
def run(*tests):
root = tk.Tk()
root.title('IDLE htest')
root.resizable(0, 0)
# 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 = 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)
test_list = [] # List of tuples of the form (spec, callable widget)
if tests:
for test in tests:
test_spec = globals()[test.__name__ + '_spec']
test_spec['name'] = test.__name__
test_list.append((test_spec, test))
else:
for k, d in globals().items():
if k.endswith('_spec'):
test_name = k[:-5]
test_spec = d
test_spec['name'] = test_name
mod = import_module('idlelib.' + test_spec['file'])
test = getattr(mod, test_name)
test_list.append((test_spec, test))
test_name = tk.StringVar(root)
callable_object = None
test_kwds = None
def next_test():
nonlocal test_name, callable_object, test_kwds
if len(test_list) == 1:
next_button.pack_forget()
test_spec, callable_object = test_list.pop()
test_kwds = test_spec['kwds']
test_kwds['parent'] = root
test_name.set('Test ' + test_spec['name'])
text.configure(state='normal') # enable text editing
text.delete('1.0','end')
text.insert("1.0",test_spec['msg'])
text.configure(state='disabled') # preserve read-only property
def run_test(_=None):
widget = callable_object(**test_kwds)
try:
print(widget.result)
except AttributeError:
pass
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.pack()
next_button.focus_set()
root.bind('<Key-Return>', run_test)
root.bind('<Key-Escape>', close)
next_test()
root.mainloop()
if __name__ == '__main__':
run()

View file

@ -0,0 +1,60 @@
'''Mock classes that imitate idlelib modules or classes.
Attributes and methods will be added as needed for tests.
'''
from idlelib.idle_test.mock_tk import Text
class Func:
'''Record call, capture args, return/raise result set by test.
When mock function is called, set or use attributes:
self.called - increment call number even if no args, kwds passed.
self.args - capture positional arguments.
self.kwds - capture keyword arguments.
self.result - return or raise value set in __init__.
self.return_self - return self instead, to mock query class return.
Most common use will probably be to mock instance methods.
Given class instance, can set and delete as instance attribute.
Mock_tk.Var and Mbox_func are special variants of this.
'''
def __init__(self, result=None, return_self=False):
self.called = 0
self.result = result
self.return_self = return_self
self.args = None
self.kwds = None
def __call__(self, *args, **kwds):
self.called += 1
self.args = args
self.kwds = kwds
if isinstance(self.result, BaseException):
raise self.result
elif self.return_self:
return self
else:
return self.result
class Editor:
'''Minimally imitate editor.EditorWindow class.
'''
def __init__(self, flist=None, filename=None, key=None, root=None):
self.text = Text()
self.undo = UndoDelegator()
def get_selection_indices(self):
first = self.text.index('1.0')
last = self.text.index('end')
return first, last
class UndoDelegator:
'''Minimally imitate undo.UndoDelegator class.
'''
# A real undo block is only needed for user interaction.
def undo_block_start(*args):
pass
def undo_block_stop(*args):
pass

View file

@ -0,0 +1,303 @@
"""Classes that replace tkinter gui objects used by an object being tested.
A gui object is anything with a master or parent parameter, which is
typically required in spite of what the doc strings say.
"""
class Event:
'''Minimal mock with attributes for testing event handlers.
This is not a gui object, but is used as an argument for callbacks
that access attributes of the event passed. If a callback ignores
the event, other than the fact that is happened, pass 'event'.
Keyboard, mouse, window, and other sources generate Event instances.
Event instances have the following attributes: serial (number of
event), time (of event), type (of event as number), widget (in which
event occurred), and x,y (position of mouse). There are other
attributes for specific events, such as keycode for key events.
tkinter.Event.__doc__ has more but is still not complete.
'''
def __init__(self, **kwds):
"Create event with attributes needed for test"
self.__dict__.update(kwds)
class Var:
"Use for String/Int/BooleanVar: incomplete"
def __init__(self, master=None, value=None, name=None):
self.master = master
self.value = value
self.name = name
def set(self, value):
self.value = value
def get(self):
return self.value
class Mbox_func:
"""Generic mock for messagebox functions, which all have the same signature.
Instead of displaying a message box, the mock's call method saves the
arguments as instance attributes, which test functions can then examime.
The test can set the result returned to ask function
"""
def __init__(self, result=None):
self.result = result # Return None for all show funcs
def __call__(self, title, message, *args, **kwds):
# Save all args for possible examination by tester
self.title = title
self.message = message
self.args = args
self.kwds = kwds
return self.result # Set by tester for ask functions
class Mbox:
"""Mock for tkinter.messagebox with an Mbox_func for each function.
This module was 'tkMessageBox' in 2.x; hence the 'import as' in 3.x.
Example usage in test_module.py for testing functions in module.py:
---
from idlelib.idle_test.mock_tk import Mbox
import module
orig_mbox = module.tkMessageBox
showerror = Mbox.showerror # example, for attribute access in test methods
class Test(unittest.TestCase):
@classmethod
def setUpClass(cls):
module.tkMessageBox = Mbox
@classmethod
def tearDownClass(cls):
module.tkMessageBox = orig_mbox
---
For 'ask' functions, set func.result return value before calling the method
that uses the message function. When tkMessageBox functions are the
only gui alls in a method, this replacement makes the method gui-free,
"""
askokcancel = Mbox_func() # True or False
askquestion = Mbox_func() # 'yes' or 'no'
askretrycancel = Mbox_func() # True or False
askyesno = Mbox_func() # True or False
askyesnocancel = Mbox_func() # True, False, or None
showerror = Mbox_func() # None
showinfo = Mbox_func() # None
showwarning = Mbox_func() # None
from _tkinter import TclError
class Text:
"""A semi-functional non-gui replacement for tkinter.Text text editors.
The mock's data model is that a text is a list of \n-terminated lines.
The mock adds an empty string at the beginning of the list so that the
index of actual lines start at 1, as with Tk. The methods never see this.
Tk initializes files with a terminal \n that cannot be deleted. It is
invisible in the sense that one cannot move the cursor beyond it.
This class is only tested (and valid) with strings of ascii chars.
For testing, we are not concerned with Tk Text's treatment of,
for instance, 0-width characters or character + accent.
"""
def __init__(self, master=None, cnf={}, **kw):
'''Initialize mock, non-gui, text-only Text widget.
At present, all args are ignored. Almost all affect visual behavior.
There are just a few Text-only options that affect text behavior.
'''
self.data = ['', '\n']
def index(self, index):
"Return string version of index decoded according to current text."
return "%s.%s" % self._decode(index, endflag=1)
def _decode(self, index, endflag=0):
"""Return a (line, char) tuple of int indexes into self.data.
This implements .index without converting the result back to a string.
The result is constrained by the number of lines and linelengths of
self.data. For many indexes, the result is initially (1, 0).
The input index may have any of several possible forms:
* line.char float: converted to 'line.char' string;
* 'line.char' string, where line and char are decimal integers;
* 'line.char lineend', where lineend='lineend' (and char is ignored);
* 'line.end', where end='end' (same as above);
* 'insert', the positions before terminal \n;
* 'end', whose meaning depends on the endflag passed to ._endex.
* 'sel.first' or 'sel.last', where sel is a tag -- not implemented.
"""
if isinstance(index, (float, bytes)):
index = str(index)
try:
index=index.lower()
except AttributeError:
raise TclError('bad text index "%s"' % index) from None
lastline = len(self.data) - 1 # same as number of text lines
if index == 'insert':
return lastline, len(self.data[lastline]) - 1
elif index == 'end':
return self._endex(endflag)
line, char = index.split('.')
line = int(line)
# Out of bounds line becomes first or last ('end') index
if line < 1:
return 1, 0
elif line > lastline:
return self._endex(endflag)
linelength = len(self.data[line]) -1 # position before/at \n
if char.endswith(' lineend') or char == 'end':
return line, linelength
# Tk requires that ignored chars before ' lineend' be valid int
# Out of bounds char becomes first or last index of line
char = int(char)
if char < 0:
char = 0
elif char > linelength:
char = linelength
return line, char
def _endex(self, endflag):
'''Return position for 'end' or line overflow corresponding to endflag.
-1: position before terminal \n; for .insert(), .delete
0: position after terminal \n; for .get, .delete index 1
1: same viewed as beginning of non-existent next line (for .index)
'''
n = len(self.data)
if endflag == 1:
return n, 0
else:
n -= 1
return n, len(self.data[n]) + endflag
def insert(self, index, chars):
"Insert chars before the character at index."
if not chars: # ''.splitlines() is [], not ['']
return
chars = chars.splitlines(True)
if chars[-1][-1] == '\n':
chars.append('')
line, char = self._decode(index, -1)
before = self.data[line][:char]
after = self.data[line][char:]
self.data[line] = before + chars[0]
self.data[line+1:line+1] = chars[1:]
self.data[line+len(chars)-1] += after
def get(self, index1, index2=None):
"Return slice from index1 to index2 (default is 'index1+1')."
startline, startchar = self._decode(index1)
if index2 is None:
endline, endchar = startline, startchar+1
else:
endline, endchar = self._decode(index2)
if startline == endline:
return self.data[startline][startchar:endchar]
else:
lines = [self.data[startline][startchar:]]
for i in range(startline+1, endline):
lines.append(self.data[i])
lines.append(self.data[endline][:endchar])
return ''.join(lines)
def delete(self, index1, index2=None):
'''Delete slice from index1 to index2 (default is 'index1+1').
Adjust default index2 ('index+1) for line ends.
Do not delete the terminal \n at the very end of self.data ([-1][-1]).
'''
startline, startchar = self._decode(index1, -1)
if index2 is None:
if startchar < len(self.data[startline])-1:
# not deleting \n
endline, endchar = startline, startchar+1
elif startline < len(self.data) - 1:
# deleting non-terminal \n, convert 'index1+1 to start of next line
endline, endchar = startline+1, 0
else:
# do not delete terminal \n if index1 == 'insert'
return
else:
endline, endchar = self._decode(index2, -1)
# restricting end position to insert position excludes terminal \n
if startline == endline and startchar < endchar:
self.data[startline] = self.data[startline][:startchar] + \
self.data[startline][endchar:]
elif startline < endline:
self.data[startline] = self.data[startline][:startchar] + \
self.data[endline][endchar:]
startline += 1
for i in range(startline, endline+1):
del self.data[startline]
def compare(self, index1, op, index2):
line1, char1 = self._decode(index1)
line2, char2 = self._decode(index2)
if op == '<':
return line1 < line2 or line1 == line2 and char1 < char2
elif op == '<=':
return line1 < line2 or line1 == line2 and char1 <= char2
elif op == '>':
return line1 > line2 or line1 == line2 and char1 > char2
elif op == '>=':
return line1 > line2 or line1 == line2 and char1 >= char2
elif op == '==':
return line1 == line2 and char1 == char2
elif op == '!=':
return line1 != line2 or char1 != char2
else:
raise TclError('''bad comparison operator "%s": '''
'''must be <, <=, ==, >=, >, or !=''' % op)
# The following Text methods normally do something and return None.
# Whether doing nothing is sufficient for a test will depend on the test.
def mark_set(self, name, index):
"Set mark *name* before the character at index."
pass
def mark_unset(self, *markNames):
"Delete all marks in markNames."
def tag_remove(self, tagName, index1, index2=None):
"Remove tag tagName from all characters between index1 and index2."
pass
# The following Text methods affect the graphics screen and return None.
# Doing nothing should always be sufficient for tests.
def scan_dragto(self, x, y):
"Adjust the view of the text according to scan_mark"
def scan_mark(self, x, y):
"Remember the current X, Y coordinates."
def see(self, index):
"Scroll screen to make the character at INDEX is visible."
pass
# The following is a Misc method inherited by Text.
# It should properly go in a Misc mock, but is included here for now.
def bind(sequence=None, func=None, add=None):
"Bind to this widget at event sequence a call to function func."
pass
class Entry:
"Mock for tkinter.Entry."
def focus_set(self):
pass

View file

@ -0,0 +1,30 @@
"Test , coverage %."
from idlelib import zzdummy
import unittest
from test.support import requires
from tkinter import Tk
class Test(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.root.withdraw()
@classmethod
def tearDownClass(cls):
cls.root.update_idletasks()
## for id in cls.root.tk.call('after', 'info'):
## cls.root.after_cancel(id) # Need for EditorWindow.
cls.root.destroy()
del cls.root
def test_init(self):
self.assertTrue(True)
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,144 @@
"Test autocomplete, coverage 57%."
import unittest
from test.support import requires
from tkinter import Tk, Text
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
class DummyEditwin:
def __init__(self, root, text):
self.root = root
self.text = text
self.indentwidth = 8
self.tabwidth = 8
self.context_use_ps1 = True
class AutoCompleteTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.text = Text(cls.root)
cls.editor = DummyEditwin(cls.root, cls.text)
@classmethod
def tearDownClass(cls):
del cls.editor, cls.text
cls.root.destroy()
del cls.root
def setUp(self):
self.editor.text.delete('1.0', 'end')
self.autocomplete = ac.AutoComplete(self.editor)
def test_init(self):
self.assertEqual(self.autocomplete.editwin, self.editor)
def test_make_autocomplete_window(self):
testwin = self.autocomplete._make_autocomplete_window()
self.assertIsInstance(testwin, acw.AutoCompleteWindow)
def test_remove_autocomplete_window(self):
self.autocomplete.autocompletewindow = (
self.autocomplete._make_autocomplete_window())
self.autocomplete._remove_autocomplete_window()
self.assertIsNone(self.autocomplete.autocompletewindow)
def test_force_open_completions_event(self):
# Test that force_open_completions_event calls _open_completions
o_cs = Func()
self.autocomplete.open_completions = o_cs
self.autocomplete.force_open_completions_event('event')
self.assertEqual(o_cs.args, (True, False, True))
def test_try_open_completions_event(self):
Equal = self.assertEqual
autocomplete = self.autocomplete
trycompletions = self.autocomplete.try_open_completions_event
o_c_l = Func()
autocomplete._open_completions_later = o_c_l
# _open_completions_later should not be called with no text in editor
trycompletions('event')
Equal(o_c_l.args, None)
# _open_completions_later should be called with COMPLETE_ATTRIBUTES (1)
self.text.insert('1.0', 're.')
trycompletions('event')
Equal(o_c_l.args, (False, False, False, 1))
# _open_completions_later should be called with COMPLETE_FILES (2)
self.text.delete('1.0', 'end')
self.text.insert('1.0', '"./Lib/')
trycompletions('event')
Equal(o_c_l.args, (False, False, False, 2))
def test_autocomplete_event(self):
Equal = self.assertEqual
autocomplete = self.autocomplete
# Test that the autocomplete event is ignored if user is pressing a
# modifier key in addition to the tab key
ev = Event(mc_state=True)
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()
Equal(self.autocomplete.autocomplete_event(ev), 'break')
# If autocomplete window is not active or does not exist,
# open_completions is called. Return depends on its return.
autocomplete._remove_autocomplete_window()
o_cs = Func() # .result = None
autocomplete.open_completions = o_cs
Equal(self.autocomplete.autocomplete_event(ev), None)
Equal(o_cs.args, (False, True, True))
o_cs.result = True
Equal(self.autocomplete.autocomplete_event(ev), 'break')
Equal(o_cs.args, (False, True, True))
def test_open_completions_later(self):
# Test that autocomplete._delayed_completion_id is set
pass
def test_delayed_open_completions(self):
# Test that autocomplete._delayed_completion_id set to None and that
# open_completions only called if insertion index is the same as
# _delayed_completion_index
pass
def test_open_completions(self):
# Test completions of files and attributes as well as non-completion
# of errors
pass
def test_fetch_completions(self):
# Test that fetch_completions returns 2 lists:
# For attribute completion, a large list containing all variables, and
# a small list containing non-private variables.
# For file completion, a large list containing all files in the path,
# and a small list containing files that do not start with '.'
pass
def test_get_entity(self):
# Test that a name is in the namespace of sys.modules and
# __main__.__dict__
pass
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,32 @@
"Test autocomplete_w, coverage 11%."
import unittest
from test.support import requires
from tkinter import Tk, Text
import idlelib.autocomplete_w as acw
class AutoCompleteWindowTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.root.withdraw()
cls.text = Text(cls.root)
cls.acw = acw.AutoCompleteWindow(cls.text)
@classmethod
def tearDownClass(cls):
del cls.text, cls.acw
cls.root.update_idletasks()
cls.root.destroy()
del cls.root
def test_init(self):
self.assertEqual(self.acw.widget, self.text)
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,155 @@
"Test autoexpand, coverage 100%."
from idlelib.autoexpand import AutoExpand
import unittest
from test.support import requires
from tkinter import Text, Tk
class Dummy_Editwin:
# AutoExpand.__init__ only needs .text
def __init__(self, text):
self.text = text
class AutoExpandTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.tk = Tk()
cls.text = Text(cls.tk)
cls.auto_expand = AutoExpand(Dummy_Editwin(cls.text))
cls.auto_expand.bell = lambda: None
# If mock_tk.Text._decode understood indexes 'insert' with suffixed 'linestart',
# 'wordstart', and 'lineend', used by autoexpand, we could use the following
# to run these test on non-gui machines (but check bell).
## try:
## requires('gui')
## #raise ResourceDenied() # Uncomment to test mock.
## except ResourceDenied:
## from idlelib.idle_test.mock_tk import Text
## cls.text = Text()
## cls.text.bell = lambda: None
## else:
## from tkinter import Tk, Text
## cls.tk = Tk()
## cls.text = Text(cls.tk)
@classmethod
def tearDownClass(cls):
del cls.text, cls.auto_expand
if hasattr(cls, 'tk'):
cls.tk.destroy()
del cls.tk
def tearDown(self):
self.text.delete('1.0', 'end')
def test_get_prevword(self):
text = self.text
previous = self.auto_expand.getprevword
equal = self.assertEqual
equal(previous(), '')
text.insert('insert', 't')
equal(previous(), 't')
text.insert('insert', 'his')
equal(previous(), 'this')
text.insert('insert', ' ')
equal(previous(), '')
text.insert('insert', 'is')
equal(previous(), 'is')
text.insert('insert', '\nsample\nstring')
equal(previous(), 'string')
text.delete('3.0', 'insert')
equal(previous(), '')
text.delete('1.0', 'end')
equal(previous(), '')
def test_before_only(self):
previous = self.auto_expand.getprevword
expand = self.auto_expand.expand_word_event
equal = self.assertEqual
self.text.insert('insert', 'ab ac bx ad ab a')
equal(self.auto_expand.getwords(), ['ab', 'ad', 'ac', 'a'])
expand('event')
equal(previous(), 'ab')
expand('event')
equal(previous(), 'ad')
expand('event')
equal(previous(), 'ac')
expand('event')
equal(previous(), 'a')
def test_after_only(self):
# Also add punctuation 'noise' that should be ignored.
text = self.text
previous = self.auto_expand.getprevword
expand = self.auto_expand.expand_word_event
equal = self.assertEqual
text.insert('insert', 'a, [ab] ac: () bx"" cd ac= ad ya')
text.mark_set('insert', '1.1')
equal(self.auto_expand.getwords(), ['ab', 'ac', 'ad', 'a'])
expand('event')
equal(previous(), 'ab')
expand('event')
equal(previous(), 'ac')
expand('event')
equal(previous(), 'ad')
expand('event')
equal(previous(), 'a')
def test_both_before_after(self):
text = self.text
previous = self.auto_expand.getprevword
expand = self.auto_expand.expand_word_event
equal = self.assertEqual
text.insert('insert', 'ab xy yz\n')
text.insert('insert', 'a ac by ac')
text.mark_set('insert', '2.1')
equal(self.auto_expand.getwords(), ['ab', 'ac', 'a'])
expand('event')
equal(previous(), 'ab')
expand('event')
equal(previous(), 'ac')
expand('event')
equal(previous(), 'a')
def test_other_expand_cases(self):
text = self.text
expand = self.auto_expand.expand_word_event
equal = self.assertEqual
# no expansion candidate found
equal(self.auto_expand.getwords(), [])
equal(expand('event'), 'break')
text.insert('insert', 'bx cy dz a')
equal(self.auto_expand.getwords(), [])
# reset state by successfully expanding once
# move cursor to another position and expand again
text.insert('insert', 'ac xy a ac ad a')
text.mark_set('insert', '1.7')
expand('event')
initial_state = self.auto_expand.state
text.mark_set('insert', '1.end')
expand('event')
new_state = self.auto_expand.state
self.assertNotEqual(initial_state, new_state)
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,248 @@
"Test browser, coverage 90%."
from idlelib import browser
from test.support import requires
import unittest
from unittest import mock
from idlelib.idle_test.mock_idle import Func
from collections import deque
import os.path
from idlelib import _pyclbr as pyclbr
from tkinter import Tk
from idlelib.tree import TreeNode
class ModuleBrowserTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.root.withdraw()
cls.mb = browser.ModuleBrowser(cls.root, __file__, _utest=True)
@classmethod
def tearDownClass(cls):
cls.mb.close()
cls.root.update_idletasks()
cls.root.destroy()
del cls.root, cls.mb
def test_init(self):
mb = self.mb
eq = self.assertEqual
eq(mb.path, __file__)
eq(pyclbr._modules, {})
self.assertIsInstance(mb.node, TreeNode)
self.assertIsNotNone(browser.file_open)
def test_settitle(self):
mb = self.mb
self.assertIn(os.path.basename(__file__), mb.top.title())
self.assertEqual(mb.top.iconname(), 'Module Browser')
def test_rootnode(self):
mb = self.mb
rn = mb.rootnode()
self.assertIsInstance(rn, browser.ModuleBrowserTreeItem)
def test_close(self):
mb = self.mb
mb.top.destroy = Func()
mb.node.destroy = Func()
mb.close()
self.assertTrue(mb.top.destroy.called)
self.assertTrue(mb.node.destroy.called)
del mb.top.destroy, mb.node.destroy
# Nested tree same as in test_pyclbr.py except for supers on C0. C1.
mb = pyclbr
module, fname = 'test', 'test.py'
f0 = mb.Function(module, 'f0', fname, 1)
f1 = mb._nest_function(f0, 'f1', 2)
f2 = mb._nest_function(f1, 'f2', 3)
c1 = mb._nest_class(f0, 'c1', 5)
C0 = mb.Class(module, 'C0', ['base'], fname, 6)
F1 = mb._nest_function(C0, 'F1', 8)
C1 = mb._nest_class(C0, 'C1', 11, [''])
C2 = mb._nest_class(C1, 'C2', 12)
F3 = mb._nest_function(C2, 'F3', 14)
mock_pyclbr_tree = {'f0': f0, 'C0': C0}
# Adjust C0.name, C1.name so tests do not depend on order.
browser.transform_children(mock_pyclbr_tree, 'test') # C0(base)
browser.transform_children(C0.children) # C1()
# The class below checks that the calls above are correct
# and that duplicate calls have no effect.
class TransformChildrenTest(unittest.TestCase):
def test_transform_module_children(self):
eq = self.assertEqual
transform = browser.transform_children
# Parameter matches tree module.
tcl = list(transform(mock_pyclbr_tree, 'test'))
eq(tcl, [f0, C0])
eq(tcl[0].name, 'f0')
eq(tcl[1].name, 'C0(base)')
# Check that second call does not change suffix.
tcl = list(transform(mock_pyclbr_tree, 'test'))
eq(tcl[1].name, 'C0(base)')
# Nothing to traverse if parameter name isn't same as tree module.
tcl = list(transform(mock_pyclbr_tree, 'different name'))
eq(tcl, [])
def test_transform_node_children(self):
eq = self.assertEqual
transform = browser.transform_children
# Class with two children, one name altered.
tcl = list(transform(C0.children))
eq(tcl, [F1, C1])
eq(tcl[0].name, 'F1')
eq(tcl[1].name, 'C1()')
tcl = list(transform(C0.children))
eq(tcl[1].name, 'C1()')
# Function with two children.
eq(list(transform(f0.children)), [f1, c1])
class ModuleBrowserTreeItemTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.mbt = browser.ModuleBrowserTreeItem(fname)
def test_init(self):
self.assertEqual(self.mbt.file, fname)
def test_gettext(self):
self.assertEqual(self.mbt.GetText(), fname)
def test_geticonname(self):
self.assertEqual(self.mbt.GetIconName(), 'python')
def test_isexpandable(self):
self.assertTrue(self.mbt.IsExpandable())
def test_listchildren(self):
save_rex = browser.pyclbr.readmodule_ex
save_tc = browser.transform_children
browser.pyclbr.readmodule_ex = Func(result=mock_pyclbr_tree)
browser.transform_children = Func(result=[f0, C0])
try:
self.assertEqual(self.mbt.listchildren(), [f0, C0])
finally:
browser.pyclbr.readmodule_ex = save_rex
browser.transform_children = save_tc
def test_getsublist(self):
mbt = self.mbt
mbt.listchildren = Func(result=[f0, C0])
sub0, sub1 = mbt.GetSubList()
del mbt.listchildren
self.assertIsInstance(sub0, browser.ChildBrowserTreeItem)
self.assertIsInstance(sub1, browser.ChildBrowserTreeItem)
self.assertEqual(sub0.name, 'f0')
self.assertEqual(sub1.name, 'C0(base)')
@mock.patch('idlelib.browser.file_open')
def test_ondoubleclick(self, fopen):
mbt = self.mbt
with mock.patch('os.path.exists', return_value=False):
mbt.OnDoubleClick()
fopen.assert_not_called()
with mock.patch('os.path.exists', return_value=True):
mbt.OnDoubleClick()
fopen.assert_called()
fopen.called_with(fname)
class ChildBrowserTreeItemTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
CBT = browser.ChildBrowserTreeItem
cls.cbt_f1 = CBT(f1)
cls.cbt_C1 = CBT(C1)
cls.cbt_F1 = CBT(F1)
@classmethod
def tearDownClass(cls):
del cls.cbt_C1, cls.cbt_f1, cls.cbt_F1
def test_init(self):
eq = self.assertEqual
eq(self.cbt_C1.name, 'C1()')
self.assertFalse(self.cbt_C1.isfunction)
eq(self.cbt_f1.name, 'f1')
self.assertTrue(self.cbt_f1.isfunction)
def test_gettext(self):
self.assertEqual(self.cbt_C1.GetText(), 'class C1()')
self.assertEqual(self.cbt_f1.GetText(), 'def f1(...)')
def test_geticonname(self):
self.assertEqual(self.cbt_C1.GetIconName(), 'folder')
self.assertEqual(self.cbt_f1.GetIconName(), 'python')
def test_isexpandable(self):
self.assertTrue(self.cbt_C1.IsExpandable())
self.assertTrue(self.cbt_f1.IsExpandable())
self.assertFalse(self.cbt_F1.IsExpandable())
def test_getsublist(self):
eq = self.assertEqual
CBT = browser.ChildBrowserTreeItem
f1sublist = self.cbt_f1.GetSubList()
self.assertIsInstance(f1sublist[0], CBT)
eq(len(f1sublist), 1)
eq(f1sublist[0].name, 'f2')
eq(self.cbt_F1.GetSubList(), [])
@mock.patch('idlelib.browser.file_open')
def test_ondoubleclick(self, fopen):
goto = fopen.return_value.gotoline = mock.Mock()
self.cbt_F1.OnDoubleClick()
fopen.assert_called()
goto.assert_called()
goto.assert_called_with(self.cbt_F1.obj.lineno)
# Failure test would have to raise OSError or AttributeError.
class NestedChildrenTest(unittest.TestCase):
"Test that all the nodes in a nested tree are added to the BrowserTree."
def test_nested(self):
queue = deque()
actual_names = []
# The tree items are processed in breadth first order.
# Verify that processing each sublist hits every node and
# in the right order.
expected_names = ['f0', 'C0(base)',
'f1', 'c1', 'F1', 'C1()',
'f2', 'C2',
'F3']
CBT = browser.ChildBrowserTreeItem
queue.extend((CBT(f0), CBT(C0)))
while queue:
cb = queue.popleft()
sublist = cb.GetSubList()
queue.extend(sublist)
self.assertIn(cb.name, cb.GetText())
self.assertIn(cb.GetIconName(), ('python', 'folder'))
self.assertIs(cb.IsExpandable(), sublist != [])
actual_names.append(cb.name)
self.assertEqual(actual_names, expected_names)
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,216 @@
"Test calltip, coverage 60%"
from idlelib import calltip
import unittest
import textwrap
import types
default_tip = calltip._default_callable_argspec
# Test Class TC is used in multiple get_argspec test methods
class TC():
'doc'
tip = "(ai=None, *b)"
def __init__(self, ai=None, *b): 'doc'
__init__.tip = "(self, ai=None, *b)"
def t1(self): 'doc'
t1.tip = "(self)"
def t2(self, ai, b=None): 'doc'
t2.tip = "(self, ai, b=None)"
def t3(self, ai, *args): 'doc'
t3.tip = "(self, ai, *args)"
def t4(self, *args): 'doc'
t4.tip = "(self, *args)"
def t5(self, ai, b=None, *args, **kw): 'doc'
t5.tip = "(self, ai, b=None, *args, **kw)"
def t6(no, self): 'doc'
t6.tip = "(no, self)"
def __call__(self, ci): 'doc'
__call__.tip = "(self, ci)"
# attaching .tip to wrapped methods does not work
@classmethod
def cm(cls, a): 'doc'
@staticmethod
def sm(b): 'doc'
tc = TC()
signature = calltip.get_argspec # 2.7 and 3.x use different functions
class Get_signatureTest(unittest.TestCase):
# The signature function must return a string, even if blank.
# Test a variety of objects to be sure that none cause it to raise
# (quite aside from getting as correct an answer as possible).
# The tests of builtins may break if inspect or the docstrings change,
# but a red buildbot is better than a user crash (as has happened).
# For a simple mismatch, change the expected output to the actual.
def test_builtins(self):
# Python class that inherits builtin methods
class List(list): "List() doc"
# Simulate builtin with no docstring for default tip test
class SB: __call__ = None
def gtest(obj, out):
self.assertEqual(signature(obj), out)
if List.__doc__ is not None:
gtest(List, List.__doc__) # This and append_doc changed in 3.7.
gtest(list.__new__,
'(*args, **kwargs)\n'
'Create and return a new object.'
' See help(type) for accurate signature.')
gtest(list.__init__,
'(self, /, *args, **kwargs)'
+ calltip._argument_positional + '\n' +
'Initialize self. See help(type(self)) for accurate signature.')
append_doc = "L.append(object) -> None -- append object to end"
gtest(list.append, append_doc)
gtest([].append, append_doc)
gtest(List.append, append_doc)
gtest(types.MethodType, "method(function, instance)")
gtest(SB(), default_tip)
import re
p = re.compile('')
gtest(re.sub, '''\
(pattern, repl, string, count=0, flags=0)
Return the string obtained by replacing the leftmost
non-overlapping occurrences of the pattern in string by the
replacement repl. repl can be either a string or a callable;
if a string, backslash escapes in it are processed. If it is
a callable, it's passed the match object and must return''')
gtest(p.sub, '''\
(repl, string, count=0)
Return the string obtained by replacing the leftmost \
non-overlapping occurrences o...''')
def test_signature_wrap(self):
if textwrap.TextWrapper.__doc__ is not None:
self.assertEqual(signature(textwrap.TextWrapper), '''\
(width=70, initial_indent='', subsequent_indent='', expand_tabs=True,
replace_whitespace=True, fix_sentence_endings=False, break_long_words=True,
drop_whitespace=True, break_on_hyphens=True, tabsize=8, *, max_lines=None,
placeholder=' [...]')''')
def test_docline_truncation(self):
def f(): pass
f.__doc__ = 'a'*300
self.assertEqual(signature(f), '()\n' + 'a' * (calltip._MAX_COLS-3) + '...')
def test_multiline_docstring(self):
# Test fewer lines than max.
self.assertEqual(signature(range),
"range(stop) -> range object\n"
"range(start, stop[, step]) -> range object")
# Test max lines
self.assertEqual(signature(bytes), '''\
bytes(iterable_of_ints) -> bytes
bytes(string, encoding[, errors]) -> bytes
bytes(bytes_or_buffer) -> immutable copy of bytes_or_buffer
bytes(int) -> bytes object of size given by the parameter initialized with null bytes
bytes() -> empty bytes object''')
# Test more than max lines
def f(): pass
f.__doc__ = 'a\n' * 15
self.assertEqual(signature(f), '()' + '\na' * calltip._MAX_LINES)
def test_functions(self):
def t1(): 'doc'
t1.tip = "()"
def t2(a, b=None): 'doc'
t2.tip = "(a, b=None)"
def t3(a, *args): 'doc'
t3.tip = "(a, *args)"
def t4(*args): 'doc'
t4.tip = "(*args)"
def t5(a, b=None, *args, **kw): 'doc'
t5.tip = "(a, b=None, *args, **kw)"
doc = '\ndoc' if t1.__doc__ is not None else ''
for func in (t1, t2, t3, t4, t5, TC):
self.assertEqual(signature(func), func.tip + doc)
def test_methods(self):
doc = '\ndoc' if TC.__doc__ is not None else ''
for meth in (TC.t1, TC.t2, TC.t3, TC.t4, TC.t5, TC.t6, TC.__call__):
self.assertEqual(signature(meth), meth.tip + doc)
self.assertEqual(signature(TC.cm), "(a)" + doc)
self.assertEqual(signature(TC.sm), "(b)" + doc)
def test_bound_methods(self):
# test that first parameter is correctly removed from argspec
doc = '\ndoc' if TC.__doc__ is not None else ''
for meth, mtip in ((tc.t1, "()"), (tc.t4, "(*args)"),
(tc.t6, "(self)"), (tc.__call__, '(ci)'),
(tc, '(ci)'), (TC.cm, "(a)"),):
self.assertEqual(signature(meth), mtip + doc)
def test_starred_parameter(self):
# test that starred first parameter is *not* removed from argspec
class C:
def m1(*args): pass
c = C()
for meth, mtip in ((C.m1, '(*args)'), (c.m1, "(*args)"),):
self.assertEqual(signature(meth), mtip)
def test_invalid_method_signature(self):
class C:
def m2(**kwargs): pass
class Test:
def __call__(*, a): pass
mtip = calltip._invalid_method
self.assertEqual(signature(C().m2), mtip)
self.assertEqual(signature(Test()), mtip)
def test_non_ascii_name(self):
# test that re works to delete a first parameter name that
# includes non-ascii chars, such as various forms of A.
uni = "(A\u0391\u0410\u05d0\u0627\u0905\u1e00\u3042, a)"
assert calltip._first_param.sub('', uni) == '(a)'
def test_no_docstring(self):
def nd(s):
pass
TC.nd = nd
self.assertEqual(signature(nd), "(s)")
self.assertEqual(signature(TC.nd), "(s)")
self.assertEqual(signature(tc.nd), "()")
def test_attribute_exception(self):
class NoCall:
def __getattr__(self, name):
raise BaseException
class CallA(NoCall):
def __call__(oui, a, b, c):
pass
class CallB(NoCall):
def __call__(self, ci):
pass
for meth, mtip in ((NoCall, default_tip), (CallA, default_tip),
(NoCall(), ''), (CallA(), '(a, b, c)'),
(CallB(), '(ci)')):
self.assertEqual(signature(meth), mtip)
def test_non_callables(self):
for obj in (0, 0.0, '0', b'0', [], {}):
self.assertEqual(signature(obj), '')
class Get_entityTest(unittest.TestCase):
def test_bad_entity(self):
self.assertIsNone(calltip.get_entity('1/0'))
def test_good_entity(self):
self.assertIs(calltip.get_entity('int'), int)
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,29 @@
"Test calltip_w, coverage 18%."
from idlelib import calltip_w
import unittest
from test.support import requires
from tkinter import Tk, Text
class CallTipWindowTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.root.withdraw()
cls.text = Text(cls.root)
cls.calltip = calltip_w.CalltipWindow(cls.text)
@classmethod
def tearDownClass(cls):
cls.root.update_idletasks()
cls.root.destroy()
del cls.text, cls.root
def test_init(self):
self.assertEqual(self.calltip.anchor_widget, self.text)
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,403 @@
"Test codecontext, coverage 100%"
from idlelib import codecontext
import unittest
from test.support import requires
from tkinter import Tk, Frame, Text, TclError
from unittest import mock
import re
from idlelib import config
usercfg = codecontext.idleConf.userCfg
testcfg = {
'main': config.IdleUserConfParser(''),
'highlight': config.IdleUserConfParser(''),
'keys': config.IdleUserConfParser(''),
'extensions': config.IdleUserConfParser(''),
}
code_sample = """\
class C1():
# Class comment.
def __init__(self, a, b):
self.a = a
self.b = b
def compare(self):
if a > b:
return a
elif a < b:
return b
else:
return None
"""
class DummyEditwin:
def __init__(self, root, frame, text):
self.root = root
self.top = root
self.text_frame = frame
self.text = text
class CodeContextTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
root = cls.root = Tk()
root.withdraw()
frame = cls.frame = Frame(root)
text = cls.text = Text(frame)
text.insert('1.0', code_sample)
# Need to pack for creation of code context text widget.
frame.pack(side='left', fill='both', expand=1)
text.pack(side='top', fill='both', expand=1)
cls.editor = DummyEditwin(root, frame, text)
codecontext.idleConf.userCfg = testcfg
@classmethod
def tearDownClass(cls):
codecontext.idleConf.userCfg = usercfg
cls.editor.text.delete('1.0', 'end')
del cls.editor, cls.frame, cls.text
cls.root.update_idletasks()
cls.root.destroy()
del cls.root
def setUp(self):
self.text.yview(0)
self.cc = codecontext.CodeContext(self.editor)
def tearDown(self):
if self.cc.context:
self.cc.context.destroy()
# Explicitly call __del__ to remove scheduled scripts.
self.cc.__del__()
del self.cc.context, self.cc
def test_init(self):
eq = self.assertEqual
ed = self.editor
cc = self.cc
eq(cc.editwin, ed)
eq(cc.text, ed.text)
eq(cc.textfont, ed.text['font'])
self.assertIsNone(cc.context)
eq(cc.info, [(0, -1, '', False)])
eq(cc.topvisible, 1)
eq(self.root.tk.call('after', 'info', self.cc.t1)[1], 'timer')
eq(self.root.tk.call('after', 'info', self.cc.t2)[1], 'timer')
def test_del(self):
self.cc.__del__()
with self.assertRaises(TclError) as msg:
self.root.tk.call('after', 'info', self.cc.t1)
self.assertIn("doesn't exist", msg)
with self.assertRaises(TclError) as msg:
self.root.tk.call('after', 'info', self.cc.t2)
self.assertIn("doesn't exist", msg)
# For coverage on the except. Have to delete because the
# above Tcl error is caught by after_cancel.
del self.cc.t1, self.cc.t2
self.cc.__del__()
def test_reload(self):
codecontext.CodeContext.reload()
self.assertEqual(self.cc.colors, {'background': 'lightgray',
'foreground': '#000000'})
self.assertEqual(self.cc.context_depth, 15)
def test_toggle_code_context_event(self):
eq = self.assertEqual
cc = self.cc
toggle = cc.toggle_code_context_event
# Make sure code context is off.
if cc.context:
toggle()
# Toggle on.
eq(toggle(), 'break')
self.assertIsNotNone(cc.context)
eq(cc.context['font'], cc.textfont)
eq(cc.context['fg'], cc.colors['foreground'])
eq(cc.context['bg'], cc.colors['background'])
eq(cc.context.get('1.0', 'end-1c'), '')
# Toggle off.
eq(toggle(), 'break')
self.assertIsNone(cc.context)
def test_get_context(self):
eq = self.assertEqual
gc = self.cc.get_context
# stopline must be greater than 0.
with self.assertRaises(AssertionError):
gc(1, stopline=0)
eq(gc(3), ([(2, 0, 'class C1():', 'class')], 0))
# Don't return comment.
eq(gc(4), ([(2, 0, 'class C1():', 'class')], 0))
# Two indentation levels and no comment.
eq(gc(5), ([(2, 0, 'class C1():', 'class'),
(4, 4, ' def __init__(self, a, b):', 'def')], 0))
# Only one 'def' is returned, not both at the same indent level.
eq(gc(10), ([(2, 0, 'class C1():', 'class'),
(7, 4, ' def compare(self):', 'def'),
(8, 8, ' if a > b:', 'if')], 0))
# With 'elif', also show the 'if' even though it's at the same level.
eq(gc(11), ([(2, 0, 'class C1():', 'class'),
(7, 4, ' def compare(self):', 'def'),
(8, 8, ' if a > b:', 'if'),
(10, 8, ' elif a < b:', 'elif')], 0))
# Set stop_line to not go back to first line in source code.
# Return includes stop_line.
eq(gc(11, stopline=2), ([(2, 0, 'class C1():', 'class'),
(7, 4, ' def compare(self):', 'def'),
(8, 8, ' if a > b:', 'if'),
(10, 8, ' elif a < b:', 'elif')], 0))
eq(gc(11, stopline=3), ([(7, 4, ' def compare(self):', 'def'),
(8, 8, ' if a > b:', 'if'),
(10, 8, ' elif a < b:', 'elif')], 4))
eq(gc(11, stopline=8), ([(8, 8, ' if a > b:', 'if'),
(10, 8, ' elif a < b:', 'elif')], 8))
# Set stop_indent to test indent level to stop at.
eq(gc(11, stopindent=4), ([(7, 4, ' def compare(self):', 'def'),
(8, 8, ' if a > b:', 'if'),
(10, 8, ' elif a < b:', 'elif')], 4))
# Check that the 'if' is included.
eq(gc(11, stopindent=8), ([(8, 8, ' if a > b:', 'if'),
(10, 8, ' elif a < b:', 'elif')], 8))
def test_update_code_context(self):
eq = self.assertEqual
cc = self.cc
# Ensure code context is active.
if not cc.context:
cc.toggle_code_context_event()
# Invoke update_code_context without scrolling - nothing happens.
self.assertIsNone(cc.update_code_context())
eq(cc.info, [(0, -1, '', False)])
eq(cc.topvisible, 1)
# Scroll down to line 1.
cc.text.yview(1)
cc.update_code_context()
eq(cc.info, [(0, -1, '', False)])
eq(cc.topvisible, 2)
eq(cc.context.get('1.0', 'end-1c'), '')
# Scroll down to line 2.
cc.text.yview(2)
cc.update_code_context()
eq(cc.info, [(0, -1, '', False), (2, 0, 'class C1():', 'class')])
eq(cc.topvisible, 3)
eq(cc.context.get('1.0', 'end-1c'), 'class C1():')
# Scroll down to line 3. Since it's a comment, nothing changes.
cc.text.yview(3)
cc.update_code_context()
eq(cc.info, [(0, -1, '', False), (2, 0, 'class C1():', 'class')])
eq(cc.topvisible, 4)
eq(cc.context.get('1.0', 'end-1c'), 'class C1():')
# Scroll down to line 4.
cc.text.yview(4)
cc.update_code_context()
eq(cc.info, [(0, -1, '', False),
(2, 0, 'class C1():', 'class'),
(4, 4, ' def __init__(self, a, b):', 'def')])
eq(cc.topvisible, 5)
eq(cc.context.get('1.0', 'end-1c'), 'class C1():\n'
' def __init__(self, a, b):')
# Scroll down to line 11. Last 'def' is removed.
cc.text.yview(11)
cc.update_code_context()
eq(cc.info, [(0, -1, '', False),
(2, 0, 'class C1():', 'class'),
(7, 4, ' def compare(self):', 'def'),
(8, 8, ' if a > b:', 'if'),
(10, 8, ' elif a < b:', 'elif')])
eq(cc.topvisible, 12)
eq(cc.context.get('1.0', 'end-1c'), 'class C1():\n'
' def compare(self):\n'
' if a > b:\n'
' elif a < b:')
# No scroll. No update, even though context_depth changed.
cc.update_code_context()
cc.context_depth = 1
eq(cc.info, [(0, -1, '', False),
(2, 0, 'class C1():', 'class'),
(7, 4, ' def compare(self):', 'def'),
(8, 8, ' if a > b:', 'if'),
(10, 8, ' elif a < b:', 'elif')])
eq(cc.topvisible, 12)
eq(cc.context.get('1.0', 'end-1c'), 'class C1():\n'
' def compare(self):\n'
' if a > b:\n'
' elif a < b:')
# Scroll up.
cc.text.yview(5)
cc.update_code_context()
eq(cc.info, [(0, -1, '', False),
(2, 0, 'class C1():', 'class'),
(4, 4, ' def __init__(self, a, b):', 'def')])
eq(cc.topvisible, 6)
# context_depth is 1.
eq(cc.context.get('1.0', 'end-1c'), ' def __init__(self, a, b):')
def test_jumptoline(self):
eq = self.assertEqual
cc = self.cc
jump = cc.jumptoline
if not cc.context:
cc.toggle_code_context_event()
# Empty context.
cc.text.yview(f'{2}.0')
cc.update_code_context()
eq(cc.topvisible, 2)
cc.context.mark_set('insert', '1.5')
jump()
eq(cc.topvisible, 1)
# 4 lines of context showing.
cc.text.yview(f'{12}.0')
cc.update_code_context()
eq(cc.topvisible, 12)
cc.context.mark_set('insert', '3.0')
jump()
eq(cc.topvisible, 8)
# More context lines than limit.
cc.context_depth = 2
cc.text.yview(f'{12}.0')
cc.update_code_context()
eq(cc.topvisible, 12)
cc.context.mark_set('insert', '1.0')
jump()
eq(cc.topvisible, 8)
@mock.patch.object(codecontext.CodeContext, 'update_code_context')
def test_timer_event(self, mock_update):
# Ensure code context is not active.
if self.cc.context:
self.cc.toggle_code_context_event()
self.cc.timer_event()
mock_update.assert_not_called()
# Activate code context.
self.cc.toggle_code_context_event()
self.cc.timer_event()
mock_update.assert_called()
def test_config_timer_event(self):
eq = self.assertEqual
cc = self.cc
save_font = cc.text['font']
save_colors = codecontext.CodeContext.colors
test_font = 'FakeFont'
test_colors = {'background': '#222222', 'foreground': '#ffff00'}
# Ensure code context is not active.
if cc.context:
cc.toggle_code_context_event()
# Nothing updates on inactive code context.
cc.text['font'] = test_font
codecontext.CodeContext.colors = test_colors
cc.config_timer_event()
eq(cc.textfont, save_font)
eq(cc.contextcolors, save_colors)
# Activate code context, but no change to font or color.
cc.toggle_code_context_event()
cc.text['font'] = save_font
codecontext.CodeContext.colors = save_colors
cc.config_timer_event()
eq(cc.textfont, save_font)
eq(cc.contextcolors, save_colors)
eq(cc.context['font'], save_font)
eq(cc.context['background'], save_colors['background'])
eq(cc.context['foreground'], save_colors['foreground'])
# Active code context, change font.
cc.text['font'] = test_font
cc.config_timer_event()
eq(cc.textfont, test_font)
eq(cc.contextcolors, save_colors)
eq(cc.context['font'], test_font)
eq(cc.context['background'], save_colors['background'])
eq(cc.context['foreground'], save_colors['foreground'])
# Active code context, change color.
cc.text['font'] = save_font
codecontext.CodeContext.colors = test_colors
cc.config_timer_event()
eq(cc.textfont, save_font)
eq(cc.contextcolors, test_colors)
eq(cc.context['font'], save_font)
eq(cc.context['background'], test_colors['background'])
eq(cc.context['foreground'], test_colors['foreground'])
codecontext.CodeContext.colors = save_colors
cc.config_timer_event()
class HelperFunctionText(unittest.TestCase):
def test_get_spaces_firstword(self):
get = codecontext.get_spaces_firstword
test_lines = (
(' first word', (' ', 'first')),
('\tfirst word', ('\t', 'first')),
(' \u19D4\u19D2: ', (' ', '\u19D4\u19D2')),
('no spaces', ('', 'no')),
('', ('', '')),
('# TEST COMMENT', ('', '')),
(' (continuation)', (' ', ''))
)
for line, expected_output in test_lines:
self.assertEqual(get(line), expected_output)
# Send the pattern in the call.
self.assertEqual(get(' (continuation)',
c=re.compile(r'^(\s*)([^\s]*)')),
(' ', '(continuation)'))
def test_get_line_info(self):
eq = self.assertEqual
gli = codecontext.get_line_info
lines = code_sample.splitlines()
# Line 1 is not a BLOCKOPENER.
eq(gli(lines[0]), (codecontext.INFINITY, '', False))
# Line 2 is a BLOCKOPENER without an indent.
eq(gli(lines[1]), (0, 'class C1():', 'class'))
# Line 3 is not a BLOCKOPENER and does not return the indent level.
eq(gli(lines[2]), (codecontext.INFINITY, ' # Class comment.', False))
# Line 4 is a BLOCKOPENER and is indented.
eq(gli(lines[3]), (4, ' def __init__(self, a, b):', 'def'))
# Line 8 is a different BLOCKOPENER and is indented.
eq(gli(lines[7]), (8, ' if a > b:', 'if'))
# Test tab.
eq(gli('\tif a == b:'), (1, '\tif a == b:', 'if'))
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,53 @@
"Test colorizer, coverage 25%."
from idlelib import colorizer
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)

View file

@ -0,0 +1,822 @@
"""Test config, coverage 93%.
(100% for IdleConfParser, IdleUserConfParser*, ConfigChanges).
* Exception is OSError clause in Save method.
Much of IdleConf is also exercised by ConfigDialog and test_configdialog.
"""
from idlelib import config
import sys
import os
import tempfile
from test.support import captured_stderr, findfile
import unittest
from unittest import mock
import idlelib
from idlelib.idle_test.mock_idle import Func
# 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
# due to getting '' as the filename when created.
idleConf = config.idleConf
usercfg = idleConf.userCfg
testcfg = {}
usermain = testcfg['main'] = config.IdleUserConfParser('')
userhigh = testcfg['highlight'] = config.IdleUserConfParser('')
userkeys = testcfg['keys'] = config.IdleUserConfParser('')
userextn = testcfg['extensions'] = config.IdleUserConfParser('')
def setUpModule():
idleConf.userCfg = testcfg
idlelib.testing = True
def tearDownModule():
idleConf.userCfg = usercfg
idlelib.testing = False
class IdleConfParserTest(unittest.TestCase):
"""Test that IdleConfParser works"""
config = """
[one]
one = false
two = true
three = 10
[two]
one = a string
two = true
three = false
"""
def test_get(self):
parser = config.IdleConfParser('')
parser.read_string(self.config)
eq = self.assertEqual
# Test with type argument.
self.assertIs(parser.Get('one', 'one', type='bool'), False)
self.assertIs(parser.Get('one', 'two', type='bool'), True)
eq(parser.Get('one', 'three', type='int'), 10)
eq(parser.Get('two', 'one'), 'a string')
self.assertIs(parser.Get('two', 'two', type='bool'), True)
self.assertIs(parser.Get('two', 'three', type='bool'), False)
# Test without type should fallback to string.
eq(parser.Get('two', 'two'), 'true')
eq(parser.Get('two', 'three'), 'false')
# If option not exist, should return None, or default.
self.assertIsNone(parser.Get('not', 'exist'))
eq(parser.Get('not', 'exist', default='DEFAULT'), 'DEFAULT')
def test_get_option_list(self):
parser = config.IdleConfParser('')
parser.read_string(self.config)
get_list = parser.GetOptionList
self.assertCountEqual(get_list('one'), ['one', 'two', 'three'])
self.assertCountEqual(get_list('two'), ['one', 'two', 'three'])
self.assertEqual(get_list('not exist'), [])
def test_load_nothing(self):
parser = config.IdleConfParser('')
parser.Load()
self.assertEqual(parser.sections(), [])
def test_load_file(self):
# Borrow test/cfgparser.1 from test_configparser.
config_path = findfile('cfgparser.1')
parser = config.IdleConfParser(config_path)
parser.Load()
self.assertEqual(parser.Get('Foo Bar', 'foo'), 'newbar')
self.assertEqual(parser.GetOptionList('Foo Bar'), ['foo'])
class IdleUserConfParserTest(unittest.TestCase):
"""Test that IdleUserConfParser works"""
def new_parser(self, path=''):
return config.IdleUserConfParser(path)
def test_set_option(self):
parser = self.new_parser()
parser.add_section('Foo')
# Setting new option in existing section should return True.
self.assertTrue(parser.SetOption('Foo', 'bar', 'true'))
# Setting existing option with same value should return False.
self.assertFalse(parser.SetOption('Foo', 'bar', 'true'))
# Setting exiting option with new value should return True.
self.assertTrue(parser.SetOption('Foo', 'bar', 'false'))
self.assertEqual(parser.Get('Foo', 'bar'), 'false')
# Setting option in new section should create section and return True.
self.assertTrue(parser.SetOption('Bar', 'bar', 'true'))
self.assertCountEqual(parser.sections(), ['Bar', 'Foo'])
self.assertEqual(parser.Get('Bar', 'bar'), 'true')
def test_remove_option(self):
parser = self.new_parser()
parser.AddSection('Foo')
parser.SetOption('Foo', 'bar', 'true')
self.assertTrue(parser.RemoveOption('Foo', 'bar'))
self.assertFalse(parser.RemoveOption('Foo', 'bar'))
self.assertFalse(parser.RemoveOption('Not', 'Exist'))
def test_add_section(self):
parser = self.new_parser()
self.assertEqual(parser.sections(), [])
# Should not add duplicate section.
# Configparser raises DuplicateError, IdleParser not.
parser.AddSection('Foo')
parser.AddSection('Foo')
parser.AddSection('Bar')
self.assertCountEqual(parser.sections(), ['Bar', 'Foo'])
def test_remove_empty_sections(self):
parser = self.new_parser()
parser.AddSection('Foo')
parser.AddSection('Bar')
parser.SetOption('Idle', 'name', 'val')
self.assertCountEqual(parser.sections(), ['Bar', 'Foo', 'Idle'])
parser.RemoveEmptySections()
self.assertEqual(parser.sections(), ['Idle'])
def test_is_empty(self):
parser = self.new_parser()
parser.AddSection('Foo')
parser.AddSection('Bar')
self.assertTrue(parser.IsEmpty())
self.assertEqual(parser.sections(), [])
parser.SetOption('Foo', 'bar', 'false')
parser.AddSection('Bar')
self.assertFalse(parser.IsEmpty())
self.assertCountEqual(parser.sections(), ['Foo'])
def test_remove_file(self):
with tempfile.TemporaryDirectory() as tdir:
path = os.path.join(tdir, 'test.cfg')
parser = self.new_parser(path)
parser.RemoveFile() # Should not raise exception.
parser.AddSection('Foo')
parser.SetOption('Foo', 'bar', 'true')
parser.Save()
self.assertTrue(os.path.exists(path))
parser.RemoveFile()
self.assertFalse(os.path.exists(path))
def test_save(self):
with tempfile.TemporaryDirectory() as tdir:
path = os.path.join(tdir, 'test.cfg')
parser = self.new_parser(path)
parser.AddSection('Foo')
parser.SetOption('Foo', 'bar', 'true')
# Should save to path when config is not empty.
self.assertFalse(os.path.exists(path))
parser.Save()
self.assertTrue(os.path.exists(path))
# Should remove the file from disk when config is empty.
parser.remove_section('Foo')
parser.Save()
self.assertFalse(os.path.exists(path))
class IdleConfTest(unittest.TestCase):
"""Test for idleConf"""
@classmethod
def setUpClass(cls):
cls.config_string = {}
conf = config.IdleConf(_utest=True)
if __name__ != '__main__':
idle_dir = os.path.dirname(__file__)
else:
idle_dir = os.path.abspath(sys.path[0])
for ctype in conf.config_types:
config_path = os.path.join(idle_dir, '../config-%s.def' % ctype)
with open(config_path, 'r') as f:
cls.config_string[ctype] = f.read()
cls.orig_warn = config._warn
config._warn = Func()
@classmethod
def tearDownClass(cls):
config._warn = cls.orig_warn
def new_config(self, _utest=False):
return config.IdleConf(_utest=_utest)
def mock_config(self):
"""Return a mocked idleConf
Both default and user config used the same config-*.def
"""
conf = config.IdleConf(_utest=True)
for ctype in conf.config_types:
conf.defaultCfg[ctype] = config.IdleConfParser('')
conf.defaultCfg[ctype].read_string(self.config_string[ctype])
conf.userCfg[ctype] = config.IdleUserConfParser('')
conf.userCfg[ctype].read_string(self.config_string[ctype])
return conf
@unittest.skipIf(sys.platform.startswith('win'), 'this is test for unix system')
def test_get_user_cfg_dir_unix(self):
"Test to get user config directory under unix"
conf = self.new_config(_utest=True)
# Check normal way should success
with mock.patch('os.path.expanduser', return_value='/home/foo'):
with mock.patch('os.path.exists', return_value=True):
self.assertEqual(conf.GetUserCfgDir(), '/home/foo/.idlerc')
# Check os.getcwd should success
with mock.patch('os.path.expanduser', return_value='~'):
with mock.patch('os.getcwd', return_value='/home/foo/cpython'):
with mock.patch('os.mkdir'):
self.assertEqual(conf.GetUserCfgDir(),
'/home/foo/cpython/.idlerc')
# Check user dir not exists and created failed should raise SystemExit
with mock.patch('os.path.join', return_value='/path/not/exists'):
with self.assertRaises(SystemExit):
with self.assertRaises(FileNotFoundError):
conf.GetUserCfgDir()
@unittest.skipIf(not sys.platform.startswith('win'), 'this is test for Windows system')
def test_get_user_cfg_dir_windows(self):
"Test to get user config directory under Windows"
conf = self.new_config(_utest=True)
# Check normal way should success
with mock.patch('os.path.expanduser', return_value='C:\\foo'):
with mock.patch('os.path.exists', return_value=True):
self.assertEqual(conf.GetUserCfgDir(), 'C:\\foo\\.idlerc')
# Check os.getcwd should success
with mock.patch('os.path.expanduser', return_value='~'):
with mock.patch('os.getcwd', return_value='C:\\foo\\cpython'):
with mock.patch('os.mkdir'):
self.assertEqual(conf.GetUserCfgDir(),
'C:\\foo\\cpython\\.idlerc')
# Check user dir not exists and created failed should raise SystemExit
with mock.patch('os.path.join', return_value='/path/not/exists'):
with self.assertRaises(SystemExit):
with self.assertRaises(FileNotFoundError):
conf.GetUserCfgDir()
def test_create_config_handlers(self):
conf = self.new_config(_utest=True)
# Mock out idle_dir
idle_dir = '/home/foo'
with mock.patch.dict({'__name__': '__foo__'}):
with mock.patch('os.path.dirname', return_value=idle_dir):
conf.CreateConfigHandlers()
# Check keys are equal
self.assertCountEqual(conf.defaultCfg.keys(), conf.config_types)
self.assertCountEqual(conf.userCfg.keys(), conf.config_types)
# Check conf parser are correct type
for default_parser in conf.defaultCfg.values():
self.assertIsInstance(default_parser, config.IdleConfParser)
for user_parser in conf.userCfg.values():
self.assertIsInstance(user_parser, config.IdleUserConfParser)
# Check config path are correct
for config_type, parser in conf.defaultCfg.items():
self.assertEqual(parser.file,
os.path.join(idle_dir, 'config-%s.def' % config_type))
for config_type, parser in conf.userCfg.items():
self.assertEqual(parser.file,
os.path.join(conf.userdir, 'config-%s.cfg' % config_type))
def test_load_cfg_files(self):
conf = self.new_config(_utest=True)
# Borrow test/cfgparser.1 from test_configparser.
config_path = findfile('cfgparser.1')
conf.defaultCfg['foo'] = config.IdleConfParser(config_path)
conf.userCfg['foo'] = config.IdleUserConfParser(config_path)
# Load all config from path
conf.LoadCfgFiles()
eq = self.assertEqual
# Check defaultCfg is loaded
eq(conf.defaultCfg['foo'].Get('Foo Bar', 'foo'), 'newbar')
eq(conf.defaultCfg['foo'].GetOptionList('Foo Bar'), ['foo'])
# Check userCfg is loaded
eq(conf.userCfg['foo'].Get('Foo Bar', 'foo'), 'newbar')
eq(conf.userCfg['foo'].GetOptionList('Foo Bar'), ['foo'])
def test_save_user_cfg_files(self):
conf = self.mock_config()
with mock.patch('idlelib.config.IdleUserConfParser.Save') as m:
conf.SaveUserCfgFiles()
self.assertEqual(m.call_count, len(conf.userCfg))
def test_get_option(self):
conf = self.mock_config()
eq = self.assertEqual
eq(conf.GetOption('main', 'EditorWindow', 'width'), '80')
eq(conf.GetOption('main', 'EditorWindow', 'width', type='int'), 80)
with mock.patch('idlelib.config._warn') as _warn:
eq(conf.GetOption('main', 'EditorWindow', 'font', type='int'), None)
eq(conf.GetOption('main', 'EditorWindow', 'NotExists'), None)
eq(conf.GetOption('main', 'EditorWindow', 'NotExists', default='NE'), 'NE')
eq(_warn.call_count, 4)
def test_set_option(self):
conf = self.mock_config()
conf.SetOption('main', 'Foo', 'bar', 'newbar')
self.assertEqual(conf.GetOption('main', 'Foo', 'bar'), 'newbar')
def test_get_section_list(self):
conf = self.mock_config()
self.assertCountEqual(
conf.GetSectionList('default', 'main'),
['General', 'EditorWindow', 'PyShell', 'Indent', 'Theme',
'Keys', 'History', 'HelpFiles'])
self.assertCountEqual(
conf.GetSectionList('user', 'main'),
['General', 'EditorWindow', 'PyShell', 'Indent', 'Theme',
'Keys', 'History', 'HelpFiles'])
with self.assertRaises(config.InvalidConfigSet):
conf.GetSectionList('foobar', 'main')
with self.assertRaises(config.InvalidConfigType):
conf.GetSectionList('default', 'notexists')
def test_get_highlight(self):
conf = self.mock_config()
eq = self.assertEqual
eq(conf.GetHighlight('IDLE Classic', 'normal'), {'foreground': '#000000',
'background': '#ffffff'})
eq(conf.GetHighlight('IDLE Classic', 'normal', 'fg'), '#000000')
eq(conf.GetHighlight('IDLE Classic', 'normal', 'bg'), '#ffffff')
with self.assertRaises(config.InvalidFgBg):
conf.GetHighlight('IDLE Classic', 'normal', 'fb')
# Test cursor (this background should be normal-background)
eq(conf.GetHighlight('IDLE Classic', 'cursor'), {'foreground': 'black',
'background': '#ffffff'})
# Test get user themes
conf.SetOption('highlight', 'Foobar', 'normal-foreground', '#747474')
conf.SetOption('highlight', 'Foobar', 'normal-background', '#171717')
with mock.patch('idlelib.config._warn'):
eq(conf.GetHighlight('Foobar', 'normal'), {'foreground': '#747474',
'background': '#171717'})
def test_get_theme_dict(self):
"XXX: NOT YET DONE"
conf = self.mock_config()
# These two should be the same
self.assertEqual(
conf.GetThemeDict('default', 'IDLE Classic'),
conf.GetThemeDict('user', 'IDLE Classic'))
with self.assertRaises(config.InvalidTheme):
conf.GetThemeDict('bad', 'IDLE Classic')
def test_get_current_theme_and_keys(self):
conf = self.mock_config()
self.assertEqual(conf.CurrentTheme(), conf.current_colors_and_keys('Theme'))
self.assertEqual(conf.CurrentKeys(), conf.current_colors_and_keys('Keys'))
def test_current_colors_and_keys(self):
conf = self.mock_config()
self.assertEqual(conf.current_colors_and_keys('Theme'), 'IDLE Classic')
def test_default_keys(self):
current_platform = sys.platform
conf = self.new_config(_utest=True)
sys.platform = 'win32'
self.assertEqual(conf.default_keys(), 'IDLE Classic Windows')
sys.platform = 'darwin'
self.assertEqual(conf.default_keys(), 'IDLE Classic OSX')
sys.platform = 'some-linux'
self.assertEqual(conf.default_keys(), 'IDLE Modern Unix')
# Restore platform
sys.platform = current_platform
def test_get_extensions(self):
userextn.read_string('''
[ZzDummy]
enable = True
[DISABLE]
enable = False
''')
eq = self.assertEqual
iGE = idleConf.GetExtensions
eq(iGE(shell_only=True), [])
eq(iGE(), ['ZzDummy'])
eq(iGE(editor_only=True), ['ZzDummy'])
eq(iGE(active_only=False), ['ZzDummy', 'DISABLE'])
eq(iGE(active_only=False, editor_only=True), ['ZzDummy', 'DISABLE'])
userextn.remove_section('ZzDummy')
userextn.remove_section('DISABLE')
def test_remove_key_bind_names(self):
conf = self.mock_config()
self.assertCountEqual(
conf.RemoveKeyBindNames(conf.GetSectionList('default', 'extensions')),
['AutoComplete', 'CodeContext', 'FormatParagraph', 'ParenMatch', 'ZzDummy'])
def test_get_extn_name_for_event(self):
userextn.read_string('''
[ZzDummy]
enable = True
''')
eq = self.assertEqual
eq(idleConf.GetExtnNameForEvent('z-in'), 'ZzDummy')
eq(idleConf.GetExtnNameForEvent('z-out'), None)
userextn.remove_section('ZzDummy')
def test_get_extension_keys(self):
userextn.read_string('''
[ZzDummy]
enable = True
''')
self.assertEqual(idleConf.GetExtensionKeys('ZzDummy'),
{'<<z-in>>': ['<Control-Shift-KeyRelease-Insert>']})
userextn.remove_section('ZzDummy')
# need option key test
## key = ['<Option-Key-2>'] if sys.platform == 'darwin' else ['<Alt-Key-2>']
## eq(conf.GetExtensionKeys('ZoomHeight'), {'<<zoom-height>>': key})
def test_get_extension_bindings(self):
userextn.read_string('''
[ZzDummy]
enable = True
''')
eq = self.assertEqual
iGEB = idleConf.GetExtensionBindings
eq(iGEB('NotExists'), {})
expect = {'<<z-in>>': ['<Control-Shift-KeyRelease-Insert>'],
'<<z-out>>': ['<Control-Shift-KeyRelease-Delete>']}
eq(iGEB('ZzDummy'), expect)
userextn.remove_section('ZzDummy')
def test_get_keybinding(self):
conf = self.mock_config()
eq = self.assertEqual
eq(conf.GetKeyBinding('IDLE Modern Unix', '<<copy>>'),
['<Control-Shift-Key-C>', '<Control-Key-Insert>'])
eq(conf.GetKeyBinding('IDLE Classic Unix', '<<copy>>'),
['<Alt-Key-w>', '<Meta-Key-w>'])
eq(conf.GetKeyBinding('IDLE Classic Windows', '<<copy>>'),
['<Control-Key-c>', '<Control-Key-C>'])
eq(conf.GetKeyBinding('IDLE Classic Mac', '<<copy>>'), ['<Command-Key-c>'])
eq(conf.GetKeyBinding('IDLE Classic OSX', '<<copy>>'), ['<Command-Key-c>'])
# Test keybinding not exists
eq(conf.GetKeyBinding('NOT EXISTS', '<<copy>>'), [])
eq(conf.GetKeyBinding('IDLE Modern Unix', 'NOT EXISTS'), [])
def test_get_current_keyset(self):
current_platform = sys.platform
conf = self.mock_config()
# Ensure that platform isn't darwin
sys.platform = 'some-linux'
self.assertEqual(conf.GetCurrentKeySet(), conf.GetKeySet(conf.CurrentKeys()))
# This should not be the same, since replace <Alt- to <Option-.
# Above depended on config-extensions.def having Alt keys,
# which is no longer true.
# sys.platform = 'darwin'
# self.assertNotEqual(conf.GetCurrentKeySet(), conf.GetKeySet(conf.CurrentKeys()))
# Restore platform
sys.platform = current_platform
def test_get_keyset(self):
conf = self.mock_config()
# Conflic with key set, should be disable to ''
conf.defaultCfg['extensions'].add_section('Foobar')
conf.defaultCfg['extensions'].add_section('Foobar_cfgBindings')
conf.defaultCfg['extensions'].set('Foobar', 'enable', 'True')
conf.defaultCfg['extensions'].set('Foobar_cfgBindings', 'newfoo', '<Key-F3>')
self.assertEqual(conf.GetKeySet('IDLE Modern Unix')['<<newfoo>>'], '')
def test_is_core_binding(self):
# XXX: Should move out the core keys to config file or other place
conf = self.mock_config()
self.assertTrue(conf.IsCoreBinding('copy'))
self.assertTrue(conf.IsCoreBinding('cut'))
self.assertTrue(conf.IsCoreBinding('del-word-right'))
self.assertFalse(conf.IsCoreBinding('not-exists'))
def test_extra_help_source_list(self):
# Test GetExtraHelpSourceList and GetAllExtraHelpSourcesList in same
# place to prevent prepare input data twice.
conf = self.mock_config()
# Test default with no extra help source
self.assertEqual(conf.GetExtraHelpSourceList('default'), [])
self.assertEqual(conf.GetExtraHelpSourceList('user'), [])
with self.assertRaises(config.InvalidConfigSet):
self.assertEqual(conf.GetExtraHelpSourceList('bad'), [])
self.assertCountEqual(
conf.GetAllExtraHelpSourcesList(),
conf.GetExtraHelpSourceList('default') + conf.GetExtraHelpSourceList('user'))
# Add help source to user config
conf.userCfg['main'].SetOption('HelpFiles', '4', 'Python;https://python.org') # This is bad input
conf.userCfg['main'].SetOption('HelpFiles', '3', 'Python:https://python.org') # This is bad input
conf.userCfg['main'].SetOption('HelpFiles', '2', 'Pillow;https://pillow.readthedocs.io/en/latest/')
conf.userCfg['main'].SetOption('HelpFiles', '1', 'IDLE;C:/Programs/Python36/Lib/idlelib/help.html')
self.assertEqual(conf.GetExtraHelpSourceList('user'),
[('IDLE', 'C:/Programs/Python36/Lib/idlelib/help.html', '1'),
('Pillow', 'https://pillow.readthedocs.io/en/latest/', '2'),
('Python', 'https://python.org', '4')])
self.assertCountEqual(
conf.GetAllExtraHelpSourcesList(),
conf.GetExtraHelpSourceList('default') + conf.GetExtraHelpSourceList('user'))
def test_get_font(self):
from test.support import requires
from tkinter import Tk
from tkinter.font import Font
conf = self.mock_config()
requires('gui')
root = Tk()
root.withdraw()
f = Font.actual(Font(name='TkFixedFont', exists=True, root=root))
self.assertEqual(
conf.GetFont(root, 'main', 'EditorWindow'),
(f['family'], 10 if f['size'] <= 0 else f['size'], f['weight']))
# Cleanup root
root.destroy()
del root
def test_get_core_keys(self):
conf = self.mock_config()
eq = self.assertEqual
eq(conf.GetCoreKeys()['<<center-insert>>'], ['<Control-l>'])
eq(conf.GetCoreKeys()['<<copy>>'], ['<Control-c>', '<Control-C>'])
eq(conf.GetCoreKeys()['<<history-next>>'], ['<Alt-n>'])
eq(conf.GetCoreKeys('IDLE Classic Windows')['<<center-insert>>'],
['<Control-Key-l>', '<Control-Key-L>'])
eq(conf.GetCoreKeys('IDLE Classic OSX')['<<copy>>'], ['<Command-Key-c>'])
eq(conf.GetCoreKeys('IDLE Classic Unix')['<<history-next>>'],
['<Alt-Key-n>', '<Meta-Key-n>'])
eq(conf.GetCoreKeys('IDLE Modern Unix')['<<history-next>>'],
['<Alt-Key-n>', '<Meta-Key-n>'])
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 ChangesTest(unittest.TestCase):
empty = {'main':{}, 'highlight':{}, 'keys':{}, 'extensions':{}}
def load(self): # Test_add_option verifies that this works.
changes = self.changes
changes.add_option('main', 'Msec', 'mitem', 'mval')
changes.add_option('highlight', 'Hsec', 'hitem', 'hval')
changes.add_option('keys', 'Ksec', 'kitem', 'kval')
return changes
loaded = {'main': {'Msec': {'mitem': 'mval'}},
'highlight': {'Hsec': {'hitem': 'hval'}},
'keys': {'Ksec': {'kitem':'kval'}},
'extensions': {}}
def setUp(self):
self.changes = config.ConfigChanges()
def test_init(self):
self.assertEqual(self.changes, self.empty)
def test_add_option(self):
changes = self.load()
self.assertEqual(changes, self.loaded)
changes.add_option('main', 'Msec', 'mitem', 'mval')
self.assertEqual(changes, self.loaded)
def test_save_option(self): # Static function does not touch changes.
save_option = self.changes.save_option
self.assertTrue(save_option('main', 'Indent', 'what', '0'))
self.assertFalse(save_option('main', 'Indent', 'what', '0'))
self.assertEqual(usermain['Indent']['what'], '0')
self.assertTrue(save_option('main', 'Indent', 'use-spaces', '0'))
self.assertEqual(usermain['Indent']['use-spaces'], '0')
self.assertTrue(save_option('main', 'Indent', 'use-spaces', '1'))
self.assertFalse(usermain.has_option('Indent', 'use-spaces'))
usermain.remove_section('Indent')
def test_save_added(self):
changes = self.load()
self.assertTrue(changes.save_all())
self.assertEqual(usermain['Msec']['mitem'], 'mval')
self.assertEqual(userhigh['Hsec']['hitem'], 'hval')
self.assertEqual(userkeys['Ksec']['kitem'], 'kval')
changes.add_option('main', 'Msec', 'mitem', 'mval')
self.assertFalse(changes.save_all())
usermain.remove_section('Msec')
userhigh.remove_section('Hsec')
userkeys.remove_section('Ksec')
def test_save_help(self):
# Any change to HelpFiles overwrites entire section.
changes = self.changes
changes.save_option('main', 'HelpFiles', 'IDLE', 'idledoc')
changes.add_option('main', 'HelpFiles', 'ELDI', 'codeldi')
changes.save_all()
self.assertFalse(usermain.has_option('HelpFiles', 'IDLE'))
self.assertTrue(usermain.has_option('HelpFiles', 'ELDI'))
def test_save_default(self): # Cover 2nd and 3rd false branches.
changes = self.changes
changes.add_option('main', 'Indent', 'use-spaces', '1')
# save_option returns False; cfg_type_changed remains False.
# TODO: test that save_all calls usercfg Saves.
def test_delete_section(self):
changes = self.load()
changes.delete_section('main', 'fake') # Test no exception.
self.assertEqual(changes, self.loaded) # Test nothing deleted.
for cfgtype, section in (('main', 'Msec'), ('keys', 'Ksec')):
testcfg[cfgtype].SetOption(section, 'name', 'value')
changes.delete_section(cfgtype, section)
with self.assertRaises(KeyError):
changes[cfgtype][section] # Test section gone from changes
testcfg[cfgtype][section] # and from mock userCfg.
# TODO test for save call.
def test_clear(self):
changes = self.load()
changes.clear()
self.assertEqual(changes, self.empty)
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)

View file

@ -0,0 +1,99 @@
"Test config_key, coverage 75%"
from idlelib import config_key
from test.support import requires
import unittest
from tkinter import Tk
from idlelib.idle_test.mock_idle import Func
from idlelib.idle_test.mock_tk import Mbox_func
class ValidationTest(unittest.TestCase):
"Test validation methods: OK, KeysOK, bind_ok."
class Validator(config_key.GetKeysDialog):
def __init__(self, *args, **kwargs):
config_key.GetKeysDialog.__init__(self, *args, **kwargs)
class listKeysFinal:
get = Func()
self.listKeysFinal = listKeysFinal
GetModifiers = Func()
showerror = Mbox_func()
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.root.withdraw()
keylist = [['<Key-F12>'], ['<Control-Key-x>', '<Control-Key-X>']]
cls.dialog = cls.Validator(
cls.root, 'Title', '<<Test>>', keylist, _utest=True)
@classmethod
def tearDownClass(cls):
cls.dialog.Cancel()
cls.root.update_idletasks()
cls.root.destroy()
del cls.dialog, cls.root
def setUp(self):
self.dialog.showerror.message = ''
# A test that needs a particular final key value should set it.
# A test that sets a non-blank modifier list should reset it to [].
def test_ok_empty(self):
self.dialog.keyString.set(' ')
self.dialog.OK()
self.assertEqual(self.dialog.result, '')
self.assertEqual(self.dialog.showerror.message, 'No key specified.')
def test_ok_good(self):
self.dialog.keyString.set('<Key-F11>')
self.dialog.listKeysFinal.get.result = 'F11'
self.dialog.OK()
self.assertEqual(self.dialog.result, '<Key-F11>')
self.assertEqual(self.dialog.showerror.message, '')
def test_keys_no_ending(self):
self.assertFalse(self.dialog.KeysOK('<Control-Shift'))
self.assertIn('Missing the final', self.dialog.showerror.message)
def test_keys_no_modifier_bad(self):
self.dialog.listKeysFinal.get.result = 'A'
self.assertFalse(self.dialog.KeysOK('<Key-A>'))
self.assertIn('No modifier', self.dialog.showerror.message)
def test_keys_no_modifier_ok(self):
self.dialog.listKeysFinal.get.result = 'F11'
self.assertTrue(self.dialog.KeysOK('<Key-F11>'))
self.assertEqual(self.dialog.showerror.message, '')
def test_keys_shift_bad(self):
self.dialog.listKeysFinal.get.result = 'a'
self.dialog.GetModifiers.result = ['Shift']
self.assertFalse(self.dialog.KeysOK('<a>'))
self.assertIn('shift modifier', self.dialog.showerror.message)
self.dialog.GetModifiers.result = []
def test_keys_dup(self):
for mods, final, seq in (([], 'F12', '<Key-F12>'),
(['Control'], 'x', '<Control-Key-x>'),
(['Control'], 'X', '<Control-Key-X>')):
with self.subTest(m=mods, f=final, s=seq):
self.dialog.listKeysFinal.get.result = final
self.dialog.GetModifiers.result = mods
self.assertFalse(self.dialog.KeysOK(seq))
self.assertIn('already in use', self.dialog.showerror.message)
self.dialog.GetModifiers.result = []
def test_bind_ok(self):
self.assertTrue(self.dialog.bind_ok('<Control-Shift-Key-a>'))
self.assertEqual(self.dialog.showerror.message, '')
def test_bind_not_ok(self):
self.assertFalse(self.dialog.bind_ok('<Control-Shift>'))
self.assertIn('not accepted', self.dialog.showerror.message)
if __name__ == '__main__':
unittest.main(verbosity=2)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,29 @@
"Test debugger, coverage 19%"
from idlelib import debugger
import unittest
from test.support import requires
requires('gui')
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')
# Other classes are Idb, Debugger, and StackViewer.
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,29 @@
"Test debugger_r, coverage 30%."
from idlelib import debugger_r
import unittest
from test.support import requires
from tkinter import Tk
class Test(unittest.TestCase):
## @classmethod
## def setUpClass(cls):
## requires('gui')
## cls.root = Tk()
##
## @classmethod
## def tearDownClass(cls):
## cls.root.destroy()
## del cls.root
def test_init(self):
self.assertTrue(True) # Get coverage of import
# Classes GUIProxy, IdbAdapter, FrameProxy, CodeProxy, DictProxy,
# GUIAdapter, IdbProxy plus 7 module functions.
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,57 @@
"Test debugobj, coverage 40%."
from idlelib import debugobj
import unittest
class ObjectTreeItemTest(unittest.TestCase):
def test_init(self):
ti = debugobj.ObjectTreeItem('label', 22)
self.assertEqual(ti.labeltext, 'label')
self.assertEqual(ti.object, 22)
self.assertEqual(ti.setfunction, None)
class ClassTreeItemTest(unittest.TestCase):
def test_isexpandable(self):
ti = debugobj.ClassTreeItem('label', 0)
self.assertTrue(ti.IsExpandable())
class AtomicObjectTreeItemTest(unittest.TestCase):
def test_isexpandable(self):
ti = debugobj.AtomicObjectTreeItem('label', 0)
self.assertFalse(ti.IsExpandable())
class SequenceTreeItemTest(unittest.TestCase):
def test_isexpandable(self):
ti = debugobj.SequenceTreeItem('label', ())
self.assertFalse(ti.IsExpandable())
ti = debugobj.SequenceTreeItem('label', (1,))
self.assertTrue(ti.IsExpandable())
def test_keys(self):
ti = debugobj.SequenceTreeItem('label', 'abc')
self.assertEqual(list(ti.keys()), [0, 1, 2])
class DictTreeItemTest(unittest.TestCase):
def test_isexpandable(self):
ti = debugobj.DictTreeItem('label', {})
self.assertFalse(ti.IsExpandable())
ti = debugobj.DictTreeItem('label', {1:1})
self.assertTrue(ti.IsExpandable())
def test_keys(self):
ti = debugobj.DictTreeItem('label', {1:1, 0:0, 2:2})
self.assertEqual(ti.keys(), [0, 1, 2])
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,22 @@
"Test debugobj_r, coverage 56%."
from idlelib import debugobj_r
import unittest
class WrappedObjectTreeItemTest(unittest.TestCase):
def test_getattr(self):
ti = debugobj_r.WrappedObjectTreeItem(list)
self.assertEqual(ti.append, list.append)
class StubObjectTreeItemTest(unittest.TestCase):
def test_init(self):
ti = debugobj_r.StubObjectTreeItem('socket', 1111)
self.assertEqual(ti.sockio, 'socket')
self.assertEqual(ti.oid, 1111)
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,44 @@
"Test delegator, coverage 100%."
from idlelib.delegator import Delegator
import unittest
class DelegatorTest(unittest.TestCase):
def test_mydel(self):
# Test a simple use scenario.
# Initialize an int delegator.
mydel = Delegator(int)
self.assertIs(mydel.delegate, int)
self.assertEqual(mydel._Delegator__cache, set())
# Trying to access a non-attribute of int fails.
self.assertRaises(AttributeError, mydel.__getattr__, 'xyz')
# Add real int attribute 'bit_length' by accessing it.
bl = mydel.bit_length
self.assertIs(bl, int.bit_length)
self.assertIs(mydel.__dict__['bit_length'], int.bit_length)
self.assertEqual(mydel._Delegator__cache, {'bit_length'})
# Add attribute 'numerator'.
mydel.numerator
self.assertEqual(mydel._Delegator__cache, {'bit_length', 'numerator'})
# Delete 'numerator'.
del mydel.numerator
self.assertNotIn('numerator', mydel.__dict__)
# The current implementation leaves it in the name cache.
# self.assertIn('numerator', mydel._Delegator__cache)
# However, this is not required and not part of the specification
# Change delegate to float, first resetting the attributes.
mydel.setdelegate(float) # calls resetcache
self.assertNotIn('bit_length', mydel.__dict__)
self.assertEqual(mydel._Delegator__cache, set())
self.assertIs(mydel.delegate, float)
if __name__ == '__main__':
unittest.main(verbosity=2, exit=2)

View file

@ -0,0 +1,74 @@
'''Test (selected) IDLE Edit menu items.
Edit modules have their own test files
'''
from test.support import requires
requires('gui')
import tkinter as tk
from tkinter import ttk
import unittest
from idlelib import pyshell
class PasteTest(unittest.TestCase):
'''Test pasting into widgets that allow pasting.
On X11, replacing selections requires tk fix.
'''
@classmethod
def setUpClass(cls):
cls.root = root = tk.Tk()
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.tentry
cls.root.clipboard_clear()
cls.root.update_idletasks()
cls.root.destroy()
del cls.root
def test_paste_text(self):
"Test pasting into text with and without a selection."
text = self.text
for tag, ans in ('', 'onetwo\n'), ('sel', 'two\n'):
with self.subTest(tag=tag, ans=ans):
text.delete('1.0', 'end')
text.insert('1.0', 'one', tag)
text.event_generate('<<Paste>>')
self.assertEqual(text.get('1.0', 'end'), ans)
def test_paste_entry(self):
"Test pasting into an entry with and without a selection."
# 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."
# See note above for entry.
spin = self.spin
for end, ans in (0, 'onetwo'), ('end', 'two'):
with self.subTest(end=end, ans=ans):
spin.delete(0, 'end')
spin.insert(0, 'one')
spin.selection('range', 0, end) # see note
spin.event_generate('<<Paste>>')
self.assertEqual(spin.get(), ans)
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,46 @@
"Test editor, coverage 35%."
from idlelib import editor
import unittest
from test.support import requires
from tkinter import Tk
Editor = editor.EditorWindow
class EditorWindowTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.root.withdraw()
@classmethod
def tearDownClass(cls):
cls.root.update_idletasks()
for id in cls.root.tk.call('after', 'info'):
cls.root.after_cancel(id)
cls.root.destroy()
del cls.root
def test_init(self):
e = Editor(root=self.root)
self.assertEqual(e.root, self.root)
e._close()
class EditorFunctionTest(unittest.TestCase):
def test_filename_to_unicode(self):
func = Editor._filename_to_unicode
class dummy():
filesystemencoding = 'utf-8'
pairs = (('abc', 'abc'), ('a\U00011111c', 'a\ufffdc'),
(b'abc', 'abc'), (b'a\xf0\x91\x84\x91c', 'a\ufffdc'))
for inp, out in pairs:
self.assertEqual(func(dummy, inp), out)
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,33 @@
"Test filelist, coverage 19%."
from idlelib import filelist
import unittest
from test.support import requires
from tkinter import Tk
class FileListTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.root.withdraw()
@classmethod
def tearDownClass(cls):
cls.root.update_idletasks()
for id in cls.root.tk.call('after', 'info'):
cls.root.after_cancel(id)
cls.root.destroy()
del cls.root
def test_new_empty(self):
flist = filelist.FileList(self.root)
self.assertEqual(flist.root, self.root)
e = flist.new()
self.assertEqual(type(e), flist.EditorWindow)
e._close()
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,86 @@
""" !Changing this line will break Test_findfile.test_found!
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.
Currently only test grep_it, coverage 51%.
"""
from idlelib.grep import GrepDialog
import unittest
from test.support import captured_stdout
from idlelib.idle_test.mock_tk import Var
import re
class Dummy_searchengine:
'''GrepDialog.__init__ calls parent SearchDiabolBase which attaches the
passed in SearchEngine instance as attribute 'engine'. Only a few of the
many possible self.engine.x attributes are needed here.
'''
def getpat(self):
return self._pat
searchengine = Dummy_searchengine()
class Dummy_grep:
# Methods tested
#default_command = GrepDialog.default_command
grep_it = GrepDialog.grep_it
findfiles = GrepDialog.findfiles
# Other stuff needed
recvar = Var(False)
engine = searchengine
def close(self): # gui method
pass
grep = Dummy_grep()
class FindfilesTest(unittest.TestCase):
# findfiles is really a function, not a method, could be iterator
# test that filename return filename
# test that idlelib has many .py files
# test that recursive flag adds idle_test .py files
pass
class Grep_itTest(unittest.TestCase):
# Test captured reports with 0 and some hits.
# Should test file names, but Windows reports have mixed / and \ separators
# from incomplete replacement, so 'later'.
def report(self, pat):
grep.engine._pat = pat
with captured_stdout() as s:
grep.grep_it(re.compile(pat), __file__)
lines = s.getvalue().split('\n')
lines.pop() # remove bogus '' after last \n
return lines
def test_unfound(self):
pat = 'xyz*'*7
lines = self.report(pat)
self.assertEqual(len(lines), 2)
self.assertIn(pat, lines[0])
self.assertEqual(lines[1], 'No hits.')
def test_found(self):
pat = '""" !Changing this line will break Test_findfile.test_found!'
lines = self.report(pat)
self.assertEqual(len(lines), 5)
self.assertIn(pat, lines[0])
self.assertIn('py: 1:', lines[1]) # line number 1
self.assertIn('2', lines[3]) # hits found 2
self.assertTrue(lines[4].startswith('(Hint:'))
class Default_commandTest(unittest.TestCase):
# To write this, move outwin import to top of GrepDialog
# so it can be replaced by captured_stdout in class setup/teardown.
pass
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,34 @@
"Test help, coverage 87%."
from idlelib import help
import unittest
from test.support import requires
requires('gui')
from os.path import abspath, dirname, join
from tkinter import Tk
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)

View file

@ -0,0 +1,180 @@
"""Test help_about, coverage 100%.
help_about.build_bits branches on sys.platform='darwin'.
'100% combines coverage on Mac and others.
"""
from idlelib import help_about
import unittest
from test.support import requires, findfile
from tkinter import Tk, TclError
from idlelib.idle_test.mock_idle import Func
from idlelib.idle_test.mock_tk import Mbox_func
from idlelib import textview
import os.path
from platform import python_version
About = help_about.AboutDialog
class LiveDialogTest(unittest.TestCase):
"""Simulate user clicking buttons other than [Close].
Test that invoked textview has text from source.
"""
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.root.withdraw()
cls.dialog = About(cls.root, 'About IDLE', _utest=True)
@classmethod
def tearDownClass(cls):
del cls.dialog
cls.root.update_idletasks()
cls.root.destroy()
del cls.root
def test_build_bits(self):
self.assertIn(help_about.build_bits(), ('32', '64'))
def test_dialog_title(self):
"""Test about dialog title"""
self.assertEqual(self.dialog.title(), 'About IDLE')
def test_dialog_logo(self):
"""Test about dialog logo."""
path, file = os.path.split(self.dialog.icon_image['file'])
fn, ext = os.path.splitext(file)
self.assertEqual(fn, 'idle_48')
def test_printer_buttons(self):
"""Test buttons whose commands use printer function."""
dialog = self.dialog
button_sources = [(dialog.py_license, license, 'license'),
(dialog.py_copyright, copyright, 'copyright'),
(dialog.py_credits, credits, 'credits')]
for button, printer, name in button_sources:
with self.subTest(name=name):
printer._Printer__setup()
button.invoke()
get = dialog._current_textview.viewframe.textframe.text.get
lines = printer._Printer__lines
self.assertEqual(lines[0], get('1.0', '1.end'))
self.assertEqual(lines[1], get('2.0', '2.end'))
dialog._current_textview.destroy()
def test_file_buttons(self):
"""Test buttons that display files."""
dialog = self.dialog
button_sources = [(self.dialog.readme, 'README.txt', 'readme'),
(self.dialog.idle_news, 'NEWS.txt', 'news'),
(self.dialog.idle_credits, 'CREDITS.txt', 'credits')]
for button, filename, name in button_sources:
with self.subTest(name=name):
button.invoke()
fn = findfile(filename, subdir='idlelib')
get = dialog._current_textview.viewframe.textframe.text.get
with open(fn, encoding='utf-8') as f:
self.assertEqual(f.readline().strip(), get('1.0', '1.end'))
f.readline()
self.assertEqual(f.readline().strip(), get('3.0', '3.end'))
dialog._current_textview.destroy()
class DefaultTitleTest(unittest.TestCase):
"Test default title."
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.root.withdraw()
cls.dialog = About(cls.root, _utest=True)
@classmethod
def tearDownClass(cls):
del cls.dialog
cls.root.update_idletasks()
cls.root.destroy()
del cls.root
def test_dialog_title(self):
"""Test about dialog title"""
self.assertEqual(self.dialog.title(),
f'About IDLE {python_version()}'
f' ({help_about.build_bits()} bit)')
class CloseTest(unittest.TestCase):
"""Simulate user clicking [Close] button"""
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.root.withdraw()
cls.dialog = About(cls.root, 'About IDLE', _utest=True)
@classmethod
def tearDownClass(cls):
del cls.dialog
cls.root.update_idletasks()
cls.root.destroy()
del cls.root
def test_close(self):
self.assertEqual(self.dialog.winfo_class(), 'Toplevel')
self.dialog.button_ok.invoke()
with self.assertRaises(TclError):
self.dialog.winfo_class()
class Dummy_about_dialog():
# Dummy class for testing file display functions.
idle_credits = About.show_idle_credits
idle_readme = About.show_readme
idle_news = About.show_idle_news
# Called by the above
display_file_text = About.display_file_text
_utest = True
class DisplayFileTest(unittest.TestCase):
"""Test functions that display files.
While somewhat redundant with gui-based test_file_dialog,
these unit tests run on all buildbots, not just a few.
"""
dialog = Dummy_about_dialog()
@classmethod
def setUpClass(cls):
cls.orig_error = textview.showerror
cls.orig_view = textview.view_text
cls.error = Mbox_func()
cls.view = Func()
textview.showerror = cls.error
textview.view_text = cls.view
@classmethod
def tearDownClass(cls):
textview.showerror = cls.orig_error
textview.view_text = cls.orig_view
def test_file_display(self):
for handler in (self.dialog.idle_credits,
self.dialog.idle_readme,
self.dialog.idle_news):
self.error.message = ''
self.view.called = False
with self.subTest(handler=handler):
handler()
self.assertEqual(self.error.message, '')
self.assertEqual(self.view.called, True)
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,172 @@
" Test history, coverage 100%."
from idlelib.history import History
import unittest
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.config import idleConf
line1 = 'a = 7'
line2 = 'b = a'
class StoreTest(unittest.TestCase):
'''Tests History.__init__ and History.store with mock Text'''
@classmethod
def setUpClass(cls):
cls.text = mkText()
cls.history = History(cls.text)
def tearDown(self):
self.text.delete('1.0', 'end')
self.history.history = []
def test_init(self):
self.assertIs(self.history.text, self.text)
self.assertEqual(self.history.history, [])
self.assertIsNone(self.history.prefix)
self.assertIsNone(self.history.pointer)
self.assertEqual(self.history.cyclic,
idleConf.GetOption("main", "History", "cyclic", 1, "bool"))
def test_store_short(self):
self.history.store('a')
self.assertEqual(self.history.history, [])
self.history.store(' a ')
self.assertEqual(self.history.history, [])
def test_store_dup(self):
self.history.store(line1)
self.assertEqual(self.history.history, [line1])
self.history.store(line2)
self.assertEqual(self.history.history, [line1, line2])
self.history.store(line1)
self.assertEqual(self.history.history, [line2, line1])
def test_store_reset(self):
self.history.prefix = line1
self.history.pointer = 0
self.history.store(line2)
self.assertIsNone(self.history.prefix)
self.assertIsNone(self.history.pointer)
class TextWrapper:
def __init__(self, master):
self.text = tkText(master=master)
self._bell = False
def __getattr__(self, name):
return getattr(self.text, name)
def bell(self):
self._bell = True
class FetchTest(unittest.TestCase):
'''Test History.fetch with wrapped tk.Text.
'''
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = tk.Tk()
cls.root.withdraw()
def setUp(self):
self.text = text = TextWrapper(self.root)
text.insert('1.0', ">>> ")
text.mark_set('iomark', '1.4')
text.mark_gravity('iomark', 'left')
self.history = History(text)
self.history.history = [line1, line2]
@classmethod
def tearDownClass(cls):
cls.root.destroy()
del cls.root
def fetch_test(self, reverse, line, prefix, index, *, bell=False):
# Perform one fetch as invoked by Alt-N or Alt-P
# Test the result. The line test is the most important.
# The last two are diagnostic of fetch internals.
History = self.history
History.fetch(reverse)
Equal = self.assertEqual
Equal(self.text.get('iomark', 'end-1c'), line)
Equal(self.text._bell, bell)
if bell:
self.text._bell = False
Equal(History.prefix, prefix)
Equal(History.pointer, index)
Equal(self.text.compare("insert", '==', "end-1c"), 1)
def test_fetch_prev_cyclic(self):
prefix = ''
test = self.fetch_test
test(True, line2, prefix, 1)
test(True, line1, prefix, 0)
test(True, prefix, None, None, bell=True)
def test_fetch_next_cyclic(self):
prefix = ''
test = self.fetch_test
test(False, line1, prefix, 0)
test(False, line2, prefix, 1)
test(False, prefix, None, None, bell=True)
# Prefix 'a' tests skip line2, which starts with 'b'
def test_fetch_prev_prefix(self):
prefix = 'a'
self.text.insert('iomark', prefix)
self.fetch_test(True, line1, prefix, 0)
self.fetch_test(True, prefix, None, None, bell=True)
def test_fetch_next_prefix(self):
prefix = 'a'
self.text.insert('iomark', prefix)
self.fetch_test(False, line1, prefix, 0)
self.fetch_test(False, prefix, None, None, bell=True)
def test_fetch_prev_noncyclic(self):
prefix = ''
self.history.cyclic = False
test = self.fetch_test
test(True, line2, prefix, 1)
test(True, line1, prefix, 0)
test(True, line1, prefix, 0, bell=True)
def test_fetch_next_noncyclic(self):
prefix = ''
self.history.cyclic = False
test = self.fetch_test
test(False, prefix, None, None, bell=True)
test(True, line2, prefix, 1)
test(False, prefix, None, None, bell=True)
test(False, prefix, None, None, bell=True)
def test_fetch_cursor_move(self):
# Move cursor after fetch
self.history.fetch(reverse=True) # initialization
self.text.mark_set('insert', 'iomark')
self.fetch_test(True, line2, None, None, bell=True)
def test_fetch_edit(self):
# Edit after fetch
self.history.fetch(reverse=True) # initialization
self.text.delete('iomark', 'insert', )
self.text.insert('iomark', 'a =')
self.fetch_test(True, line1, 'a =', 0) # prefix is reset
def test_history_prev_next(self):
# Minimally test functions bound to events
self.history.history_prev('dummy event')
self.assertEqual(self.history.pointer, 1)
self.history.history_next('dummy event')
self.assertEqual(self.history.pointer, None)
if __name__ == '__main__':
unittest.main(verbosity=2, exit=2)

View file

@ -0,0 +1,276 @@
"Test hyperparser, coverage 98%."
from idlelib.hyperparser import HyperParser
import unittest
from test.support import requires
from tkinter import Tk, Text
from idlelib.editor import EditorWindow
class DummyEditwin:
def __init__(self, text):
self.text = text
self.indentwidth = 8
self.tabwidth = 8
self.context_use_ps1 = True
self.num_context_lines = 50, 500, 1000
_build_char_in_string_func = EditorWindow._build_char_in_string_func
is_char_in_string = EditorWindow.is_char_in_string
class HyperParserTest(unittest.TestCase):
code = (
'"""This is a module docstring"""\n'
'# this line is a comment\n'
'x = "this is a string"\n'
"y = 'this is also a string'\n"
'l = [i for i in range(10)]\n'
'm = [py*py for # comment\n'
' py in l]\n'
'x.__len__\n'
"z = ((r'asdf')+('a')))\n"
'[x for x in\n'
'for = False\n'
'cliché = "this is a string with unicode, what a cliché"'
)
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.root.withdraw()
cls.text = Text(cls.root)
cls.editwin = DummyEditwin(cls.text)
@classmethod
def tearDownClass(cls):
del cls.text, cls.editwin
cls.root.destroy()
del cls.root
def setUp(self):
self.text.insert('insert', self.code)
def tearDown(self):
self.text.delete('1.0', 'end')
self.editwin.context_use_ps1 = True
def get_parser(self, index):
"""
Return a parser object with index at 'index'
"""
return HyperParser(self.editwin, index)
def test_init(self):
"""
test corner cases in the init method
"""
with self.assertRaises(ValueError) as ve:
self.text.tag_add('console', '1.0', '1.end')
p = self.get_parser('1.5')
self.assertIn('precedes', str(ve.exception))
# test without ps1
self.editwin.context_use_ps1 = False
# number of lines lesser than 50
p = self.get_parser('end')
self.assertEqual(p.rawtext, self.text.get('1.0', 'end'))
# number of lines greater than 50
self.text.insert('end', self.text.get('1.0', 'end')*4)
p = self.get_parser('54.5')
def test_is_in_string(self):
get = self.get_parser
p = get('1.0')
self.assertFalse(p.is_in_string())
p = get('1.4')
self.assertTrue(p.is_in_string())
p = get('2.3')
self.assertFalse(p.is_in_string())
p = get('3.3')
self.assertFalse(p.is_in_string())
p = get('3.7')
self.assertTrue(p.is_in_string())
p = get('4.6')
self.assertTrue(p.is_in_string())
p = get('12.54')
self.assertTrue(p.is_in_string())
def test_is_in_code(self):
get = self.get_parser
p = get('1.0')
self.assertTrue(p.is_in_code())
p = get('1.1')
self.assertFalse(p.is_in_code())
p = get('2.5')
self.assertFalse(p.is_in_code())
p = get('3.4')
self.assertTrue(p.is_in_code())
p = get('3.6')
self.assertFalse(p.is_in_code())
p = get('4.14')
self.assertFalse(p.is_in_code())
def test_get_surrounding_bracket(self):
get = self.get_parser
def without_mustclose(parser):
# a utility function to get surrounding bracket
# with mustclose=False
return parser.get_surrounding_brackets(mustclose=False)
def with_mustclose(parser):
# a utility function to get surrounding bracket
# with mustclose=True
return parser.get_surrounding_brackets(mustclose=True)
p = get('3.2')
self.assertIsNone(with_mustclose(p))
self.assertIsNone(without_mustclose(p))
p = get('5.6')
self.assertTupleEqual(without_mustclose(p), ('5.4', '5.25'))
self.assertTupleEqual(without_mustclose(p), with_mustclose(p))
p = get('5.23')
self.assertTupleEqual(without_mustclose(p), ('5.21', '5.24'))
self.assertTupleEqual(without_mustclose(p), with_mustclose(p))
p = get('6.15')
self.assertTupleEqual(without_mustclose(p), ('6.4', '6.end'))
self.assertIsNone(with_mustclose(p))
p = get('9.end')
self.assertIsNone(with_mustclose(p))
self.assertIsNone(without_mustclose(p))
def test_get_expression(self):
get = self.get_parser
p = get('4.2')
self.assertEqual(p.get_expression(), 'y ')
p = get('4.7')
with self.assertRaises(ValueError) as ve:
p.get_expression()
self.assertIn('is inside a code', str(ve.exception))
p = get('5.25')
self.assertEqual(p.get_expression(), 'range(10)')
p = get('6.7')
self.assertEqual(p.get_expression(), 'py')
p = get('6.8')
self.assertEqual(p.get_expression(), '')
p = get('7.9')
self.assertEqual(p.get_expression(), 'py')
p = get('8.end')
self.assertEqual(p.get_expression(), 'x.__len__')
p = get('9.13')
self.assertEqual(p.get_expression(), "r'asdf'")
p = get('9.17')
with self.assertRaises(ValueError) as ve:
p.get_expression()
self.assertIn('is inside a code', str(ve.exception))
p = get('10.0')
self.assertEqual(p.get_expression(), '')
p = get('10.6')
self.assertEqual(p.get_expression(), '')
p = get('10.11')
self.assertEqual(p.get_expression(), '')
p = get('11.3')
self.assertEqual(p.get_expression(), '')
p = get('11.11')
self.assertEqual(p.get_expression(), 'False')
p = get('12.6')
self.assertEqual(p.get_expression(), 'cliché')
def test_eat_identifier(self):
def is_valid_id(candidate):
result = HyperParser._eat_identifier(candidate, 0, len(candidate))
if result == len(candidate):
return True
elif result == 0:
return False
else:
err_msg = "Unexpected result: {} (expected 0 or {}".format(
result, len(candidate)
)
raise Exception(err_msg)
# invalid first character which is valid elsewhere in an identifier
self.assertFalse(is_valid_id('2notid'))
# ASCII-only valid identifiers
self.assertTrue(is_valid_id('valid_id'))
self.assertTrue(is_valid_id('_valid_id'))
self.assertTrue(is_valid_id('valid_id_'))
self.assertTrue(is_valid_id('_2valid_id'))
# keywords which should be "eaten"
self.assertTrue(is_valid_id('True'))
self.assertTrue(is_valid_id('False'))
self.assertTrue(is_valid_id('None'))
# keywords which should not be "eaten"
self.assertFalse(is_valid_id('for'))
self.assertFalse(is_valid_id('import'))
self.assertFalse(is_valid_id('return'))
# valid unicode identifiers
self.assertTrue(is_valid_id('cliche'))
self.assertTrue(is_valid_id('cliché'))
self.assertTrue(is_valid_id(''))
# invalid unicode identifiers
self.assertFalse(is_valid_id('2a'))
self.assertFalse(is_valid_id('٢a'))
self.assertFalse(is_valid_id(''))
# valid identifier after "punctuation"
self.assertEqual(HyperParser._eat_identifier('+ var', 0, 5), len('var'))
self.assertEqual(HyperParser._eat_identifier('+var', 0, 4), len('var'))
self.assertEqual(HyperParser._eat_identifier('.var', 0, 4), len('var'))
# invalid identifiers
self.assertFalse(is_valid_id('+'))
self.assertFalse(is_valid_id(' '))
self.assertFalse(is_valid_id(':'))
self.assertFalse(is_valid_id('?'))
self.assertFalse(is_valid_id('^'))
self.assertFalse(is_valid_id('\\'))
self.assertFalse(is_valid_id('"'))
self.assertFalse(is_valid_id('"a string"'))
def test_eat_identifier_various_lengths(self):
eat_id = HyperParser._eat_identifier
for length in range(1, 21):
self.assertEqual(eat_id('a' * length, 0, length), length)
self.assertEqual(eat_id('é' * length, 0, length), length)
self.assertEqual(eat_id('a' + '2' * (length - 1), 0, length), length)
self.assertEqual(eat_id('é' + '2' * (length - 1), 0, length), length)
self.assertEqual(eat_id('é' + 'a' * (length - 1), 0, length), length)
self.assertEqual(eat_id('é' * (length - 1) + 'a', 0, length), length)
self.assertEqual(eat_id('+' * length, 0, length), 0)
self.assertEqual(eat_id('2' + 'a' * (length - 1), 0, length), 0)
self.assertEqual(eat_id('2' + 'é' * (length - 1), 0, length), 0)
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,37 @@
"Test , coverage 16%."
from idlelib import iomenu
import unittest
from test.support import requires
from tkinter import Tk
from idlelib.editor import EditorWindow
class IOBindigTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.root.withdraw()
cls.editwin = EditorWindow(root=cls.root)
@classmethod
def tearDownClass(cls):
cls.editwin._close()
del cls.editwin
cls.root.update_idletasks()
for id in cls.root.tk.call('after', 'info'):
cls.root.after_cancel(id) # Need for EditorWindow.
cls.root.destroy()
del cls.root
def test_init(self):
io = iomenu.IOBinding(self.editwin)
self.assertIs(io.editwin, self.editwin)
io.close
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,104 @@
"Test macosx, coverage 45% on Windows."
from idlelib import macosx
import unittest
from test.support import requires
import tkinter as tk
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()
def cmd(tkpath, func):
assert isinstance(tkpath, str)
assert isinstance(func, type(cmd))
cls.root.createcommand = cmd
@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)

View file

@ -0,0 +1,21 @@
"Test mainmenu, coverage 100%."
# Reported as 88%; mocking turtledemo absence would have no point.
from idlelib import mainmenu
import unittest
class MainMenuTest(unittest.TestCase):
def test_menudefs(self):
actual = [item[0] for item in mainmenu.menudefs]
expect = ['file', 'edit', 'format', 'run', 'shell',
'debug', 'options', 'window', 'help']
self.assertEqual(actual, expect)
def test_default_keydefs(self):
self.assertGreaterEqual(len(mainmenu.default_keydefs), 50)
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,40 @@
"Test multicall, coverage 33%."
from idlelib import multicall
import unittest
from test.support import requires
from tkinter import Tk, Text
class MultiCallTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.root.withdraw()
cls.mc = multicall.MultiCallCreator(Text)
@classmethod
def tearDownClass(cls):
del cls.mc
cls.root.update_idletasks()
## for id in cls.root.tk.call('after', 'info'):
## cls.root.after_cancel(id) # Need for EditorWindow.
cls.root.destroy()
del cls.root
def test_creator(self):
mc = self.mc
self.assertIs(multicall._multicall_dict[Text], mc)
self.assertTrue(issubclass(mc, Text))
mc2 = multicall.MultiCallCreator(Text)
self.assertIs(mc, mc2)
def test_init(self):
mctext = self.mc(self.root)
self.assertIsInstance(mctext._MultiCall__binders, list)
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,171 @@
"Test outwin, coverage 76%."
from idlelib import outwin
import unittest
from test.support import requires
from tkinter import Tk, Text
from idlelib.idle_test.mock_tk import Mbox_func
from idlelib.idle_test.mock_idle import Func
from unittest import mock
class OutputWindowTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
root = cls.root = Tk()
root.withdraw()
w = cls.window = outwin.OutputWindow(None, None, None, root)
cls.text = w.text = Text(root)
@classmethod
def tearDownClass(cls):
cls.window.close()
del cls.text, cls.window
cls.root.destroy()
del cls.root
def setUp(self):
self.text.delete('1.0', 'end')
def test_ispythonsource(self):
# OutputWindow overrides ispythonsource to always return False.
w = self.window
self.assertFalse(w.ispythonsource('test.txt'))
self.assertFalse(w.ispythonsource(__file__))
def test_window_title(self):
self.assertEqual(self.window.top.title(), 'Output')
def test_maybesave(self):
w = self.window
eq = self.assertEqual
w.get_saved = Func()
w.get_saved.result = False
eq(w.maybesave(), 'no')
eq(w.get_saved.called, 1)
w.get_saved.result = True
eq(w.maybesave(), 'yes')
eq(w.get_saved.called, 2)
del w.get_saved
def test_write(self):
eq = self.assertEqual
delete = self.text.delete
get = self.text.get
write = self.window.write
# Test bytes.
b = b'Test bytes.'
eq(write(b), len(b))
eq(get('1.0', '1.end'), b.decode())
# No new line - insert stays on same line.
delete('1.0', 'end')
test_text = 'test text'
eq(write(test_text), len(test_text))
eq(get('1.0', '1.end'), 'test text')
eq(get('insert linestart', 'insert lineend'), 'test text')
# New line - insert moves to next line.
delete('1.0', 'end')
test_text = 'test text\n'
eq(write(test_text), len(test_text))
eq(get('1.0', '1.end'), 'test text')
eq(get('insert linestart', 'insert lineend'), '')
# Text after new line is tagged for second line of Text widget.
delete('1.0', 'end')
test_text = 'test text\nLine 2'
eq(write(test_text), len(test_text))
eq(get('1.0', '1.end'), 'test text')
eq(get('2.0', '2.end'), 'Line 2')
eq(get('insert linestart', 'insert lineend'), 'Line 2')
# Test tags.
delete('1.0', 'end')
test_text = 'test text\n'
test_text2 = 'Line 2\n'
eq(write(test_text, tags='mytag'), len(test_text))
eq(write(test_text2, tags='secondtag'), len(test_text2))
eq(get('mytag.first', 'mytag.last'), test_text)
eq(get('secondtag.first', 'secondtag.last'), test_text2)
eq(get('1.0', '1.end'), test_text.rstrip('\n'))
eq(get('2.0', '2.end'), test_text2.rstrip('\n'))
def test_writelines(self):
eq = self.assertEqual
get = self.text.get
writelines = self.window.writelines
writelines(('Line 1\n', 'Line 2\n', 'Line 3\n'))
eq(get('1.0', '1.end'), 'Line 1')
eq(get('2.0', '2.end'), 'Line 2')
eq(get('3.0', '3.end'), 'Line 3')
eq(get('insert linestart', 'insert lineend'), '')
def test_goto_file_line(self):
eq = self.assertEqual
w = self.window
text = self.text
w.flist = mock.Mock()
gfl = w.flist.gotofileline = Func()
showerror = w.showerror = Mbox_func()
# No file/line number.
w.write('Not a file line')
self.assertIsNone(w.goto_file_line())
eq(gfl.called, 0)
eq(showerror.title, 'No special line')
# Current file/line number.
w.write(f'{str(__file__)}: 42: spam\n')
w.write(f'{str(__file__)}: 21: spam')
self.assertIsNone(w.goto_file_line())
eq(gfl.args, (str(__file__), 21))
# Previous line has file/line number.
text.delete('1.0', 'end')
w.write(f'{str(__file__)}: 42: spam\n')
w.write('Not a file line')
self.assertIsNone(w.goto_file_line())
eq(gfl.args, (str(__file__), 42))
del w.flist.gotofileline, w.showerror
class ModuleFunctionTest(unittest.TestCase):
@classmethod
def setUp(cls):
outwin.file_line_progs = None
def test_compile_progs(self):
outwin.compile_progs()
for pat, regex in zip(outwin.file_line_pats, outwin.file_line_progs):
self.assertEqual(regex.pattern, pat)
@mock.patch('builtins.open')
def test_file_line_helper(self, mock_open):
flh = outwin.file_line_helper
test_lines = (
(r'foo file "testfile1", line 42, bar', ('testfile1', 42)),
(r'foo testfile2(21) bar', ('testfile2', 21)),
(r' testfile3 : 42: foo bar\n', (' testfile3 ', 42)),
(r'foo testfile4.py :1: ', ('foo testfile4.py ', 1)),
('testfile5: \u19D4\u19D2: ', ('testfile5', 42)),
(r'testfile6: 42', None), # only one `:`
(r'testfile7 42 text', None) # no separators
)
for line, expected_output in test_lines:
self.assertEqual(flh(line), expected_output)
if expected_output:
mock_open.assert_called_with(expected_output[0], 'r')
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,379 @@
"Test paragraph, coverage 76%."
from idlelib import paragraph as pg
import unittest
from test.support import requires
from tkinter import Tk, Text
from idlelib.editor import EditorWindow
class Is_Get_Test(unittest.TestCase):
"""Test the is_ and get_ functions"""
test_comment = '# This is a comment'
test_nocomment = 'This is not a comment'
trailingws_comment = '# This is a comment '
leadingws_comment = ' # This is a comment'
leadingws_nocomment = ' This is not a comment'
def test_is_all_white(self):
self.assertTrue(pg.is_all_white(''))
self.assertTrue(pg.is_all_white('\t\n\r\f\v'))
self.assertFalse(pg.is_all_white(self.test_comment))
def test_get_indent(self):
Equal = self.assertEqual
Equal(pg.get_indent(self.test_comment), '')
Equal(pg.get_indent(self.trailingws_comment), '')
Equal(pg.get_indent(self.leadingws_comment), ' ')
Equal(pg.get_indent(self.leadingws_nocomment), ' ')
def test_get_comment_header(self):
Equal = self.assertEqual
# Test comment strings
Equal(pg.get_comment_header(self.test_comment), '#')
Equal(pg.get_comment_header(self.trailingws_comment), '#')
Equal(pg.get_comment_header(self.leadingws_comment), ' #')
# Test non-comment strings
Equal(pg.get_comment_header(self.leadingws_nocomment), ' ')
Equal(pg.get_comment_header(self.test_nocomment), '')
class FindTest(unittest.TestCase):
"""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.
It appears that code with the same indentation as a quoted string is grouped
as part of the same paragraph, which is probably incorrect behavior.
"""
@classmethod
def setUpClass(cls):
from idlelib.idle_test.mock_tk import Text
cls.text = Text()
def runcase(self, inserttext, stopline, expected):
# Check that find_paragraph returns the expected paragraph when
# the mark index is set to beginning, middle, end of each line
# up to but not including the stop line
text = self.text
text.insert('1.0', inserttext)
for line in range(1, stopline):
linelength = int(text.index("%d.end" % line).split('.')[1])
for col in (0, linelength//2, linelength):
tempindex = "%d.%d" % (line, col)
self.assertEqual(pg.find_paragraph(text, tempindex), expected)
text.delete('1.0', 'end')
def test_find_comment(self):
comment = (
"# Comment block with no blank lines before\n"
"# Comment line\n"
"\n")
self.runcase(comment, 3, ('1.0', '3.0', '#', comment[0:58]))
comment = (
"\n"
"# Comment block with whitespace line before and after\n"
"# Comment line\n"
"\n")
self.runcase(comment, 4, ('2.0', '4.0', '#', comment[1:70]))
comment = (
"\n"
" # Indented comment block with whitespace before and after\n"
" # Comment line\n"
"\n")
self.runcase(comment, 4, ('2.0', '4.0', ' #', comment[1:82]))
comment = (
"\n"
"# Single line comment\n"
"\n")
self.runcase(comment, 3, ('2.0', '3.0', '#', comment[1:23]))
comment = (
"\n"
" # Single line comment with leading whitespace\n"
"\n")
self.runcase(comment, 3, ('2.0', '3.0', ' #', comment[1:51]))
comment = (
"\n"
"# Comment immediately followed by code\n"
"x = 42\n"
"\n")
self.runcase(comment, 3, ('2.0', '3.0', '#', comment[1:40]))
comment = (
"\n"
" # Indented comment immediately followed by code\n"
"x = 42\n"
"\n")
self.runcase(comment, 3, ('2.0', '3.0', ' #', comment[1:53]))
comment = (
"\n"
"# Comment immediately followed by indented code\n"
" x = 42\n"
"\n")
self.runcase(comment, 3, ('2.0', '3.0', '#', comment[1:49]))
def test_find_paragraph(self):
teststring = (
'"""String with no blank lines before\n'
'String line\n'
'"""\n'
'\n')
self.runcase(teststring, 4, ('1.0', '4.0', '', teststring[0:53]))
teststring = (
"\n"
'"""String with whitespace line before and after\n'
'String line.\n'
'"""\n'
'\n')
self.runcase(teststring, 5, ('2.0', '5.0', '', teststring[1:66]))
teststring = (
'\n'
' """Indented string with whitespace before and after\n'
' Comment string.\n'
' """\n'
'\n')
self.runcase(teststring, 5, ('2.0', '5.0', ' ', teststring[1:85]))
teststring = (
'\n'
'"""Single line string."""\n'
'\n')
self.runcase(teststring, 3, ('2.0', '3.0', '', teststring[1:27]))
teststring = (
'\n'
' """Single line string with leading whitespace."""\n'
'\n')
self.runcase(teststring, 3, ('2.0', '3.0', ' ', teststring[1:55]))
class ReformatFunctionTest(unittest.TestCase):
"""Test the reformat_paragraph function without the editor window."""
def test_reformat_paragraph(self):
Equal = self.assertEqual
reform = pg.reformat_paragraph
hw = "O hello world"
Equal(reform(' ', 1), ' ')
Equal(reform("Hello world", 20), "Hello world")
# Test without leading newline
Equal(reform(hw, 1), "O\nhello\nworld")
Equal(reform(hw, 6), "O\nhello\nworld")
Equal(reform(hw, 7), "O hello\nworld")
Equal(reform(hw, 12), "O hello\nworld")
Equal(reform(hw, 13), "O hello world")
# Test with leading newline
hw = "\nO hello world"
Equal(reform(hw, 1), "\nO\nhello\nworld")
Equal(reform(hw, 6), "\nO\nhello\nworld")
Equal(reform(hw, 7), "\nO hello\nworld")
Equal(reform(hw, 12), "\nO hello\nworld")
Equal(reform(hw, 13), "\nO hello world")
class ReformatCommentTest(unittest.TestCase):
"""Test the reformat_comment function without the editor window."""
def test_reformat_comment(self):
Equal = self.assertEqual
# reformat_comment formats to a minimum of 20 characters
test_string = (
" \"\"\"this is a test of a reformat for a triple quoted string"
" will it reformat to less than 70 characters for me?\"\"\"")
result = pg.reformat_comment(test_string, 70, " ")
expected = (
" \"\"\"this is a test of a reformat for a triple quoted string will it\n"
" reformat to less than 70 characters for me?\"\"\"")
Equal(result, expected)
test_comment = (
"# this is a test of a reformat for a triple quoted string will "
"it reformat to less than 70 characters for me?")
result = pg.reformat_comment(test_comment, 70, "#")
expected = (
"# this is a test of a reformat for a triple quoted string will it\n"
"# reformat to less than 70 characters for me?")
Equal(result, expected)
class FormatClassTest(unittest.TestCase):
def test_init_close(self):
instance = pg.FormatParagraph('editor')
self.assertEqual(instance.editwin, 'editor')
instance.close()
self.assertEqual(instance.editwin, None)
# For testing format_paragraph_event, Initialize FormatParagraph with
# a mock Editor with .text and .get_selection_indices. The text must
# be a Text wrapper that adds two methods
# A real EditorWindow creates unneeded, time-consuming baggage and
# sometimes emits shutdown warnings like this:
# "warning: callback failed in WindowList <class '_tkinter.TclError'>
# : invalid command name ".55131368.windows".
# Calling EditorWindow._close in tearDownClass prevents this but causes
# other problems (windows left open).
class TextWrapper:
def __init__(self, master):
self.text = Text(master=master)
def __getattr__(self, name):
return getattr(self.text, name)
def undo_block_start(self): pass
def undo_block_stop(self): pass
class Editor:
def __init__(self, root):
self.text = TextWrapper(root)
get_selection_indices = EditorWindow. get_selection_indices
class FormatEventTest(unittest.TestCase):
"""Test the formatting of text inside a Text widget.
This is done with FormatParagraph.format.paragraph_event,
which calls functions in the module as appropriate.
"""
test_string = (
" '''this is a test of a reformat for a triple "
"quoted string will it reformat to less than 70 "
"characters for me?'''\n")
multiline_test_string = (
" '''The first line is under the max width.\n"
" The second line's length is way over the max width. It goes "
"on and on until it is over 100 characters long.\n"
" Same thing with the third line. It is also way over the max "
"width, but FormatParagraph will fix it.\n"
" '''\n")
multiline_test_comment = (
"# The first line is under the max width.\n"
"# The second line's length is way over the max width. It goes on "
"and on until it is over 100 characters long.\n"
"# Same thing with the third line. It is also way over the max "
"width, but FormatParagraph will fix it.\n"
"# The fourth line is short like the first line.")
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.root.withdraw()
editor = Editor(root=cls.root)
cls.text = editor.text.text # Test code does not need the wrapper.
cls.formatter = pg.FormatParagraph(editor).format_paragraph_event
# Sets the insert mark just after the re-wrapped and inserted text.
@classmethod
def tearDownClass(cls):
del cls.text, cls.formatter
cls.root.update_idletasks()
cls.root.destroy()
del cls.root
def test_short_line(self):
self.text.insert('1.0', "Short line\n")
self.formatter("Dummy")
self.assertEqual(self.text.get('1.0', 'insert'), "Short line\n" )
self.text.delete('1.0', 'end')
def test_long_line(self):
text = self.text
# Set cursor ('insert' mark) to '1.0', within text.
text.insert('1.0', self.test_string)
text.mark_set('insert', '1.0')
self.formatter('ParameterDoesNothing', limit=70)
result = text.get('1.0', 'insert')
# find function includes \n
expected = (
" '''this is a test of a reformat for a triple quoted string will it\n"
" reformat to less than 70 characters for me?'''\n") # yes
self.assertEqual(result, expected)
text.delete('1.0', 'end')
# Select from 1.11 to line end.
text.insert('1.0', self.test_string)
text.tag_add('sel', '1.11', '1.end')
self.formatter('ParameterDoesNothing', limit=70)
result = text.get('1.0', 'insert')
# selection excludes \n
expected = (
" '''this is a test of a reformat for a triple quoted string will it reformat\n"
" to less than 70 characters for me?'''") # no
self.assertEqual(result, expected)
text.delete('1.0', 'end')
def test_multiple_lines(self):
text = self.text
# Select 2 long lines.
text.insert('1.0', self.multiline_test_string)
text.tag_add('sel', '2.0', '4.0')
self.formatter('ParameterDoesNothing', limit=70)
result = text.get('2.0', 'insert')
expected = (
" The second line's length is way over the max width. It goes on and\n"
" on until it is over 100 characters long. Same thing with the third\n"
" line. It is also way over the max width, but FormatParagraph will\n"
" fix it.\n")
self.assertEqual(result, expected)
text.delete('1.0', 'end')
def test_comment_block(self):
text = self.text
# Set cursor ('insert') to '1.0', within block.
text.insert('1.0', self.multiline_test_comment)
self.formatter('ParameterDoesNothing', limit=70)
result = text.get('1.0', 'insert')
expected = (
"# The first line is under the max width. The second line's length is\n"
"# way over the max width. It goes on and on until it is over 100\n"
"# characters long. Same thing with the third line. It is also way over\n"
"# the max width, but FormatParagraph will fix it. The fourth line is\n"
"# short like the first line.\n")
self.assertEqual(result, expected)
text.delete('1.0', 'end')
# Select line 2, verify line 1 unaffected.
text.insert('1.0', self.multiline_test_comment)
text.tag_add('sel', '2.0', '3.0')
self.formatter('ParameterDoesNothing', limit=70)
result = text.get('1.0', 'insert')
expected = (
"# The first line is under the max width.\n"
"# The second line's length is way over the max width. It goes on and\n"
"# on until it is over 100 characters long.\n")
self.assertEqual(result, expected)
text.delete('1.0', 'end')
# The following block worked with EditorWindow but fails with the mock.
# Lines 2 and 3 get pasted together even though the previous block left
# the previous line alone. More investigation is needed.
## # Select lines 3 and 4
## text.insert('1.0', self.multiline_test_comment)
## text.tag_add('sel', '3.0', '5.0')
## self.formatter('ParameterDoesNothing')
## result = text.get('3.0', 'insert')
## expected = (
##"# Same thing with the third line. It is also way over the max width,\n"
##"# but FormatParagraph will fix it. The fourth line is short like the\n"
##"# first line.\n")
## self.assertEqual(result, expected)
## text.delete('1.0', 'end')
if __name__ == '__main__':
unittest.main(verbosity=2, exit=2)

View file

@ -0,0 +1,112 @@
"""Test parenmatch, coverage 91%.
This must currently be a gui test because ParenMatch methods use
several text methods not defined on idlelib.idle_test.mock_tk.Text.
"""
from idlelib.parenmatch import ParenMatch
from test.support import requires
requires('gui')
import unittest
from unittest.mock import Mock
from tkinter import Tk, Text
class DummyEditwin:
def __init__(self, text):
self.text = text
self.indentwidth = 8
self.tabwidth = 8
self.context_use_ps1 = True
class ParenMatchTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.root = Tk()
cls.root.withdraw()
cls.text = Text(cls.root)
cls.editwin = DummyEditwin(cls.text)
cls.editwin.text_frame = Mock()
@classmethod
def tearDownClass(cls):
del cls.text, cls.editwin
cls.root.update_idletasks()
cls.root.destroy()
del cls.root
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_styles(self):
"""
Test ParenMatch with each style.
"""
text = self.text
pm = self.get_parenmatch()
for style, range1, range2 in (
('opener', ('1.10', '1.11'), ('1.10', '1.11')),
('default',('1.10', '1.11'),('1.10', '1.11')),
('parens', ('1.14', '1.15'), ('1.15', '1.16')),
('expression', ('1.10', '1.15'), ('1.10', '1.16'))):
with self.subTest(style=style):
text.delete('1.0', 'end')
pm.STYLE = style
text.insert('insert', 'def foobar(a, b')
pm.flash_paren_event('event')
self.assertIn('<<parenmatch-check-restore>>', text.event_info())
if style == 'parens':
self.assertTupleEqual(text.tag_nextrange('paren', '1.0'),
('1.10', '1.11'))
self.assertTupleEqual(
text.tag_prevrange('paren', 'end'), range1)
text.insert('insert', ')')
pm.restore_event()
self.assertNotIn('<<parenmatch-check-restore>>',
text.event_info())
self.assertEqual(text.tag_prevrange('paren', 'end'), ())
pm.paren_closed_event('event')
self.assertTupleEqual(
text.tag_prevrange('paren', 'end'), range2)
def test_paren_corner(self):
"""
Test corner cases in flash_paren_event and paren_closed_event.
These cases force conditional expression and alternate paths.
"""
text = self.text
pm = self.get_parenmatch()
text.insert('insert', '# this is a commen)')
pm.paren_closed_event('event')
text.insert('insert', '\ndef')
pm.flash_paren_event('event')
pm.paren_closed_event('event')
text.insert('insert', ' a, *arg)')
pm.paren_closed_event('event')
def test_handle_restore_timer(self):
pm = self.get_parenmatch()
pm.restore_event = Mock()
pm.handle_restore_timer(0)
self.assertTrue(pm.restore_event.called)
pm.restore_event.reset_mock()
pm.handle_restore_timer(1)
self.assertFalse(pm.restore_event.called)
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,86 @@
"Test pathbrowser, coverage 95%."
from idlelib import pathbrowser
import unittest
from test.support import requires
from tkinter import Tk
import os.path
import pyclbr # for _modules
import sys # for sys.path
from idlelib.idle_test.mock_idle import Func
import idlelib # for __file__
from idlelib import browser
from idlelib.tree import TreeNode
class PathBrowserTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.root.withdraw()
cls.pb = pathbrowser.PathBrowser(cls.root, _utest=True)
@classmethod
def tearDownClass(cls):
cls.pb.close()
cls.root.update_idletasks()
cls.root.destroy()
del cls.root, cls.pb
def test_init(self):
pb = self.pb
eq = self.assertEqual
eq(pb.master, self.root)
eq(pyclbr._modules, {})
self.assertIsInstance(pb.node, TreeNode)
self.assertIsNotNone(browser.file_open)
def test_settitle(self):
pb = self.pb
self.assertEqual(pb.top.title(), 'Path Browser')
self.assertEqual(pb.top.iconname(), 'Path Browser')
def test_rootnode(self):
pb = self.pb
rn = pb.rootnode()
self.assertIsInstance(rn, pathbrowser.PathBrowserTreeItem)
def test_close(self):
pb = self.pb
pb.top.destroy = Func()
pb.node.destroy = Func()
pb.close()
self.assertTrue(pb.top.destroy.called)
self.assertTrue(pb.node.destroy.called)
del pb.top.destroy, pb.node.destroy
class DirBrowserTreeItemTest(unittest.TestCase):
def test_DirBrowserTreeItem(self):
# Issue16226 - make sure that getting a sublist works
d = pathbrowser.DirBrowserTreeItem('')
d.GetSubList()
self.assertEqual('', d.GetText())
dir = os.path.split(os.path.abspath(idlelib.__file__))[0]
self.assertEqual(d.ispackagedir(dir), True)
self.assertEqual(d.ispackagedir(dir + '/Icons'), False)
class PathBrowserTreeItemTest(unittest.TestCase):
def test_PathBrowserTreeItem(self):
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)
if __name__ == '__main__':
unittest.main(verbosity=2, exit=False)

View file

@ -0,0 +1,118 @@
"Test percolator, coverage 100%."
from idlelib.percolator import Percolator, Delegator
import unittest
from test.support import requires
requires('gui')
from tkinter import Text, Tk, END
class MyFilter(Delegator):
def __init__(self):
Delegator.__init__(self, None)
def insert(self, *args):
self.insert_called_with = args
self.delegate.insert(*args)
def delete(self, *args):
self.delete_called_with = args
self.delegate.delete(*args)
def uppercase_insert(self, index, chars, tags=None):
chars = chars.upper()
self.delegate.insert(index, chars)
def lowercase_insert(self, index, chars, tags=None):
chars = chars.lower()
self.delegate.insert(index, chars)
def dont_insert(self, index, chars, tags=None):
pass
class PercolatorTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.root = Tk()
cls.text = Text(cls.root)
@classmethod
def tearDownClass(cls):
del cls.text
cls.root.destroy()
del cls.root
def setUp(self):
self.percolator = Percolator(self.text)
self.filter_one = MyFilter()
self.filter_two = MyFilter()
self.percolator.insertfilter(self.filter_one)
self.percolator.insertfilter(self.filter_two)
def tearDown(self):
self.percolator.close()
self.text.delete('1.0', END)
def test_insertfilter(self):
self.assertIsNotNone(self.filter_one.delegate)
self.assertEqual(self.percolator.top, self.filter_two)
self.assertEqual(self.filter_two.delegate, self.filter_one)
self.assertEqual(self.filter_one.delegate, self.percolator.bottom)
def test_removefilter(self):
filter_three = MyFilter()
self.percolator.removefilter(self.filter_two)
self.assertEqual(self.percolator.top, self.filter_one)
self.assertIsNone(self.filter_two.delegate)
filter_three = MyFilter()
self.percolator.insertfilter(self.filter_two)
self.percolator.insertfilter(filter_three)
self.percolator.removefilter(self.filter_one)
self.assertEqual(self.percolator.top, filter_three)
self.assertEqual(filter_three.delegate, self.filter_two)
self.assertEqual(self.filter_two.delegate, self.percolator.bottom)
self.assertIsNone(self.filter_one.delegate)
def test_insert(self):
self.text.insert('insert', 'foo')
self.assertEqual(self.text.get('1.0', END), 'foo\n')
self.assertTupleEqual(self.filter_one.insert_called_with,
('insert', 'foo', None))
def test_modify_insert(self):
self.filter_one.insert = self.filter_one.uppercase_insert
self.text.insert('insert', 'bAr')
self.assertEqual(self.text.get('1.0', END), 'BAR\n')
def test_modify_chain_insert(self):
filter_three = MyFilter()
self.percolator.insertfilter(filter_three)
self.filter_two.insert = self.filter_two.uppercase_insert
self.filter_one.insert = self.filter_one.lowercase_insert
self.text.insert('insert', 'BaR')
self.assertEqual(self.text.get('1.0', END), 'bar\n')
def test_dont_insert(self):
self.filter_one.insert = self.filter_one.dont_insert
self.text.insert('insert', 'foo bar')
self.assertEqual(self.text.get('1.0', END), '\n')
self.filter_one.insert = self.filter_one.dont_insert
self.text.insert('insert', 'foo bar')
self.assertEqual(self.text.get('1.0', END), '\n')
def test_without_filter(self):
self.text.insert('insert', 'hello')
self.assertEqual(self.text.get('1.0', 'end'), 'hello\n')
def test_delete(self):
self.text.insert('insert', 'foo')
self.text.delete('1.0', '1.2')
self.assertEqual(self.text.get('1.0', END), 'o\n')
self.assertTupleEqual(self.filter_one.delete_called_with,
('1.0', '1.2'))
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,466 @@
"Test pyparse, coverage 96%."
from idlelib import pyparse
import unittest
from collections import namedtuple
class ParseMapTest(unittest.TestCase):
def test_parsemap(self):
keepwhite = {ord(c): ord(c) for c in ' \t\n\r'}
mapping = pyparse.ParseMap(keepwhite)
self.assertEqual(mapping[ord('\t')], ord('\t'))
self.assertEqual(mapping[ord('a')], ord('x'))
self.assertEqual(mapping[1000], ord('x'))
def test_trans(self):
# trans is the production instance of ParseMap, used in _study1
parser = pyparse.Parser(4, 4)
self.assertEqual('\t a([{b}])b"c\'d\n'.translate(pyparse.trans),
'xxx(((x)))x"x\'x\n')
class PyParseTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.parser = pyparse.Parser(indentwidth=4, tabwidth=4)
@classmethod
def tearDownClass(cls):
del cls.parser
def test_init(self):
self.assertEqual(self.parser.indentwidth, 4)
self.assertEqual(self.parser.tabwidth, 4)
def test_set_code(self):
eq = self.assertEqual
p = self.parser
setcode = p.set_code
# Not empty and doesn't end with newline.
with self.assertRaises(AssertionError):
setcode('a')
tests = ('',
'a\n')
for string in tests:
with self.subTest(string=string):
setcode(string)
eq(p.code, string)
eq(p.study_level, 0)
def test_find_good_parse_start(self):
eq = self.assertEqual
p = self.parser
setcode = p.set_code
start = p.find_good_parse_start
# Split def across lines.
setcode('"""This is a module docstring"""\n'
'class C():\n'
' def __init__(self, a,\n'
' b=True):\n'
' pass\n'
)
# No value sent for is_char_in_string().
self.assertIsNone(start())
# Make text look like a string. This returns pos as the start
# position, but it's set to None.
self.assertIsNone(start(is_char_in_string=lambda index: True))
# Make all text look like it's not in a string. This means that it
# found a good start position.
eq(start(is_char_in_string=lambda index: False), 44)
# If the beginning of the def line is not in a string, then it
# returns that as the index.
eq(start(is_char_in_string=lambda index: index > 44), 44)
# If the beginning of the def line is in a string, then it
# looks for a previous index.
eq(start(is_char_in_string=lambda index: index >= 44), 33)
# If everything before the 'def' is in a string, then returns None.
# The non-continuation def line returns 44 (see below).
eq(start(is_char_in_string=lambda index: index < 44), None)
# Code without extra line break in def line - mostly returns the same
# values.
setcode('"""This is a module docstring"""\n'
'class C():\n'
' def __init__(self, a, b=True):\n'
' pass\n'
)
eq(start(is_char_in_string=lambda index: False), 44)
eq(start(is_char_in_string=lambda index: index > 44), 44)
eq(start(is_char_in_string=lambda index: index >= 44), 33)
# When the def line isn't split, this returns which doesn't match the
# split line test.
eq(start(is_char_in_string=lambda index: index < 44), 44)
def test_set_lo(self):
code = (
'"""This is a module docstring"""\n'
'class C():\n'
' def __init__(self, a,\n'
' b=True):\n'
' pass\n'
)
p = self.parser
p.set_code(code)
# Previous character is not a newline.
with self.assertRaises(AssertionError):
p.set_lo(5)
# A value of 0 doesn't change self.code.
p.set_lo(0)
self.assertEqual(p.code, code)
# An index that is preceded by a newline.
p.set_lo(44)
self.assertEqual(p.code, code[44:])
def test_study1(self):
eq = self.assertEqual
p = self.parser
setcode = p.set_code
study = p._study1
(NONE, BACKSLASH, FIRST, NEXT, BRACKET) = range(5)
TestInfo = namedtuple('TestInfo', ['string', 'goodlines',
'continuation'])
tests = (
TestInfo('', [0], NONE),
# Docstrings.
TestInfo('"""This is a complete docstring."""\n', [0, 1], NONE),
TestInfo("'''This is a complete docstring.'''\n", [0, 1], NONE),
TestInfo('"""This is a continued docstring.\n', [0, 1], FIRST),
TestInfo("'''This is a continued docstring.\n", [0, 1], FIRST),
TestInfo('"""Closing quote does not match."\n', [0, 1], FIRST),
TestInfo('"""Bracket in docstring [\n', [0, 1], FIRST),
TestInfo("'''Incomplete two line docstring.\n\n", [0, 2], NEXT),
# Single-quoted strings.
TestInfo('"This is a complete string."\n', [0, 1], NONE),
TestInfo('"This is an incomplete string.\n', [0, 1], NONE),
TestInfo("'This is more incomplete.\n\n", [0, 1, 2], NONE),
# Comment (backslash does not continue comments).
TestInfo('# Comment\\\n', [0, 1], NONE),
# Brackets.
TestInfo('("""Complete string in bracket"""\n', [0, 1], BRACKET),
TestInfo('("""Open string in bracket\n', [0, 1], FIRST),
TestInfo('a = (1 + 2) - 5 *\\\n', [0, 1], BACKSLASH), # No bracket.
TestInfo('\n def function1(self, a,\n b):\n',
[0, 1, 3], NONE),
TestInfo('\n def function1(self, a,\\\n', [0, 1, 2], BRACKET),
TestInfo('\n def function1(self, a,\n', [0, 1, 2], BRACKET),
TestInfo('())\n', [0, 1], NONE), # Extra closer.
TestInfo(')(\n', [0, 1], BRACKET), # Extra closer.
# For the mismatched example, it doesn't look like contination.
TestInfo('{)(]\n', [0, 1], NONE), # Mismatched.
)
for test in tests:
with self.subTest(string=test.string):
setcode(test.string) # resets study_level
study()
eq(p.study_level, 1)
eq(p.goodlines, test.goodlines)
eq(p.continuation, test.continuation)
# Called again, just returns without reprocessing.
self.assertIsNone(study())
def test_get_continuation_type(self):
eq = self.assertEqual
p = self.parser
setcode = p.set_code
gettype = p.get_continuation_type
(NONE, BACKSLASH, FIRST, NEXT, BRACKET) = range(5)
TestInfo = namedtuple('TestInfo', ['string', 'continuation'])
tests = (
TestInfo('', NONE),
TestInfo('"""This is a continuation docstring.\n', FIRST),
TestInfo("'''This is a multiline-continued docstring.\n\n", NEXT),
TestInfo('a = (1 + 2) - 5 *\\\n', BACKSLASH),
TestInfo('\n def function1(self, a,\\\n', BRACKET)
)
for test in tests:
with self.subTest(string=test.string):
setcode(test.string)
eq(gettype(), test.continuation)
def test_study2(self):
eq = self.assertEqual
p = self.parser
setcode = p.set_code
study = p._study2
TestInfo = namedtuple('TestInfo', ['string', 'start', 'end', 'lastch',
'openbracket', 'bracketing'])
tests = (
TestInfo('', 0, 0, '', None, ((0, 0),)),
TestInfo("'''This is a multiline continutation docstring.\n\n",
0, 49, "'", None, ((0, 0), (0, 1), (49, 0))),
TestInfo(' # Comment\\\n',
0, 12, '', None, ((0, 0), (1, 1), (12, 0))),
# A comment without a space is a special case
TestInfo(' #Comment\\\n',
0, 0, '', None, ((0, 0),)),
# Backslash continuation.
TestInfo('a = (1 + 2) - 5 *\\\n',
0, 19, '*', None, ((0, 0), (4, 1), (11, 0))),
# Bracket continuation with close.
TestInfo('\n def function1(self, a,\n b):\n',
1, 48, ':', None, ((1, 0), (17, 1), (46, 0))),
# Bracket continuation with unneeded backslash.
TestInfo('\n def function1(self, a,\\\n',
1, 28, ',', 17, ((1, 0), (17, 1))),
# Bracket continuation.
TestInfo('\n def function1(self, a,\n',
1, 27, ',', 17, ((1, 0), (17, 1))),
# Bracket continuation with comment at end of line with text.
TestInfo('\n def function1(self, a, # End of line comment.\n',
1, 51, ',', 17, ((1, 0), (17, 1), (28, 2), (51, 1))),
# Multi-line statement with comment line in between code lines.
TestInfo(' a = ["first item",\n # Comment line\n "next item",\n',
0, 55, ',', 6, ((0, 0), (6, 1), (7, 2), (19, 1),
(23, 2), (38, 1), (42, 2), (53, 1))),
TestInfo('())\n',
0, 4, ')', None, ((0, 0), (0, 1), (2, 0), (3, 0))),
TestInfo(')(\n', 0, 3, '(', 1, ((0, 0), (1, 0), (1, 1))),
# Wrong closers still decrement stack level.
TestInfo('{)(]\n',
0, 5, ']', None, ((0, 0), (0, 1), (2, 0), (2, 1), (4, 0))),
# Character after backslash.
TestInfo(':\\a\n', 0, 4, '\\a', None, ((0, 0),)),
TestInfo('\n', 0, 0, '', None, ((0, 0),)),
)
for test in tests:
with self.subTest(string=test.string):
setcode(test.string)
study()
eq(p.study_level, 2)
eq(p.stmt_start, test.start)
eq(p.stmt_end, test.end)
eq(p.lastch, test.lastch)
eq(p.lastopenbracketpos, test.openbracket)
eq(p.stmt_bracketing, test.bracketing)
# Called again, just returns without reprocessing.
self.assertIsNone(study())
def test_get_num_lines_in_stmt(self):
eq = self.assertEqual
p = self.parser
setcode = p.set_code
getlines = p.get_num_lines_in_stmt
TestInfo = namedtuple('TestInfo', ['string', 'lines'])
tests = (
TestInfo('[x for x in a]\n', 1), # Closed on one line.
TestInfo('[x\nfor x in a\n', 2), # Not closed.
TestInfo('[x\\\nfor x in a\\\n', 2), # "", uneeded backslashes.
TestInfo('[x\nfor x in a\n]\n', 3), # Closed on multi-line.
TestInfo('\n"""Docstring comment L1"""\nL2\nL3\nL4\n', 1),
TestInfo('\n"""Docstring comment L1\nL2"""\nL3\nL4\n', 1),
TestInfo('\n"""Docstring comment L1\\\nL2\\\nL3\\\nL4\\\n', 4),
TestInfo('\n\n"""Docstring comment L1\\\nL2\\\nL3\\\nL4\\\n"""\n', 5)
)
# Blank string doesn't have enough elements in goodlines.
setcode('')
with self.assertRaises(IndexError):
getlines()
for test in tests:
with self.subTest(string=test.string):
setcode(test.string)
eq(getlines(), test.lines)
def test_compute_bracket_indent(self):
eq = self.assertEqual
p = self.parser
setcode = p.set_code
indent = p.compute_bracket_indent
TestInfo = namedtuple('TestInfo', ['string', 'spaces'])
tests = (
TestInfo('def function1(self, a,\n', 14),
# Characters after bracket.
TestInfo('\n def function1(self, a,\n', 18),
TestInfo('\n\tdef function1(self, a,\n', 18),
# No characters after bracket.
TestInfo('\n def function1(\n', 8),
TestInfo('\n\tdef function1(\n', 8),
TestInfo('\n def function1( \n', 8), # Ignore extra spaces.
TestInfo('[\n"first item",\n # Comment line\n "next item",\n', 0),
TestInfo('[\n "first item",\n # Comment line\n "next item",\n', 2),
TestInfo('["first item",\n # Comment line\n "next item",\n', 1),
TestInfo('(\n', 4),
TestInfo('(a\n', 1),
)
# Must be C_BRACKET continuation type.
setcode('def function1(self, a, b):\n')
with self.assertRaises(AssertionError):
indent()
for test in tests:
setcode(test.string)
eq(indent(), test.spaces)
def test_compute_backslash_indent(self):
eq = self.assertEqual
p = self.parser
setcode = p.set_code
indent = p.compute_backslash_indent
# Must be C_BACKSLASH continuation type.
errors = (('def function1(self, a, b\\\n'), # Bracket.
(' """ (\\\n'), # Docstring.
('a = #\\\n'), # Inline comment.
)
for string in errors:
with self.subTest(string=string):
setcode(string)
with self.assertRaises(AssertionError):
indent()
TestInfo = namedtuple('TestInfo', ('string', 'spaces'))
tests = (TestInfo('a = (1 + 2) - 5 *\\\n', 4),
TestInfo('a = 1 + 2 - 5 *\\\n', 4),
TestInfo(' a = 1 + 2 - 5 *\\\n', 8),
TestInfo(' a = "spam"\\\n', 6),
TestInfo(' a = \\\n"a"\\\n', 4),
TestInfo(' a = #\\\n"a"\\\n', 5),
TestInfo('a == \\\n', 2),
TestInfo('a != \\\n', 2),
# Difference between containing = and those not.
TestInfo('\\\n', 2),
TestInfo(' \\\n', 6),
TestInfo('\t\\\n', 6),
TestInfo('a\\\n', 3),
TestInfo('{}\\\n', 4),
TestInfo('(1 + 2) - 5 *\\\n', 3),
)
for test in tests:
with self.subTest(string=test.string):
setcode(test.string)
eq(indent(), test.spaces)
def test_get_base_indent_string(self):
eq = self.assertEqual
p = self.parser
setcode = p.set_code
baseindent = p.get_base_indent_string
TestInfo = namedtuple('TestInfo', ['string', 'indent'])
tests = (TestInfo('', ''),
TestInfo('def a():\n', ''),
TestInfo('\tdef a():\n', '\t'),
TestInfo(' def a():\n', ' '),
TestInfo(' def a(\n', ' '),
TestInfo('\t\n def a(\n', ' '),
TestInfo('\t\n # Comment.\n', ' '),
)
for test in tests:
with self.subTest(string=test.string):
setcode(test.string)
eq(baseindent(), test.indent)
def test_is_block_opener(self):
yes = self.assertTrue
no = self.assertFalse
p = self.parser
setcode = p.set_code
opener = p.is_block_opener
TestInfo = namedtuple('TestInfo', ['string', 'assert_'])
tests = (
TestInfo('def a():\n', yes),
TestInfo('\n def function1(self, a,\n b):\n', yes),
TestInfo(':\n', yes),
TestInfo('a:\n', yes),
TestInfo('):\n', yes),
TestInfo('(:\n', yes),
TestInfo('":\n', no),
TestInfo('\n def function1(self, a,\n', no),
TestInfo('def function1(self, a):\n pass\n', no),
TestInfo('# A comment:\n', no),
TestInfo('"""A docstring:\n', no),
TestInfo('"""A docstring:\n', no),
)
for test in tests:
with self.subTest(string=test.string):
setcode(test.string)
test.assert_(opener())
def test_is_block_closer(self):
yes = self.assertTrue
no = self.assertFalse
p = self.parser
setcode = p.set_code
closer = p.is_block_closer
TestInfo = namedtuple('TestInfo', ['string', 'assert_'])
tests = (
TestInfo('return\n', yes),
TestInfo('\tbreak\n', yes),
TestInfo(' continue\n', yes),
TestInfo(' raise\n', yes),
TestInfo('pass \n', yes),
TestInfo('pass\t\n', yes),
TestInfo('return #\n', yes),
TestInfo('raised\n', no),
TestInfo('returning\n', no),
TestInfo('# return\n', no),
TestInfo('"""break\n', no),
TestInfo('"continue\n', no),
TestInfo('def function1(self, a):\n pass\n', yes),
)
for test in tests:
with self.subTest(string=test.string):
setcode(test.string)
test.assert_(closer())
def test_get_last_stmt_bracketing(self):
eq = self.assertEqual
p = self.parser
setcode = p.set_code
bracketing = p.get_last_stmt_bracketing
TestInfo = namedtuple('TestInfo', ['string', 'bracket'])
tests = (
TestInfo('', ((0, 0),)),
TestInfo('a\n', ((0, 0),)),
TestInfo('()()\n', ((0, 0), (0, 1), (2, 0), (2, 1), (4, 0))),
TestInfo('(\n)()\n', ((0, 0), (0, 1), (3, 0), (3, 1), (5, 0))),
TestInfo('()\n()\n', ((3, 0), (3, 1), (5, 0))),
TestInfo('()(\n)\n', ((0, 0), (0, 1), (2, 0), (2, 1), (5, 0))),
TestInfo('(())\n', ((0, 0), (0, 1), (1, 2), (3, 1), (4, 0))),
TestInfo('(\n())\n', ((0, 0), (0, 1), (2, 2), (4, 1), (5, 0))),
# Same as matched test.
TestInfo('{)(]\n', ((0, 0), (0, 1), (2, 0), (2, 1), (4, 0))),
TestInfo('(((())\n',
((0, 0), (0, 1), (1, 2), (2, 3), (3, 4), (5, 3), (6, 2))),
)
for test in tests:
with self.subTest(string=test.string):
setcode(test.string)
eq(bracketing(), test.bracket)
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,42 @@
"Test pyshell, coverage 12%."
# Plus coverage of test_warning. Was 20% with test_openshell.
from idlelib import pyshell
import unittest
from test.support import requires
from tkinter import Tk
class PyShellFileListTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.root.withdraw()
@classmethod
def tearDownClass(cls):
#cls.root.update_idletasks()
## for id in cls.root.tk.call('after', 'info'):
## cls.root.after_cancel(id) # Need for EditorWindow.
cls.root.destroy()
del cls.root
def test_init(self):
psfl = pyshell.PyShellFileList(self.root)
self.assertEqual(psfl.EditorWindow, pyshell.PyShellEditorWindow)
self.assertIsNone(psfl.pyshell)
# The following sometimes causes 'invalid command name "109734456recolorize"'.
# Uncommenting after_cancel above prevents this, but results in
# TclError: bad window path name ".!listedtoplevel.!frame.text"
# which is normally prevented by after_cancel.
## def test_openshell(self):
## pyshell.use_subprocess = False
## ps = pyshell.PyShellFileList(self.root).open_shell()
## self.assertIsInstance(ps, pyshell.PyShell)
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,352 @@
"""Test query, coverage 91%).
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.
"""
from idlelib import query
import unittest
from test.support import requires
from tkinter import Tk
import sys
from unittest import mock
from idlelib.idle_test.mock_tk import Var
# 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 combinations 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.destroy
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)

View file

@ -0,0 +1,122 @@
"Test redirector, coverage 100%."
from idlelib.redirector import WidgetRedirector
import unittest
from test.support import requires
from tkinter import Tk, Text, TclError
from idlelib.idle_test.mock_idle import Func
class InitCloseTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.root.withdraw()
cls.text = Text(cls.root)
@classmethod
def tearDownClass(cls):
del cls.text
cls.root.destroy()
del cls.root
def test_init(self):
redir = WidgetRedirector(self.text)
self.assertEqual(redir.widget, self.text)
self.assertEqual(redir.tk, self.text.tk)
self.assertRaises(TclError, WidgetRedirector, self.text)
redir.close() # restore self.tk, self.text
def test_close(self):
redir = WidgetRedirector(self.text)
redir.register('insert', Func)
redir.close()
self.assertEqual(redir._operations, {})
self.assertFalse(hasattr(self.text, 'widget'))
class WidgetRedirectorTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = Tk()
cls.root.withdraw()
cls.text = Text(cls.root)
@classmethod
def tearDownClass(cls):
del cls.text
cls.root.update_idletasks()
cls.root.destroy()
del cls.root
def setUp(self):
self.redir = WidgetRedirector(self.text)
self.func = Func()
self.orig_insert = self.redir.register('insert', self.func)
self.text.insert('insert', 'asdf') # leaves self.text empty
def tearDown(self):
self.text.delete('1.0', 'end')
self.redir.close()
def test_repr(self): # partly for 100% coverage
self.assertIn('Redirector', repr(self.redir))
self.assertIn('Original', repr(self.orig_insert))
def test_register(self):
self.assertEqual(self.text.get('1.0', 'end'), '\n')
self.assertEqual(self.func.args, ('insert', 'asdf'))
self.assertIn('insert', self.redir._operations)
self.assertIn('insert', self.text.__dict__)
self.assertEqual(self.text.insert, self.func)
def test_original_command(self):
self.assertEqual(self.orig_insert.operation, 'insert')
self.assertEqual(self.orig_insert.tk_call, self.text.tk.call)
self.orig_insert('insert', 'asdf')
self.assertEqual(self.text.get('1.0', 'end'), 'asdf\n')
def test_unregister(self):
self.assertIsNone(self.redir.unregister('invalid operation name'))
self.assertEqual(self.redir.unregister('insert'), self.func)
self.assertNotIn('insert', self.redir._operations)
self.assertNotIn('insert', self.text.__dict__)
def test_unregister_no_attribute(self):
del self.text.insert
self.assertEqual(self.redir.unregister('insert'), self.func)
def test_dispatch_intercept(self):
self.func.__init__(True)
self.assertTrue(self.redir.dispatch('insert', False))
self.assertFalse(self.func.args[0])
def test_dispatch_bypass(self):
self.orig_insert('insert', 'asdf')
# tk.call returns '' where Python would return None
self.assertEqual(self.redir.dispatch('delete', '1.0', 'end'), '')
self.assertEqual(self.text.get('1.0', 'end'), '\n')
def test_dispatch_error(self):
self.func.__init__(TclError())
self.assertEqual(self.redir.dispatch('insert', False), '')
self.assertEqual(self.redir.dispatch('invalid'), '')
def test_command_dispatch(self):
# Test that .__init__ causes redirection of tk calls
# through redir.dispatch
self.root.call(self.text._w, 'insert', 'hello')
self.assertEqual(self.func.args, ('hello',))
self.assertEqual(self.text.get('1.0', 'end'), '\n')
# Ensure that called through redir .dispatch and not through
# self.text.insert by having mock raise TclError.
self.func.__init__(TclError())
self.assertEqual(self.root.call(self.text._w, 'insert', 'boo'), '')
if __name__ == '__main__':
unittest.main(verbosity=2)

Some files were not shown because too many files have changed in this diff Show more