Removed the Requirement to Install Python and NodeJS (Now Bundled with Borealis)
47
Dependencies/Python/Lib/idlelib/CREDITS.txt
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
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.
|
||||
|
||||
Until Python 2.3, IDLE's 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.
|
||||
IDLefork was merged into the Python code base in 2003.
|
||||
|
||||
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!
|
||||
|
||||
Major contributors since 2005:
|
||||
|
||||
- 2005: Tal Einat
|
||||
- 2010: Terry Jan Reedy (current maintainer)
|
||||
- 2013: Roger Serwy
|
||||
- 2014: Saimadhav Heblikar
|
||||
- 2015: Mark Roseman
|
||||
- 2017: Louie Lu, Cheryl Sabella, and Serhiy Storchaka
|
||||
|
||||
For additional details refer to NEWS.txt and Changelog.
|
||||
|
||||
If we missed you, feel free to submit a PR with a summary of
|
||||
contributions (for instance, at least 5 merged PRs).
|
||||
|
||||
|
||||
|
||||
1591
Dependencies/Python/Lib/idlelib/ChangeLog
vendored
Normal file
296
Dependencies/Python/Lib/idlelib/HISTORY.txt
vendored
Normal 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
|
||||
automatically 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.
|
||||
|
||||
======================================================================
|
||||
51
Dependencies/Python/Lib/idlelib/Icons/README.txt
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
IDLE-PYTHON LOGOS
|
||||
|
||||
These are sent to tk on Windows, *NIX, and non-Aqua macOS
|
||||
in pyshell following "# set application icon".
|
||||
|
||||
|
||||
2006?: Andrew Clover made variously sized python icons for win23.
|
||||
https://www.doxdesk.com/software/py/pyicons.html
|
||||
|
||||
2006: 16, 32, and 48 bit .png versions were copied to CPython
|
||||
as Python application icons, maybe in PC/icons/py.ico.
|
||||
https://github.com/python/cpython/issues/43372
|
||||
|
||||
2014: They were copied (perhaps a bit revised) to idlelib/Icons.
|
||||
https://github.com/python/cpython/issues/64605
|
||||
.gif versions were also added.
|
||||
|
||||
2020: Add Clover's 256-bit image.
|
||||
https://github.com/python/cpython/issues/82620
|
||||
Other fixups were done.
|
||||
|
||||
The idle.ico file used for Windows was created with ImageMagick:
|
||||
$ convert idle_16.png idle_32.png idle_48.png idle_256.png idle.ico
|
||||
** This needs redoing whenever files are changed.
|
||||
?? Do Start, Desktop, and Taskbar use idlelib/Icons files?
|
||||
|
||||
Issue added Windows Store PC/icons/idlex44.png and .../idlex150.png.
|
||||
https://github.com/python/cpython/pull/22817
|
||||
?? Should these be updated with major changes?
|
||||
|
||||
2022: Optimize .png images in CPython repository with external program.
|
||||
https://github.com/python/cpython/pull/21348
|
||||
idle.ico (and idlex##) were not updated.
|
||||
|
||||
The idlexx.gif files are only needed for *nix running tcl/tk 8.5.
|
||||
As of 2022, this was known true for 1 'major' Linux distribution.
|
||||
(Same would be true for any non-Aqua macOS with 8.5, but now none?)
|
||||
Can be deleted when we require 8.6 or it is known always used.
|
||||
|
||||
Future: Derivatives of Python logo should be submitted for approval.
|
||||
PSF Trademark Working Group / Committee psf-trademarks@python.org
|
||||
https://www.python.org/community/logos/ # Original files
|
||||
https://www.python.org/psf/trademarks-faq/
|
||||
https://www.python.org/psf/trademarks/ # Usage.
|
||||
|
||||
|
||||
OTHER GIFS: These are used by browsers using idlelib.tree.
|
||||
At least some will not be used when tree is replaced by ttk.Treeview.
|
||||
|
||||
|
||||
Edited 2024 August 26 by TJR.
|
||||
BIN
Dependencies/Python/Lib/idlelib/Icons/folder.gif
vendored
Normal file
|
After Width: | Height: | Size: 120 B |
BIN
Dependencies/Python/Lib/idlelib/Icons/idle.ico
vendored
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
Dependencies/Python/Lib/idlelib/Icons/idle_16.gif
vendored
Normal file
|
After Width: | Height: | Size: 634 B |
BIN
Dependencies/Python/Lib/idlelib/Icons/idle_16.png
vendored
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
Dependencies/Python/Lib/idlelib/Icons/idle_256.png
vendored
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
Dependencies/Python/Lib/idlelib/Icons/idle_32.gif
vendored
Normal file
|
After Width: | Height: | Size: 1019 B |
BIN
Dependencies/Python/Lib/idlelib/Icons/idle_32.png
vendored
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
Dependencies/Python/Lib/idlelib/Icons/idle_48.gif
vendored
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
Dependencies/Python/Lib/idlelib/Icons/idle_48.png
vendored
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
Dependencies/Python/Lib/idlelib/Icons/minusnode.gif
vendored
Normal file
|
After Width: | Height: | Size: 75 B |
BIN
Dependencies/Python/Lib/idlelib/Icons/openfolder.gif
vendored
Normal file
|
After Width: | Height: | Size: 125 B |
BIN
Dependencies/Python/Lib/idlelib/Icons/plusnode.gif
vendored
Normal file
|
After Width: | Height: | Size: 78 B |
BIN
Dependencies/Python/Lib/idlelib/Icons/python.gif
vendored
Normal file
|
After Width: | Height: | Size: 380 B |
BIN
Dependencies/Python/Lib/idlelib/Icons/tk.gif
vendored
Normal file
|
After Width: | Height: | Size: 72 B |
660
Dependencies/Python/Lib/idlelib/NEWS2x.txt
vendored
Normal 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.
|
||||
--------------------------------------------------------------------
|
||||
1397
Dependencies/Python/Lib/idlelib/News3.txt
vendored
Normal file
290
Dependencies/Python/Lib/idlelib/README.txt
vendored
Normal file
@@ -0,0 +1,290 @@
|
||||
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).
|
||||
|
||||
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.py # Create calltip text.
|
||||
calltip_w.py # Display calltip.
|
||||
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_key.py # Change keybindings.
|
||||
debugger.py # Debug code run from shell or editor; show window.
|
||||
debugger_r.py # Debug code run in remote process.
|
||||
debugobj.py # Define class used in stackviewer.
|
||||
debugobj_r.py # Communicate objects between processes with rpc (nim).
|
||||
delegator.py # Define base class for delegators (nim).
|
||||
dynoption.py # Define mutable OptionMenu widget (nim)
|
||||
editor.py # Define most of editor and utility functions.
|
||||
filelist.py # Open files and manage list of open windows (nim).
|
||||
format.py # Define format menu options.
|
||||
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.
|
||||
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 # Communicate between idle and user processes (nim).
|
||||
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.
|
||||
sidebar.py # Define line number and shell prompt sidebars.
|
||||
squeezer.py # Squeeze long shell output (nim).
|
||||
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).
|
||||
tooltip.py # Define popups for calltips, squeezer (nim).
|
||||
tree.py # Define tree widget, used in browsers (nim).
|
||||
undo.py # Manage undo stack.
|
||||
util.py # Define common objects imported elsewhere (nim).
|
||||
windows.py # Manage window list and define listed top level.
|
||||
zoomheight.py # Zoom window to full height of screen.
|
||||
zzdummy.py # Example extension.
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
config-extensions.def # Defaults for extensions
|
||||
config-highlight.def # Defaults for colorizing
|
||||
config-keys.def # Defaults for key bindings
|
||||
config-main.def # Defaults for font and general tabs
|
||||
|
||||
Text
|
||||
----
|
||||
CREDITS.txt # not maintained, displayed by About IDLE
|
||||
HISTORY.txt # NEWS up to July 2001
|
||||
NEWS.txt # commits, displayed by About IDLE
|
||||
NEWS2.txt # commits to Python2
|
||||
README.txt # this file, displayed 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
|
||||
|
||||
|
||||
IDLE MENUS
|
||||
==========
|
||||
|
||||
Top level items and most submenu items are defined in mainmenu.
|
||||
Extensions 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)
|
||||
|
||||
Format (Editor only) [fFR = format.FormatRegion]
|
||||
Format Paragraph # format.FormatParagraph.format_paragraph_event
|
||||
Indent Region # fFR.indent_region_event
|
||||
Dedent Region # fFR.dedent_region_event
|
||||
Comment Out Reg. # fFR.comment_region_event
|
||||
Uncomment Region # fFR.uncomment_region_event
|
||||
Tabify Region # fFR.tabify_region_event
|
||||
Untabify Region # fFR.untabify_region_event
|
||||
Toggle Tabs # format.Indents.toggle_tabs_event
|
||||
New Indent Width # format.Indents.change_indentwidth_event
|
||||
Strip tailing whitespace # format.rstrip
|
||||
Zin # zzdummy
|
||||
Zout # zzdummy
|
||||
|
||||
Run (Editor only)
|
||||
Run Module # runscript.ScriptBinding.run_module_event
|
||||
Run... Customized # runscript.ScriptBinding.run_custom_event
|
||||
Check Module # runscript.ScriptBinding.check_module_event
|
||||
Python Shell # pyshell.Pyshell, pyshell.ModifiedInterpreter
|
||||
|
||||
Shell # pyshell
|
||||
View Last Restart # pyshell.PyShell.view_restart_mark
|
||||
Restart Shell # pyshell.PyShell.restart_shell
|
||||
Previous History # history.History.history_prev
|
||||
Next History # history.History.history_next
|
||||
Interrupt Execution # pyshell.PyShell.cancel_callback
|
||||
|
||||
Debug (Shell only)
|
||||
Go to File/Line # outwin.OutputWindow.goto_file_line
|
||||
debugger # debugger, debugger_r, PyShell.toggle_debugger
|
||||
Stack Viewer # stackviewer, PyShell.open_stack_viewer
|
||||
Auto-open Stack Viewer # stackviewer
|
||||
|
||||
Options
|
||||
Configure IDLE # eEW.config_dialog, config, configdialog (cd)
|
||||
(Parts of the dialog)
|
||||
Buttons # cd.ConfigDialog
|
||||
Font tab # cd.FontPage, config-main.def
|
||||
Highlight tab # cd.HighPage, query, config-highlight.def
|
||||
Keys tab # cd.KeysPage, query, config_key, config_keys.def
|
||||
Windows tab # cd.WinPage, config_main.def
|
||||
Shell/Ed tab # cd.ShedPage, config-main.def
|
||||
Extensions tab # config-extensions.def, corresponding .py files
|
||||
---
|
||||
... Code Context # codecontext
|
||||
... Line Numbers # sidebar
|
||||
Zoomheight # zoomheight
|
||||
|
||||
Window
|
||||
<open windows> # windows
|
||||
|
||||
Help
|
||||
About IDLE # eEW.about_dialog, help_about.AboutDialog
|
||||
---
|
||||
IDLE Help # eEW.help_dialog, help.show_idlehelp
|
||||
Python Docs # eEW.python_docs
|
||||
Turtle Demo # eEW.open_turtle_demo
|
||||
---
|
||||
<other help sources>
|
||||
|
||||
<Context Menu> (right click)
|
||||
Defined in editor, PyShell.pyshell
|
||||
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
|
||||
|
||||
|
||||
OTHER TOPICS
|
||||
============
|
||||
|
||||
Generally use PEP 8.
|
||||
|
||||
import statements
|
||||
-----------------
|
||||
Put imports 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.
|
||||
|
||||
What's New entries
|
||||
------------------
|
||||
|
||||
Repository directory Doc/whatsnew/ has a file 3.n.rst for each 3.n
|
||||
Python version. For the first entry in each file, add subsection
|
||||
'IDLE and idlelib', in alphabetical position, to the 'Improved Modules'
|
||||
section. For the rest of cpython, entries to 3.(n+1).rst begin with
|
||||
the release of 3.n.0b1. For IDLE, entries for features backported from
|
||||
'main' to '3.n' during its beta period do not got in 3.(n+1).rst. The
|
||||
latter usually gets its first entry during the 3.n.0 candidate period
|
||||
or after the 3.n.0 release.
|
||||
|
||||
When, as per PEP 434, feature changes are backported, entries are placed
|
||||
in the 3.n.rst file *in the main branch* for each Python version n that
|
||||
gets the backport. (Note: the format of entries have varied between
|
||||
versions.) Add a line "New in 3.n maintenance releases." before the
|
||||
first back-ported feature after 3.n.0 is released. Since each older
|
||||
version file gets a different number of backports, it is easiest to
|
||||
make a separate PR for each file and label it with the backports
|
||||
needed.
|
||||
|
||||
Github repository and issues
|
||||
----------------------------
|
||||
|
||||
The CPython repository is https://github.com/python/cpython. The
|
||||
IDLE Issues listing is https://github.com/orgs/python/projects/31.
|
||||
The main classification is by Topic, based on the IDLE menu. View the
|
||||
topics list by clicking the [<]] button in the upper right.
|
||||
210
Dependencies/Python/Lib/idlelib/TODO.txt
vendored
Normal 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 managers. (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
|
||||
10
Dependencies/Python/Lib/idlelib/__init__.py
vendored
Normal 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.
|
||||
7
Dependencies/Python/Lib/idlelib/__main__.py
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
"""
|
||||
IDLE main entry point
|
||||
|
||||
Run IDLE as python -m idlelib
|
||||
"""
|
||||
import idlelib.pyshell
|
||||
idlelib.pyshell.main()
|
||||
228
Dependencies/Python/Lib/idlelib/autocomplete.py
vendored
Normal file
@@ -0,0 +1,228 @@
|
||||
"""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 __main__
|
||||
import keyword
|
||||
import os
|
||||
import string
|
||||
import sys
|
||||
|
||||
# Modified keyword list is used in fetch_completions.
|
||||
completion_kwds = [s for s in keyword.kwlist
|
||||
if s not in {'True', 'False', 'None'}] # In builtins.
|
||||
completion_kwds.extend(('match', 'case')) # Context keywords.
|
||||
completion_kwds.sort()
|
||||
|
||||
# Two types of completions; defined here for autocomplete_w import below.
|
||||
ATTRS, FILES = 0, 1
|
||||
from idlelib import autocomplete_w
|
||||
from idlelib.config import idleConf
|
||||
from idlelib.hyperparser import HyperParser
|
||||
|
||||
# Tuples passed to open_completions.
|
||||
# EvalFunc, Complete, WantWin, Mode
|
||||
FORCE = True, False, True, None # Control-Space.
|
||||
TAB = False, True, True, None # Tab.
|
||||
TRY_A = False, False, False, ATTRS # '.' for attributes.
|
||||
TRY_F = False, False, False, FILES # '/' in quotes for file name.
|
||||
|
||||
# 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 = f"{os.sep}{os.altsep if os.altsep else ''}"
|
||||
TRIGGERS = f".{SEPS}"
|
||||
|
||||
class AutoComplete:
|
||||
|
||||
def __init__(self, editwin=None, tags=None):
|
||||
self.editwin = editwin
|
||||
if editwin is not None: # not in subprocess or no-gui test
|
||||
self.text = editwin.text
|
||||
self.tags = tags
|
||||
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): # Makes mocking easier.
|
||||
return autocomplete_w.AutoCompleteWindow(self.text, tags=self.tags)
|
||||
|
||||
def _remove_autocomplete_window(self, event=None):
|
||||
if self.autocompletewindow:
|
||||
self.autocompletewindow.hide_window()
|
||||
self.autocompletewindow = None
|
||||
|
||||
def force_open_completions_event(self, event):
|
||||
"(^space) Open completion list, even if a function call is needed."
|
||||
self.open_completions(FORCE)
|
||||
return "break"
|
||||
|
||||
def autocomplete_event(self, event):
|
||||
"(tab) Complete word or open list if multiple options."
|
||||
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(TAB)
|
||||
return "break" if opened else None
|
||||
|
||||
def try_open_completions_event(self, event=None):
|
||||
"(./) Open completion list after pause with no movement."
|
||||
lastchar = self.text.get("insert-1c")
|
||||
if lastchar in TRIGGERS:
|
||||
args = TRY_A if lastchar == "." else TRY_F
|
||||
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):
|
||||
"Call open_completions if index unchanged."
|
||||
self._delayed_completion_id = None
|
||||
if self.text.index("insert") == self._delayed_completion_index:
|
||||
self.open_completions(args)
|
||||
|
||||
def open_completions(self, args):
|
||||
"""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.
|
||||
"""
|
||||
evalfuncs, complete, wantwin, mode = args
|
||||
# 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==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 = 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==ATTRS):
|
||||
self._remove_autocomplete_window()
|
||||
mode = ATTRS
|
||||
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] == '.': # Need object with attributes.
|
||||
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, wantwin)
|
||||
|
||||
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 == ATTRS:
|
||||
if what == "": # Main module names.
|
||||
namespace = {**__main__.__builtins__.__dict__,
|
||||
**__main__.__dict__}
|
||||
bigl = eval("dir()", namespace)
|
||||
bigl.extend(completion_kwds)
|
||||
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 == 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__."
|
||||
return eval(name, {**sys.modules, **__main__.__dict__})
|
||||
|
||||
|
||||
AutoComplete.reload()
|
||||
|
||||
if __name__ == '__main__':
|
||||
from unittest import main
|
||||
main('idlelib.idle_test.test_autocomplete', verbosity=2)
|
||||
498
Dependencies/Python/Lib/idlelib/autocomplete_w.py
vendored
Normal file
@@ -0,0 +1,498 @@
|
||||
"""
|
||||
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 FILES, ATTRS
|
||||
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>", "<Key-Escape>")
|
||||
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, tags):
|
||||
# The widget (Text) on which we place the AutoCompleteWindow
|
||||
self.widget = widget
|
||||
# Tags to mark inserted text with
|
||||
self.tags = tags
|
||||
# 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.ATTRS or .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
|
||||
# Flag set to avoid recursive <Configure> callback invocations.
|
||||
self.is_configuring = 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.tags)
|
||||
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.
|
||||
"""
|
||||
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):
|
||||
"""Call 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
|
||||
|
||||
self.autocompletewindow = acw = Toplevel(self.widget)
|
||||
acw.withdraw()
|
||||
acw.wm_overrideredirect(1)
|
||||
try:
|
||||
# Prevent grabbing focus on macOS.
|
||||
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)
|
||||
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.update_idletasks() # Need for tk8.6.8 on macOS: #40128.
|
||||
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.is_configuring = False
|
||||
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 self.is_configuring:
|
||||
# Avoid running on recursive <Configure> callback invocations.
|
||||
return
|
||||
|
||||
self.is_configuring = True
|
||||
if not self.is_active():
|
||||
return
|
||||
|
||||
# Since the <Configure> event may occur after the completion window is gone,
|
||||
# catch potential TclError exceptions when accessing acw. See: bpo-41611.
|
||||
try:
|
||||
# Position the completion list window
|
||||
text = self.widget
|
||||
text.see(self.startindex)
|
||||
x, y, cx, cy = text.bbox(self.startindex)
|
||||
acw = self.autocompletewindow
|
||||
if platform.system().startswith('Windows'):
|
||||
# On Windows an update() call is needed for the completion
|
||||
# list window to be created, so that we can fetch its width
|
||||
# and height. However, this is not needed on other platforms
|
||||
# (tested on Ubuntu and macOS) but at one point began
|
||||
# causing freezes on macOS. See issues 37849 and 41611.
|
||||
acw.update()
|
||||
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))
|
||||
acw.deiconify()
|
||||
acw.update_idletasks()
|
||||
except TclError:
|
||||
pass
|
||||
|
||||
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.
|
||||
try:
|
||||
acw.unbind(WINCONFIG_SEQUENCE, self.winconfigid)
|
||||
except TclError:
|
||||
pass
|
||||
self.winconfigid = None
|
||||
|
||||
self.is_configuring = False
|
||||
|
||||
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 == 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 == ATTRS and keysym in
|
||||
("period", "space", "parenleft", "parenright", "bracketleft",
|
||||
"bracketright")) or \
|
||||
(self.mode == 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 == ATTRS 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
|
||||
96
Dependencies/Python/Lib/idlelib/autoexpand.py
vendored
Normal 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)
|
||||
260
Dependencies/Python/Lib/idlelib/browser.py
vendored
Normal file
@@ -0,0 +1,260 @@
|
||||
"""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
|
||||
"""
|
||||
|
||||
import os
|
||||
import pyclbr
|
||||
import sys
|
||||
|
||||
from idlelib.config import idleConf
|
||||
from idlelib import pyshell
|
||||
from idlelib.tree import TreeNode, TreeItem, ScrolledCanvas
|
||||
from idlelib.util import py_extensions
|
||||
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.
|
||||
|
||||
# The browser depends on pyclbr and importlib which do not support .pyi files.
|
||||
browseable_extension_blocklist = ('.pyi',)
|
||||
|
||||
|
||||
def is_browseable_extension(path):
|
||||
_, ext = os.path.splitext(path)
|
||||
ext = os.path.normcase(ext)
|
||||
return ext in py_extensions and ext not in browseable_extension_blocklist
|
||||
|
||||
|
||||
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.
|
||||
The insertion order of the dictionary is assumed to have been in line
|
||||
number order, so sorting is not necessary.
|
||||
|
||||
The current tree only calls this once per child_dict 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 isinstance(sup, str):
|
||||
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 obs
|
||||
|
||||
|
||||
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 supported 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 not is_browseable_extension(self.file):
|
||||
return
|
||||
if not os.path.exists(self.file):
|
||||
return
|
||||
file_open(self.file)
|
||||
|
||||
def IsExpandable(self):
|
||||
"Return True if Python file."
|
||||
return is_browseable_extension(self.file)
|
||||
|
||||
def listchildren(self):
|
||||
"Return sequenced classes and functions in the module."
|
||||
if not is_browseable_extension(self.file):
|
||||
return []
|
||||
dir, base = os.path.split(self.file)
|
||||
name, _ = os.path.splitext(base)
|
||||
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)
|
||||
205
Dependencies/Python/Lib/idlelib/calltip.py
vendored
Normal file
@@ -0,0 +1,205 @@
|
||||
"""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 __main__
|
||||
import inspect
|
||||
import re
|
||||
import sys
|
||||
import textwrap
|
||||
import types
|
||||
|
||||
from idlelib import calltip_w
|
||||
from idlelib.hyperparser import HyperParser
|
||||
|
||||
|
||||
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):
|
||||
"""Maybe close an existing calltip and maybe open a new calltip.
|
||||
|
||||
Called from (force_open|try_open|refresh)_calltip_event functions.
|
||||
"""
|
||||
hp = HyperParser(self.editwin, "insert")
|
||||
sur_paren = hp.get_surrounding_brackets('(')
|
||||
|
||||
# If not inside parentheses, no calltip.
|
||||
if not sur_paren:
|
||||
self.remove_calltip_window()
|
||||
return
|
||||
|
||||
# If a calltip is shown for the current parentheses, do
|
||||
# nothing.
|
||||
if self.active_calltip:
|
||||
opener_line, opener_col = map(int, sur_paren[0].split('.'))
|
||||
if (
|
||||
(opener_line, opener_col) ==
|
||||
(self.active_calltip.parenline, self.active_calltip.parencol)
|
||||
):
|
||||
return
|
||||
|
||||
hp.set_index(sur_paren[0])
|
||||
try:
|
||||
expression = hp.get_expression()
|
||||
except ValueError:
|
||||
expression = None
|
||||
if not expression:
|
||||
# No expression before the opening parenthesis, e.g.
|
||||
# because it's in a string or the opener for a tuple:
|
||||
# Do nothing.
|
||||
return
|
||||
|
||||
# At this point, the current index is after an opening
|
||||
# parenthesis, in a section of code, preceded by a valid
|
||||
# expression. If there is a calltip shown, it's not for the
|
||||
# same index and should be closed.
|
||||
self.remove_calltip_window()
|
||||
|
||||
# Simple, fast heuristic: If the preceding expression includes
|
||||
# an opening parenthesis, it likely includes a function call.
|
||||
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, **__main__.__dict__}
|
||||
try:
|
||||
return eval(expression, namespace) # Only protect user code.
|
||||
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"
|
||||
|
||||
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.
|
||||
'''
|
||||
# Determine function object fob to inspect.
|
||||
try:
|
||||
ob_call = ob.__call__
|
||||
except BaseException: # Buggy user object could raise anything.
|
||||
return '' # No popup for non-callables.
|
||||
# For Get_argspecTest.test_buggy_getattr_class, CallA() & CallB().
|
||||
fob = ob_call if isinstance(ob_call, types.MethodType) else ob
|
||||
|
||||
# Initialize argspec and wrap it to get lines.
|
||||
try:
|
||||
argspec = str(inspect.signature(fob))
|
||||
except Exception as err:
|
||||
msg = str(err)
|
||||
if msg.startswith(_invalid_method):
|
||||
return _invalid_method
|
||||
else:
|
||||
argspec = ''
|
||||
|
||||
if isinstance(fob, type) and argspec == '()':
|
||||
# If fob has 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 [])
|
||||
|
||||
# Augment lines from docstring, if any, and join to get argspec.
|
||||
doc = inspect.getdoc(ob)
|
||||
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)
|
||||
|
||||
return argspec or _default_callable_argspec
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from unittest import main
|
||||
main('idlelib.idle_test.test_calltip', verbosity=2)
|
||||
202
Dependencies/Python/Lib/idlelib/calltip_w.py
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
"""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().__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().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().showtip()
|
||||
|
||||
self._bind_events()
|
||||
|
||||
def showcontents(self):
|
||||
"""Create the call-tip widget."""
|
||||
self.label = Label(self.tipwindow, text=self.text, justify=LEFT,
|
||||
background="#ffffd0", foreground="black",
|
||||
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().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)
|
||||
270
Dependencies/Python/Lib/idlelib/codecontext.py
vendored
Normal file
@@ -0,0 +1,270 @@
|
||||
"""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.
|
||||
|
||||
For EditorWindows, <<toggle-code-context>> is bound to CodeContext(self).
|
||||
toggle_code_context_event.
|
||||
"""
|
||||
import re
|
||||
from sys import maxsize as INFINITY
|
||||
|
||||
from tkinter import Frame, Text, TclError
|
||||
from tkinter.constants import NSEW, SUNKEN
|
||||
|
||||
from idlelib.config import idleConf
|
||||
|
||||
BLOCKOPENERS = {'class', 'def', 'if', 'elif', 'else', 'while', 'for',
|
||||
'try', 'except', 'finally', 'with', 'async'}
|
||||
|
||||
|
||||
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."
|
||||
UPDATEINTERVAL = 100 # millisec
|
||||
|
||||
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.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._reset()
|
||||
|
||||
def _reset(self):
|
||||
self.context = None
|
||||
self.cell00 = None
|
||||
self.t1 = None
|
||||
self.topvisible = 1
|
||||
self.info = [(0, -1, "", False)]
|
||||
|
||||
@classmethod
|
||||
def reload(cls):
|
||||
"Load class variables from config."
|
||||
cls.context_depth = idleConf.GetOption("extensions", "CodeContext",
|
||||
"maxlines", type="int",
|
||||
default=15)
|
||||
|
||||
def __del__(self):
|
||||
"Cancel scheduled events."
|
||||
if self.t1 is not None:
|
||||
try:
|
||||
self.text.after_cancel(self.t1)
|
||||
except TclError: # pragma: no cover
|
||||
pass
|
||||
self.t1 = None
|
||||
|
||||
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 self.context is None:
|
||||
# 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:
|
||||
info = (widget.grid_info()
|
||||
if widget is self.editwin.text
|
||||
else widget.pack_info())
|
||||
padx += widget.tk.getint(info['padx'])
|
||||
padx += widget.tk.getint(widget.cget('padx'))
|
||||
border += widget.tk.getint(widget.cget('border'))
|
||||
context = self.context = Text(
|
||||
self.editwin.text_frame,
|
||||
height=1,
|
||||
width=1, # Don't request more than we get.
|
||||
highlightthickness=0,
|
||||
padx=padx, border=border, relief=SUNKEN, state='disabled')
|
||||
self.update_font()
|
||||
self.update_highlight_colors()
|
||||
context.bind('<ButtonRelease-1>', self.jumptoline)
|
||||
# Get the current context and initiate the recurring update event.
|
||||
self.timer_event()
|
||||
# Grid the context widget above the text widget.
|
||||
context.grid(row=0, column=1, sticky=NSEW)
|
||||
|
||||
line_number_colors = idleConf.GetHighlight(idleConf.CurrentTheme(),
|
||||
'linenumber')
|
||||
self.cell00 = Frame(self.editwin.text_frame,
|
||||
bg=line_number_colors['background'])
|
||||
self.cell00.grid(row=0, column=0, sticky=NSEW)
|
||||
menu_status = 'Hide'
|
||||
else:
|
||||
self.context.destroy()
|
||||
self.context = None
|
||||
self.cell00.destroy()
|
||||
self.cell00 = None
|
||||
self.text.after_cancel(self.t1)
|
||||
self._reset()
|
||||
menu_status = 'Show'
|
||||
self.editwin.update_menu_label(menu='options', index='*ode*ontext',
|
||||
label=f'{menu_status} Code Context')
|
||||
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 = self.editwin.getlineno("@0,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.
|
||||
|
||||
If a selection was made, don't jump; allow copying.
|
||||
If no visible context, show the top line of the file.
|
||||
"""
|
||||
try:
|
||||
self.context.index("sel.first")
|
||||
except TclError:
|
||||
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 is not None:
|
||||
self.update_code_context()
|
||||
self.t1 = self.text.after(self.UPDATEINTERVAL, self.timer_event)
|
||||
|
||||
def update_font(self):
|
||||
if self.context is not None:
|
||||
font = idleConf.GetFont(self.text, 'main', 'EditorWindow')
|
||||
self.context['font'] = font
|
||||
|
||||
def update_highlight_colors(self):
|
||||
if self.context is not None:
|
||||
colors = idleConf.GetHighlight(idleConf.CurrentTheme(), 'context')
|
||||
self.context['background'] = colors['background']
|
||||
self.context['foreground'] = colors['foreground']
|
||||
|
||||
if self.cell00 is not None:
|
||||
line_number_colors = idleConf.GetHighlight(idleConf.CurrentTheme(),
|
||||
'linenumber')
|
||||
self.cell00.config(bg=line_number_colors['background'])
|
||||
|
||||
|
||||
CodeContext.reload()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from unittest import main
|
||||
main('idlelib.idle_test.test_codecontext', verbosity=2, exit=False)
|
||||
|
||||
# Add htest.
|
||||
384
Dependencies/Python/Lib/idlelib/colorizer.py
vendored
Normal file
@@ -0,0 +1,384 @@
|
||||
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) + r"\b"
|
||||
match_softkw = (
|
||||
r"^[ \t]*" + # at beginning of line + possible indentation
|
||||
r"(?P<MATCH_SOFTKW>match)\b" +
|
||||
r"(?![ \t]*(?:" + "|".join([ # not followed by ...
|
||||
r"[:,;=^&|@~)\]}]", # a character which means it can't be a
|
||||
# pattern-matching statement
|
||||
r"\b(?:" + r"|".join(keyword.kwlist) + r")\b", # a keyword
|
||||
]) +
|
||||
r"))"
|
||||
)
|
||||
case_default = (
|
||||
r"^[ \t]*" + # at beginning of line + possible indentation
|
||||
r"(?P<CASE_SOFTKW>case)" +
|
||||
r"[ \t]+(?P<CASE_DEFAULT_UNDERSCORE>_\b)"
|
||||
)
|
||||
case_softkw_and_pattern = (
|
||||
r"^[ \t]*" + # at beginning of line + possible indentation
|
||||
r"(?P<CASE_SOFTKW2>case)\b" +
|
||||
r"(?![ \t]*(?:" + "|".join([ # not followed by ...
|
||||
r"_\b", # a lone underscore
|
||||
r"[:,;=^&|@~)\]}]", # a character which means it can't be a
|
||||
# pattern-matching case
|
||||
r"\b(?:" + r"|".join(keyword.kwlist) + r")\b", # a keyword
|
||||
]) +
|
||||
r"))"
|
||||
)
|
||||
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])
|
||||
prog = re.compile("|".join([
|
||||
builtin, comment, string, kw,
|
||||
match_softkw, case_default,
|
||||
case_softkw_and_pattern,
|
||||
any("SYNC", [r"\n"]),
|
||||
]),
|
||||
re.DOTALL | re.MULTILINE)
|
||||
return prog
|
||||
|
||||
|
||||
prog = make_pat()
|
||||
idprog = re.compile(r"\s+(\w+)")
|
||||
prog_group_name_to_tag = {
|
||||
"MATCH_SOFTKW": "KEYWORD",
|
||||
"CASE_SOFTKW": "KEYWORD",
|
||||
"CASE_DEFAULT_UNDERSCORE": "KEYWORD",
|
||||
"CASE_SOFTKW2": "KEYWORD",
|
||||
}
|
||||
|
||||
|
||||
def matched_named_groups(re_match):
|
||||
"Get only the non-empty named groups from an re.Match object."
|
||||
return ((k, v) for (k, v) in re_match.groupdict().items() if v)
|
||||
|
||||
|
||||
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')['foreground']
|
||||
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):
|
||||
"""Delegator for syntax highlighting (text coloring).
|
||||
|
||||
Instance variables:
|
||||
delegate: Delegator below this one in the stack, meaning the
|
||||
one this one delegates to.
|
||||
|
||||
Used to track state:
|
||||
after_id: Identifier for scheduled after event, which is a
|
||||
timer for colorizing the text.
|
||||
allow_colorizing: Boolean toggle for applying colorizing.
|
||||
colorizing: Boolean flag when colorizing is in process.
|
||||
stop_colorizing: Boolean flag to end an active colorizing
|
||||
process.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
Delegator.__init__(self)
|
||||
self.init_state()
|
||||
self.prog = prog
|
||||
self.idprog = idprog
|
||||
self.LoadTagDefs()
|
||||
|
||||
def init_state(self):
|
||||
"Initialize variables that track colorizing state."
|
||||
self.after_id = None
|
||||
self.allow_colorizing = True
|
||||
self.stop_colorizing = False
|
||||
self.colorizing = False
|
||||
|
||||
def setdelegate(self, delegate):
|
||||
"""Set the delegate for this instance.
|
||||
|
||||
A delegate is an instance of a Delegator class and each
|
||||
delegate points to the next delegator in the stack. This
|
||||
allows multiple delegators to be chained together for a
|
||||
widget. The bottom delegate for a colorizer is a Text
|
||||
widget.
|
||||
|
||||
If there is a delegate, also start the colorizing process.
|
||||
"""
|
||||
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):
|
||||
"Configure text widget tags with colors from tagdefs."
|
||||
for tag, cnf in self.tagdefs.items():
|
||||
self.tag_configure(tag, **cnf)
|
||||
self.tag_raise('sel')
|
||||
|
||||
def LoadTagDefs(self):
|
||||
"Create dictionary of tag names to text colors."
|
||||
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"),
|
||||
# "hit" is used by ReplaceDialog to mark matches. It shouldn't be changed by Colorizer, but
|
||||
# that currently isn't technically possible. This should be moved elsewhere in the future
|
||||
# when fixing the "hit" tag's visibility, or when the replace dialog is replaced with a
|
||||
# non-modal alternative.
|
||||
"hit": idleConf.GetHighlight(theme, "hit"),
|
||||
}
|
||||
if DEBUG: print('tagdefs', self.tagdefs)
|
||||
|
||||
def insert(self, index, chars, tags=None):
|
||||
"Insert chars into widget at index and mark for colorizing."
|
||||
index = self.index(index)
|
||||
self.delegate.insert(index, chars, tags)
|
||||
self.notify_range(index, index + "+%dc" % len(chars))
|
||||
|
||||
def delete(self, index1, index2=None):
|
||||
"Delete chars between indexes and mark for colorizing."
|
||||
index1 = self.index(index1)
|
||||
self.delegate.delete(index1, index2)
|
||||
self.notify_range(index1)
|
||||
|
||||
def notify_range(self, index1, index2=None):
|
||||
"Mark text changes for processing and restart colorizing, if active."
|
||||
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)
|
||||
return
|
||||
|
||||
def close(self):
|
||||
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
|
||||
|
||||
def toggle_colorize_event(self, event=None):
|
||||
"""Toggle colorizing on and off.
|
||||
|
||||
When toggling off, if colorizing is scheduled or is in
|
||||
process, it will be cancelled and/or stopped.
|
||||
|
||||
When toggling on, colorizing will be scheduled.
|
||||
"""
|
||||
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",
|
||||
"on" if self.allow_colorizing else "off")
|
||||
return "break"
|
||||
|
||||
def recolorize(self):
|
||||
"""Timer event (every 1ms) to colorize text.
|
||||
|
||||
Colorizing is only attempted when the text widget exists,
|
||||
when colorizing is toggled on, and when the colorizing
|
||||
process is not already running.
|
||||
|
||||
After colorizing is complete, some cleanup is done to
|
||||
make sure that all the text has been colorized.
|
||||
"""
|
||||
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)
|
||||
|
||||
def recolorize_main(self):
|
||||
"Evaluate text and apply colorizing tags."
|
||||
next = "1.0"
|
||||
while todo_tag_range := self.tag_nextrange("TODO", next):
|
||||
self.tag_remove("SYNC", todo_tag_range[0], todo_tag_range[1])
|
||||
sync_tag_range = self.tag_prevrange("SYNC", todo_tag_range[0])
|
||||
head = sync_tag_range[1] if sync_tag_range else "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 += line
|
||||
self._add_tags_in_section(chars, head)
|
||||
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_idletasks()
|
||||
if self.stop_colorizing:
|
||||
if DEBUG: print("colorizing stopped")
|
||||
return
|
||||
|
||||
def _add_tag(self, start, end, head, matched_group_name):
|
||||
"""Add a tag to a given range in the text widget.
|
||||
|
||||
This is a utility function, receiving the range as `start` and
|
||||
`end` positions, each of which is a number of characters
|
||||
relative to the given `head` index in the text widget.
|
||||
|
||||
The tag to add is determined by `matched_group_name`, which is
|
||||
the name of a regular expression "named group" as matched by
|
||||
by the relevant highlighting regexps.
|
||||
"""
|
||||
tag = prog_group_name_to_tag.get(matched_group_name,
|
||||
matched_group_name)
|
||||
self.tag_add(tag,
|
||||
f"{head}+{start:d}c",
|
||||
f"{head}+{end:d}c")
|
||||
|
||||
def _add_tags_in_section(self, chars, head):
|
||||
"""Parse and add highlighting tags to a given part of the text.
|
||||
|
||||
`chars` is a string with the text to parse and to which
|
||||
highlighting is to be applied.
|
||||
|
||||
`head` is the index in the text widget where the text is found.
|
||||
"""
|
||||
for m in self.prog.finditer(chars):
|
||||
for name, matched_text in matched_named_groups(m):
|
||||
a, b = m.span(name)
|
||||
self._add_tag(a, b, head, name)
|
||||
if matched_text in ("def", "class"):
|
||||
if m1 := self.idprog.match(chars, b):
|
||||
a, b = m1.span(1)
|
||||
self._add_tag(a, b, head, "DEFINITION")
|
||||
|
||||
def removecolors(self):
|
||||
"Remove all colorizing tags."
|
||||
for tag in self.tagdefs:
|
||||
self.tag_remove(tag, "1.0", "end")
|
||||
|
||||
|
||||
def _color_delegator(parent): # htest #
|
||||
from tkinter import Toplevel, Text
|
||||
from idlelib.idle_test.test_colorizer import source
|
||||
from idlelib.percolator import Percolator
|
||||
|
||||
top = Toplevel(parent)
|
||||
top.title("Test ColorDelegator")
|
||||
x, y = map(int, parent.geometry().split('+')[1:])
|
||||
top.geometry("700x550+%d+%d" % (x + 20, y + 175))
|
||||
|
||||
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)
|
||||
62
Dependencies/Python/Lib/idlelib/config-extensions.def
vendored
Normal 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>
|
||||
105
Dependencies/Python/Lib/idlelib/config-highlight.def
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
# 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
|
||||
context-foreground= #000000
|
||||
context-background= lightgray
|
||||
linenumber-foreground= gray
|
||||
linenumber-background= #ffffff
|
||||
#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
|
||||
|
||||
[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
|
||||
context-foreground= #000000
|
||||
context-background= lightgray
|
||||
linenumber-foreground= gray
|
||||
linenumber-background= #ffffff
|
||||
#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
|
||||
|
||||
[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
|
||||
linenumber-foreground= gray
|
||||
linenumber-background= #002240
|
||||
309
Dependencies/Python/Lib/idlelib/config-keys.def
vendored
Normal file
@@ -0,0 +1,309 @@
|
||||
# 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>
|
||||
run-custom= <Shift-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>
|
||||
run-custom= <Shift-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>
|
||||
run-custom= <Shift-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>
|
||||
run-custom= <Shift-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>
|
||||
run-custom= <Shift-Key-F5>
|
||||
check-module= <Option-Key-x>
|
||||
zoom-height= <Option-Key-0>
|
||||
93
Dependencies/Python/Lib/idlelib/config-main.def
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
# 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
|
||||
cursor-blink= 1
|
||||
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
|
||||
line-numbers-default= 0
|
||||
|
||||
[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]
|
||||
917
Dependencies/Python/Lib/idlelib/config.py
vendored
Normal file
@@ -0,0 +1,917 @@
|
||||
"""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 'extensions', 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 usable 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 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 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 and fname[0] != '#':
|
||||
if not self.IsEmpty():
|
||||
try:
|
||||
cfgFile = open(fname, 'w')
|
||||
except OSError:
|
||||
os.unlink(fname)
|
||||
cfgFile = open(fname, 'w')
|
||||
with cfgFile:
|
||||
self.write(cfgFile)
|
||||
elif os.path.exists(self.file):
|
||||
os.remove(self.file)
|
||||
|
||||
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
|
||||
|
||||
# See https://bugs.python.org/issue4630#msg356516 for following.
|
||||
# self.blink_off_time = <first editor text>['insertofftime']
|
||||
|
||||
if not _utest:
|
||||
self.CreateConfigHandlers()
|
||||
self.LoadCfgFiles()
|
||||
|
||||
def CreateConfigHandlers(self):
|
||||
"Populate default and user config parser dictionaries."
|
||||
idledir = os.path.dirname(__file__)
|
||||
self.userdir = userdir = '' if idlelib.testing else self.GetUserCfgDir()
|
||||
for cfg_type in self.config_types:
|
||||
self.defaultCfg[cfg_type] = IdleConfParser(
|
||||
os.path.join(idledir, f'config-{cfg_type}.def'))
|
||||
self.userCfg[cfg_type] = IdleUserConfParser(
|
||||
os.path.join(userdir or '#', f'config-{cfg_type}.cfg'))
|
||||
|
||||
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):
|
||||
if not idlelib.testing:
|
||||
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:
|
||||
if not idlelib.testing:
|
||||
warn = ('\n Warning: unable to create user config directory\n' +
|
||||
userDir + '\n Check path and permissions.\n Exiting!\n')
|
||||
try:
|
||||
print(warn, file=sys.stderr)
|
||||
except OSError:
|
||||
pass
|
||||
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):
|
||||
"""Return dict of theme element highlight colors.
|
||||
|
||||
The keys are 'foreground' and 'background'. The values are
|
||||
tkinter color strings for configuring backgrounds and tags.
|
||||
"""
|
||||
cfg = ('default' if self.defaultCfg['highlight'].has_section(theme)
|
||||
else 'user')
|
||||
theme_dict = self.GetThemeDict(cfg, theme)
|
||||
fore = theme_dict[element + '-foreground']
|
||||
if element == 'cursor':
|
||||
element = 'normal'
|
||||
back = theme_dict[element + '-background']
|
||||
return {"foreground": fore, "background": back}
|
||||
|
||||
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',
|
||||
'context-foreground':'#000000',
|
||||
'context-background':'#ffffff',
|
||||
'linenumber-foreground':'#000000',
|
||||
'linenumber-background':'#ffffff',
|
||||
#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',
|
||||
}
|
||||
for element in theme:
|
||||
if not (cfgParser.has_option(themeName, element) or
|
||||
# Skip warning for new elements.
|
||||
element.startswith(('context-', 'linenumber-'))):
|
||||
# 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 keyBindings 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>>',
|
||||
'<<run-custom>>',
|
||||
}
|
||||
|
||||
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.
|
||||
"""
|
||||
# TODO: = dict(sorted([(v-event, keys), ...]))?
|
||||
keyBindings={
|
||||
# virtual-event: list of key events.
|
||||
'<<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>'],
|
||||
'<<run-custom>>': ['<Shift-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):
|
||||
nonlocal 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):
|
||||
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)
|
||||
|
||||
_dump()
|
||||
# Run revised _dump() (700+ lines) as htest? More sorting.
|
||||
# Perhaps as window with tabs for textviews, making it config viewer.
|
||||
354
Dependencies/Python/Lib/idlelib/config_key.py
vendored
Normal file
@@ -0,0 +1,354 @@
|
||||
"""
|
||||
Dialog for building Tkinter accelerator key bindings
|
||||
"""
|
||||
from tkinter import Toplevel, Listbox, StringVar, TclError
|
||||
from tkinter.ttk import Frame, Button, Checkbutton, Entry, Label, Scrollbar
|
||||
from tkinter import messagebox
|
||||
from tkinter.simpledialog import _setup_dialog
|
||||
import string
|
||||
import sys
|
||||
|
||||
|
||||
FUNCTION_KEYS = ('F1', 'F2' ,'F3' ,'F4' ,'F5' ,'F6',
|
||||
'F7', 'F8' ,'F9' ,'F10' ,'F11' ,'F12')
|
||||
ALPHANUM_KEYS = tuple(string.ascii_lowercase + string.digits)
|
||||
PUNCTUATION_KEYS = tuple('~!@#%^&*()_-+={}[]|;:,.<>/?')
|
||||
WHITESPACE_KEYS = ('Tab', 'Space', 'Return')
|
||||
EDIT_KEYS = ('BackSpace', 'Delete', 'Insert')
|
||||
MOVE_KEYS = ('Home', 'End', 'Page Up', 'Page Down', 'Left Arrow',
|
||||
'Right Arrow', 'Up Arrow', 'Down Arrow')
|
||||
AVAILABLE_KEYS = (ALPHANUM_KEYS + PUNCTUATION_KEYS + FUNCTION_KEYS +
|
||||
WHITESPACE_KEYS + EDIT_KEYS + MOVE_KEYS)
|
||||
|
||||
|
||||
def translate_key(key, modifiers):
|
||||
"Translate from keycap symbol to the Tkinter keysym."
|
||||
mapping = {'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'}
|
||||
key = mapping.get(key, key)
|
||||
if 'Shift' in modifiers and key in string.ascii_lowercase:
|
||||
key = key.upper()
|
||||
return f'Key-{key}'
|
||||
|
||||
|
||||
class GetKeysFrame(Frame):
|
||||
|
||||
# Dialog title for invalid key sequence
|
||||
keyerror_title = 'Key Sequence Error'
|
||||
|
||||
def __init__(self, parent, action, current_key_sequences):
|
||||
"""
|
||||
parent - parent of this dialog
|
||||
action - the name of the virtual event these keys will be
|
||||
mapped to
|
||||
current_key_sequences - a list of all key sequence lists
|
||||
currently mapped to virtual events, for overlap checking
|
||||
"""
|
||||
super().__init__(parent)
|
||||
self['borderwidth'] = 2
|
||||
self['relief'] = 'sunken'
|
||||
self.parent = parent
|
||||
self.action = action
|
||||
self.current_key_sequences = current_key_sequences
|
||||
self.result = ''
|
||||
self.key_string = StringVar(self)
|
||||
self.key_string.set('')
|
||||
# Set self.modifiers, self.modifier_label.
|
||||
self.set_modifiers_for_platform()
|
||||
self.modifier_vars = []
|
||||
for modifier in self.modifiers:
|
||||
variable = StringVar(self)
|
||||
variable.set('')
|
||||
self.modifier_vars.append(variable)
|
||||
self.advanced = False
|
||||
self.create_widgets()
|
||||
|
||||
def showerror(self, *args, **kwargs):
|
||||
# Make testing easier. Replace in #30751.
|
||||
messagebox.showerror(*args, **kwargs)
|
||||
|
||||
def create_widgets(self):
|
||||
# Basic entry key sequence.
|
||||
self.frame_keyseq_basic = Frame(self, name='keyseq_basic')
|
||||
self.frame_keyseq_basic.grid(row=0, column=0, sticky='nsew',
|
||||
padx=5, pady=5)
|
||||
basic_title = Label(self.frame_keyseq_basic,
|
||||
text=f"New keys for '{self.action}' :")
|
||||
basic_title.pack(anchor='w')
|
||||
|
||||
basic_keys = Label(self.frame_keyseq_basic, justify='left',
|
||||
textvariable=self.key_string, relief='groove',
|
||||
borderwidth=2)
|
||||
basic_keys.pack(ipadx=5, ipady=5, fill='x')
|
||||
|
||||
# Basic entry controls.
|
||||
self.frame_controls_basic = Frame(self)
|
||||
self.frame_controls_basic.grid(row=1, column=0, sticky='nsew', padx=5)
|
||||
|
||||
# Basic entry modifiers.
|
||||
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.frame_controls_basic,
|
||||
command=self.build_key_string, text=label,
|
||||
variable=variable, onvalue=modifier, offvalue='')
|
||||
check.grid(row=0, column=column, padx=2, sticky='w')
|
||||
self.modifier_checkbuttons[modifier] = check
|
||||
column += 1
|
||||
|
||||
# Basic entry help text.
|
||||
help_basic = Label(self.frame_controls_basic, 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.)")
|
||||
help_basic.grid(row=1, column=0, columnspan=4, padx=2, sticky='w')
|
||||
|
||||
# Basic entry key list.
|
||||
self.list_keys_final = Listbox(self.frame_controls_basic, width=15,
|
||||
height=10, selectmode='single')
|
||||
self.list_keys_final.insert('end', *AVAILABLE_KEYS)
|
||||
self.list_keys_final.bind('<ButtonRelease-1>', self.final_key_selected)
|
||||
self.list_keys_final.grid(row=0, column=4, rowspan=4, sticky='ns')
|
||||
scroll_keys_final = Scrollbar(self.frame_controls_basic,
|
||||
orient='vertical',
|
||||
command=self.list_keys_final.yview)
|
||||
self.list_keys_final.config(yscrollcommand=scroll_keys_final.set)
|
||||
scroll_keys_final.grid(row=0, column=5, rowspan=4, sticky='ns')
|
||||
self.button_clear = Button(self.frame_controls_basic,
|
||||
text='Clear Keys',
|
||||
command=self.clear_key_seq)
|
||||
self.button_clear.grid(row=2, column=0, columnspan=4)
|
||||
|
||||
# Advanced entry key sequence.
|
||||
self.frame_keyseq_advanced = Frame(self, name='keyseq_advanced')
|
||||
self.frame_keyseq_advanced.grid(row=0, column=0, sticky='nsew',
|
||||
padx=5, pady=5)
|
||||
advanced_title = Label(self.frame_keyseq_advanced, justify='left',
|
||||
text=f"Enter new binding(s) for '{self.action}' :\n" +
|
||||
"(These bindings will not be checked for validity!)")
|
||||
advanced_title.pack(anchor='w')
|
||||
self.advanced_keys = Entry(self.frame_keyseq_advanced,
|
||||
textvariable=self.key_string)
|
||||
self.advanced_keys.pack(fill='x')
|
||||
|
||||
# Advanced entry help text.
|
||||
self.frame_help_advanced = Frame(self)
|
||||
self.frame_help_advanced.grid(row=1, column=0, sticky='nsew', padx=5)
|
||||
help_advanced = Label(self.frame_help_advanced, 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>." )
|
||||
help_advanced.grid(row=0, column=0, sticky='nsew')
|
||||
|
||||
# Switch between basic and advanced.
|
||||
self.button_level = Button(self, command=self.toggle_level,
|
||||
text='<< Basic Key Binding Entry')
|
||||
self.button_level.grid(row=2, column=0, stick='ew', padx=5, pady=5)
|
||||
self.toggle_level()
|
||||
|
||||
def set_modifiers_for_platform(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 toggle_level(self):
|
||||
"Toggle between basic and advanced keys."
|
||||
if self.button_level.cget('text').startswith('Advanced'):
|
||||
self.clear_key_seq()
|
||||
self.button_level.config(text='<< Basic Key Binding Entry')
|
||||
self.frame_keyseq_advanced.lift()
|
||||
self.frame_help_advanced.lift()
|
||||
self.advanced_keys.focus_set()
|
||||
self.advanced = True
|
||||
else:
|
||||
self.clear_key_seq()
|
||||
self.button_level.config(text='Advanced Key Binding Entry >>')
|
||||
self.frame_keyseq_basic.lift()
|
||||
self.frame_controls_basic.lift()
|
||||
self.advanced = False
|
||||
|
||||
def final_key_selected(self, event=None):
|
||||
"Handler for clicking on key in basic settings list."
|
||||
self.build_key_string()
|
||||
|
||||
def build_key_string(self):
|
||||
"Create formatted string of modifiers plus the key."
|
||||
keylist = modifiers = self.get_modifiers()
|
||||
final_key = self.list_keys_final.get('anchor')
|
||||
if final_key:
|
||||
final_key = translate_key(final_key, modifiers)
|
||||
keylist.append(final_key)
|
||||
self.key_string.set(f"<{'-'.join(keylist)}>")
|
||||
|
||||
def get_modifiers(self):
|
||||
"Return ordered list of modifiers that have been selected."
|
||||
mod_list = [variable.get() for variable in self.modifier_vars]
|
||||
return [mod for mod in mod_list if mod]
|
||||
|
||||
def clear_key_seq(self):
|
||||
"Clear modifiers and keys selection."
|
||||
self.list_keys_final.select_clear(0, 'end')
|
||||
self.list_keys_final.yview('moveto', '0.0')
|
||||
for variable in self.modifier_vars:
|
||||
variable.set('')
|
||||
self.key_string.set('')
|
||||
|
||||
def ok(self):
|
||||
self.result = ''
|
||||
keys = self.key_string.get().strip()
|
||||
if not keys:
|
||||
self.showerror(title=self.keyerror_title, parent=self,
|
||||
message="No key specified.")
|
||||
return
|
||||
if (self.advanced or self.keys_ok(keys)) and self.bind_ok(keys):
|
||||
self.result = keys
|
||||
return
|
||||
|
||||
def keys_ok(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.
|
||||
"""
|
||||
final_key = self.list_keys_final.get('anchor')
|
||||
modifiers = self.get_modifiers()
|
||||
title = self.keyerror_title
|
||||
key_sequences = [key for keylist in self.current_key_sequences
|
||||
for key in keylist]
|
||||
if not keys.endswith('>'):
|
||||
self.showerror(title, parent=self,
|
||||
message='Missing the final Key')
|
||||
elif (not modifiers
|
||||
and final_key not in FUNCTION_KEYS + MOVE_KEYS):
|
||||
self.showerror(title=title, parent=self,
|
||||
message='No modifier key(s) specified.')
|
||||
elif (modifiers == ['Shift']) \
|
||||
and (final_key not in
|
||||
FUNCTION_KEYS + MOVE_KEYS + ('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:
|
||||
return True
|
||||
return False
|
||||
|
||||
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
|
||||
|
||||
|
||||
class GetKeysWindow(Toplevel):
|
||||
|
||||
def __init__(self, parent, title, action, current_key_sequences,
|
||||
*, _htest=False, _utest=False):
|
||||
"""
|
||||
parent - parent of this dialog
|
||||
title - string which is the title of the popup dialog
|
||||
action - string, the name of the virtual event these keys will be
|
||||
mapped to
|
||||
current_key_sequences - list, a list of all key sequence lists
|
||||
currently mapped to virtual events, for overlap checking
|
||||
_htest - bool, change box location when running htest
|
||||
_utest - bool, do not wait when running unittest
|
||||
"""
|
||||
super().__init__(parent)
|
||||
self.withdraw() # Hide while setting geometry.
|
||||
self['borderwidth'] = 5
|
||||
self.resizable(height=False, width=False)
|
||||
# Needed for winfo_reqwidth().
|
||||
self.update_idletasks()
|
||||
# Center dialog over parent (or below htest box).
|
||||
x = (parent.winfo_rootx() +
|
||||
(parent.winfo_width()//2 - self.winfo_reqwidth()//2))
|
||||
y = (parent.winfo_rooty() +
|
||||
((parent.winfo_height()//2 - self.winfo_reqheight()//2)
|
||||
if not _htest else 150))
|
||||
self.geometry(f"+{x}+{y}")
|
||||
|
||||
self.title(title)
|
||||
self.frame = frame = GetKeysFrame(self, action, current_key_sequences)
|
||||
self.protocol("WM_DELETE_WINDOW", self.cancel)
|
||||
frame_buttons = Frame(self)
|
||||
self.button_ok = Button(frame_buttons, text='OK',
|
||||
width=8, command=self.ok)
|
||||
self.button_cancel = Button(frame_buttons, text='Cancel',
|
||||
width=8, command=self.cancel)
|
||||
self.button_ok.grid(row=0, column=0, padx=5, pady=5)
|
||||
self.button_cancel.grid(row=0, column=1, padx=5, pady=5)
|
||||
frame.pack(side='top', expand=True, fill='both')
|
||||
frame_buttons.pack(side='bottom', fill='x')
|
||||
|
||||
self.transient(parent)
|
||||
_setup_dialog(self)
|
||||
self.grab_set()
|
||||
if not _utest:
|
||||
self.deiconify() # Geometry set, unhide.
|
||||
self.wait_window()
|
||||
|
||||
@property
|
||||
def result(self):
|
||||
return self.frame.result
|
||||
|
||||
@result.setter
|
||||
def result(self, value):
|
||||
self.frame.result = value
|
||||
|
||||
def ok(self, event=None):
|
||||
self.frame.ok()
|
||||
self.grab_release()
|
||||
self.destroy()
|
||||
|
||||
def cancel(self, event=None):
|
||||
self.result = ''
|
||||
self.grab_release()
|
||||
self.destroy()
|
||||
|
||||
|
||||
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(GetKeysWindow)
|
||||
2408
Dependencies/Python/Lib/idlelib/configdialog.py
vendored
Normal file
602
Dependencies/Python/Lib/idlelib/debugger.py
vendored
Normal file
@@ -0,0 +1,602 @@
|
||||
"""Debug user code with a GUI interface to a subclass of bdb.Bdb.
|
||||
|
||||
The Idb idb and Debugger gui instances each need a reference to each
|
||||
other or to an rpc proxy for each other.
|
||||
|
||||
If IDLE is started with '-n', so that user code and idb both run in the
|
||||
IDLE process, Debugger is called without an idb. Debugger.__init__
|
||||
calls Idb with its incomplete self. Idb.__init__ stores gui and gui
|
||||
then stores idb.
|
||||
|
||||
If IDLE is started normally, so that user code executes in a separate
|
||||
process, debugger_r.start_remote_debugger is called, executing in the
|
||||
IDLE process. It calls 'start the debugger' in the remote process,
|
||||
which calls Idb with a gui proxy. Then Debugger is called in the IDLE
|
||||
for more.
|
||||
"""
|
||||
|
||||
import bdb
|
||||
import os
|
||||
|
||||
from tkinter import *
|
||||
from tkinter.ttk import Frame, Scrollbar
|
||||
|
||||
from idlelib import macosx
|
||||
from idlelib.scrolledlist import ScrolledList
|
||||
from idlelib.window import ListedToplevel
|
||||
|
||||
|
||||
class Idb(bdb.Bdb):
|
||||
"Supply user_line and user_exception functions for Bdb."
|
||||
|
||||
def __init__(self, gui):
|
||||
self.gui = gui # An instance of Debugger or proxy thereof.
|
||||
super().__init__()
|
||||
|
||||
def user_line(self, frame):
|
||||
"""Handle a user stopping or breaking at a line.
|
||||
|
||||
Convert frame to a string and send it to gui.
|
||||
"""
|
||||
if _in_rpc_code(frame):
|
||||
self.set_step()
|
||||
return
|
||||
message = _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, exc_info):
|
||||
"""Handle an the occurrence of an exception."""
|
||||
if _in_rpc_code(frame):
|
||||
self.set_step()
|
||||
return
|
||||
message = _frame2message(frame)
|
||||
self.gui.interaction(message, frame, exc_info)
|
||||
|
||||
def _in_rpc_code(frame):
|
||||
"Determine if debugger is within RPC code."
|
||||
if frame.f_code.co_filename.count('rpc.py'):
|
||||
return True # Skip this frame.
|
||||
else:
|
||||
prev_frame = frame.f_back
|
||||
if prev_frame is None:
|
||||
return False
|
||||
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 _in_rpc_code(prev_frame)
|
||||
|
||||
def _frame2message(frame):
|
||||
"""Return a message string for frame."""
|
||||
code = frame.f_code
|
||||
filename = code.co_filename
|
||||
lineno = frame.f_lineno
|
||||
basename = os.path.basename(filename)
|
||||
message = f"{basename}:{lineno}"
|
||||
if code.co_name != "?":
|
||||
message = f"{message}: {code.co_name}()"
|
||||
return message
|
||||
|
||||
|
||||
class Debugger:
|
||||
"""The debugger interface.
|
||||
|
||||
This class handles the drawing of the debugger window and
|
||||
the interactions with the underlying debugger session.
|
||||
"""
|
||||
vstack = None
|
||||
vsource = None
|
||||
vlocals = None
|
||||
vglobals = None
|
||||
stackviewer = None
|
||||
localsviewer = None
|
||||
globalsviewer = None
|
||||
|
||||
def __init__(self, pyshell, idb=None):
|
||||
"""Instantiate and draw a debugger window.
|
||||
|
||||
:param pyshell: An instance of the PyShell Window
|
||||
:type pyshell: :class:`idlelib.pyshell.PyShell`
|
||||
|
||||
:param idb: An instance of the IDLE debugger (optional)
|
||||
:type idb: :class:`idlelib.debugger.Idb`
|
||||
"""
|
||||
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 = False
|
||||
self.nesting_level = 0
|
||||
|
||||
def run(self, *args):
|
||||
"""Run the debugger."""
|
||||
# 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 = True
|
||||
return self.idb.run(*args)
|
||||
finally:
|
||||
self.interacting = False
|
||||
|
||||
def close(self, event=None):
|
||||
"""Close the debugger and window."""
|
||||
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):
|
||||
"""Draw the debugger gui on the screen."""
|
||||
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:
|
||||
# TODO redo entire section, tries not needed.
|
||||
m1 = f"{m1}: {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')
|
||||
|
||||
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()
|
||||
|
||||
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(self, filename, lineno):
|
||||
"""Set a filename-lineno breakpoint in the debugger.
|
||||
|
||||
Called from self.load_breakpoints and EW.setbreakpoint
|
||||
"""
|
||||
self.idb.set_break(filename, lineno)
|
||||
|
||||
def clear_breakpoint(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(filename, lineno)
|
||||
except AttributeError:
|
||||
continue
|
||||
|
||||
|
||||
class StackViewer(ScrolledList):
|
||||
"Code stack viewer for debugger GUI."
|
||||
|
||||
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:
|
||||
"Global/local namespace viewer for debugger GUI."
|
||||
|
||||
def __init__(self, master, title, odict=None): # XXX odict never passed.
|
||||
width = 0
|
||||
height = 40
|
||||
if odict:
|
||||
height = 20*len(odict) # 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(odict)
|
||||
|
||||
prev_odict = -1 # Needed for initial comparison below.
|
||||
|
||||
def load_dict(self, odict, force=0, rpc_client=None):
|
||||
if odict is self.prev_odict and not force:
|
||||
return
|
||||
subframe = self.subframe
|
||||
frame = self.frame
|
||||
for c in list(subframe.children.values()):
|
||||
c.destroy()
|
||||
self.prev_odict = None
|
||||
if not odict:
|
||||
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.
|
||||
# TODO recheck above; see debugger_r 159ff, debugobj 60.
|
||||
keys_list = odict.keys()
|
||||
names = sorted(keys_list)
|
||||
|
||||
row = 0
|
||||
for name in names:
|
||||
value = odict[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.prev_odict = odict
|
||||
# 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?
|
||||
390
Dependencies/Python/Lib/idlelib/debugger_r.py
vendored
Normal file
@@ -0,0 +1,390 @@
|
||||
"""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 reprlib
|
||||
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]
|
||||
gdict = frame.f_globals
|
||||
did = id(gdict)
|
||||
dicttable[did] = gdict
|
||||
return did
|
||||
|
||||
def frame_locals(self, fid):
|
||||
frame = frametable[fid]
|
||||
ldict = frame.f_locals
|
||||
did = id(ldict)
|
||||
dicttable[did] = ldict
|
||||
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")
|
||||
## return dicttable[did].keys()
|
||||
|
||||
### Needed until dict_keys type is finished and pickleable.
|
||||
# xxx finished. pickleable?
|
||||
### Will probably need to extend rpc.py:SocketIO._proxify at that time.
|
||||
def dict_keys_list(self, did):
|
||||
return list(dicttable[did].keys())
|
||||
|
||||
def dict_item(self, did, key):
|
||||
value = dicttable[did][key]
|
||||
return reprlib.repr(value) # Can't pickle module 'builtins'.
|
||||
|
||||
#----------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_r', verbosity=2, exit=False)
|
||||
146
Dependencies/Python/Lib/idlelib/debugobj.py
vendored
Normal file
@@ -0,0 +1,146 @@
|
||||
"""Define tree items for debug stackviewer, which is only user.
|
||||
"""
|
||||
# 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(f"{key!r}:", value, setfunction)
|
||||
sublist.append(item)
|
||||
return sublist
|
||||
|
||||
class DictTreeItem(SequenceTreeItem):
|
||||
def keys(self):
|
||||
# TODO return sorted(self.object)
|
||||
keys = list(self.object)
|
||||
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 _debug_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(_debug_object_browser)
|
||||
41
Dependencies/Python/Lib/idlelib/debugobj_r.py
vendored
Normal 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)
|
||||
34
Dependencies/Python/Lib/idlelib/delegator.py
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
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 delegator 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)
|
||||
57
Dependencies/Python/Lib/idlelib/dynoption.py
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
"""
|
||||
OptionMenu widget modified to allow dynamic menu reconfiguration
|
||||
and setting of highlightthickness
|
||||
"""
|
||||
from tkinter import OptionMenu, _setit, StringVar, Button
|
||||
|
||||
class DynOptionMenu(OptionMenu):
|
||||
"""Add SetMenu and highlightthickness to OptionMenu.
|
||||
|
||||
Highlightthickness adds space around menu button.
|
||||
"""
|
||||
def __init__(self, master, variable, value, *values, **kwargs):
|
||||
highlightthickness = kwargs.pop('highlightthickness', None)
|
||||
OptionMenu.__init__(self, master, variable, value, *values, **kwargs)
|
||||
self['highlightthickness'] = highlightthickness
|
||||
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("Test 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",
|
||||
highlightthickness=5)
|
||||
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__':
|
||||
# Only module without unittests because of intention to replace.
|
||||
from idlelib.idle_test.htest import run
|
||||
run(_dyn_option_menu)
|
||||
1767
Dependencies/Python/Lib/idlelib/editor.py
vendored
Normal file
83
Dependencies/Python/Lib/idlelib/extend.txt
vendored
Normal 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. <<z-in>>, and
|
||||
corresponding methods, e.g. z_in_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 config-extensions.def)
|
||||
|
||||
Here is a partial example from zzdummy.py:
|
||||
|
||||
class ZzDummy:
|
||||
|
||||
menudefs = [
|
||||
('format', [
|
||||
('Z in', '<<z-in>>'),
|
||||
('Z out', '<<z-out>>'),
|
||||
] )
|
||||
]
|
||||
|
||||
def __init__(self, editwin):
|
||||
self.editwin = editwin
|
||||
|
||||
def z_in_event(self, event=None):
|
||||
"...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.
|
||||
132
Dependencies/Python/Lib/idlelib/filelist.py
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
"idlelib.filelist"
|
||||
|
||||
import os
|
||||
from tkinter import messagebox
|
||||
|
||||
|
||||
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:
|
||||
messagebox.showerror(
|
||||
"File Error",
|
||||
f"{filename!r} is a directory.",
|
||||
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
|
||||
messagebox.showerror(
|
||||
"Name Conflict",
|
||||
f"You now have multiple edit windows open for {filename!r}",
|
||||
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()
|
||||
426
Dependencies/Python/Lib/idlelib/format.py
vendored
Normal file
@@ -0,0 +1,426 @@
|
||||
"""Format all or a selected region (line slice) of text.
|
||||
|
||||
Region formatting options: paragraph, comment block, indent, deindent,
|
||||
comment, uncomment, tabify, and untabify.
|
||||
|
||||
File renamed from paragraph.py with functions added from editor.py.
|
||||
"""
|
||||
import re
|
||||
from tkinter.messagebox import askyesno
|
||||
from tkinter.simpledialog import askinteger
|
||||
from idlelib.config import idleConf
|
||||
|
||||
|
||||
class FormatParagraph:
|
||||
"""Format a paragraph, comment block, or selection to a max width.
|
||||
|
||||
Does basic, standard text formatting, and also understands Python
|
||||
comment blocks. Thus, for editing Python source code, this
|
||||
extension is really only suitable for reformatting these comment
|
||||
blocks or triple-quoted strings.
|
||||
|
||||
Known problems with comment reformatting:
|
||||
* If there is a selection marked, and the first line of the
|
||||
selection is not complete, the block will probably not be detected
|
||||
as comments, and will have the normal "text formatting" rules
|
||||
applied.
|
||||
* If a comment block has leading whitespace that mixes tabs and
|
||||
spaces, they will not be considered part of the same block.
|
||||
* Fancy comments, like this bulleted list, aren't handled :-)
|
||||
"""
|
||||
def __init__(self, editwin):
|
||||
self.editwin = editwin
|
||||
|
||||
@classmethod
|
||||
def reload(cls):
|
||||
cls.max_width = idleConf.GetOption('extensions', 'FormatParagraph',
|
||||
'max-width', type='int', default=72)
|
||||
|
||||
def close(self):
|
||||
self.editwin = None
|
||||
|
||||
def format_paragraph_event(self, event, limit=None):
|
||||
"""Formats paragraph to a max width specified in idleConf.
|
||||
|
||||
If text is selected, format_paragraph_event will start breaking lines
|
||||
at the max width, starting from the beginning selection.
|
||||
|
||||
If no text is selected, format_paragraph_event uses the current
|
||||
cursor location to determine the paragraph (lines of text surrounded
|
||||
by blank lines) and formats it.
|
||||
|
||||
The length limit parameter is for testing with a known value.
|
||||
"""
|
||||
limit = self.max_width if limit is None else limit
|
||||
text = self.editwin.text
|
||||
first, last = self.editwin.get_selection_indices()
|
||||
if first and last:
|
||||
data = text.get(first, last)
|
||||
comment_header = get_comment_header(data)
|
||||
else:
|
||||
first, last, comment_header, data = \
|
||||
find_paragraph(text, text.index("insert"))
|
||||
if comment_header:
|
||||
newdata = reformat_comment(data, limit, comment_header)
|
||||
else:
|
||||
newdata = reformat_paragraph(data, limit)
|
||||
text.tag_remove("sel", "1.0", "end")
|
||||
|
||||
if newdata != data:
|
||||
text.mark_set("insert", first)
|
||||
text.undo_block_start()
|
||||
text.delete(first, last)
|
||||
text.insert(first, newdata)
|
||||
text.undo_block_stop()
|
||||
else:
|
||||
text.mark_set("insert", last)
|
||||
text.see("insert")
|
||||
return "break"
|
||||
|
||||
|
||||
FormatParagraph.reload()
|
||||
|
||||
def find_paragraph(text, mark):
|
||||
"""Returns the start/stop indices enclosing the paragraph that mark is in.
|
||||
|
||||
Also returns the comment format string, if any, and paragraph of text
|
||||
between the start/stop indices.
|
||||
"""
|
||||
lineno, col = map(int, mark.split("."))
|
||||
line = text.get("%d.0" % lineno, "%d.end" % lineno)
|
||||
|
||||
# Look for start of next paragraph if the index passed in is a blank line
|
||||
while text.compare("%d.0" % lineno, "<", "end") and is_all_white(line):
|
||||
lineno = lineno + 1
|
||||
line = text.get("%d.0" % lineno, "%d.end" % lineno)
|
||||
first_lineno = lineno
|
||||
comment_header = get_comment_header(line)
|
||||
comment_header_len = len(comment_header)
|
||||
|
||||
# Once start line found, search for end of paragraph (a blank line)
|
||||
while get_comment_header(line)==comment_header and \
|
||||
not is_all_white(line[comment_header_len:]):
|
||||
lineno = lineno + 1
|
||||
line = text.get("%d.0" % lineno, "%d.end" % lineno)
|
||||
last = "%d.0" % lineno
|
||||
|
||||
# Search back to beginning of paragraph (first blank line before)
|
||||
lineno = first_lineno - 1
|
||||
line = text.get("%d.0" % lineno, "%d.end" % lineno)
|
||||
while lineno > 0 and \
|
||||
get_comment_header(line)==comment_header and \
|
||||
not is_all_white(line[comment_header_len:]):
|
||||
lineno = lineno - 1
|
||||
line = text.get("%d.0" % lineno, "%d.end" % lineno)
|
||||
first = "%d.0" % (lineno+1)
|
||||
|
||||
return first, last, comment_header, text.get(first, last)
|
||||
|
||||
# This should perhaps be replaced with textwrap.wrap
|
||||
def reformat_paragraph(data, limit):
|
||||
"""Return data reformatted to specified width (limit)."""
|
||||
lines = data.split("\n")
|
||||
i = 0
|
||||
n = len(lines)
|
||||
while i < n and is_all_white(lines[i]):
|
||||
i = i+1
|
||||
if i >= n:
|
||||
return data
|
||||
indent1 = get_indent(lines[i])
|
||||
if i+1 < n and not is_all_white(lines[i+1]):
|
||||
indent2 = get_indent(lines[i+1])
|
||||
else:
|
||||
indent2 = indent1
|
||||
new = lines[:i]
|
||||
partial = indent1
|
||||
while i < n and not is_all_white(lines[i]):
|
||||
# XXX Should take double space after period (etc.) into account
|
||||
words = re.split(r"(\s+)", lines[i])
|
||||
for j in range(0, len(words), 2):
|
||||
word = words[j]
|
||||
if not word:
|
||||
continue # Can happen when line ends in whitespace
|
||||
if len((partial + word).expandtabs()) > limit and \
|
||||
partial != indent1:
|
||||
new.append(partial.rstrip())
|
||||
partial = indent2
|
||||
partial = partial + word + " "
|
||||
if j+1 < len(words) and words[j+1] != " ":
|
||||
partial = partial + " "
|
||||
i = i+1
|
||||
new.append(partial.rstrip())
|
||||
# XXX Should reformat remaining paragraphs as well
|
||||
new.extend(lines[i:])
|
||||
return "\n".join(new)
|
||||
|
||||
def reformat_comment(data, limit, comment_header):
|
||||
"""Return data reformatted to specified width with comment header."""
|
||||
|
||||
# Remove header from the comment lines
|
||||
lc = len(comment_header)
|
||||
data = "\n".join(line[lc:] for line in data.split("\n"))
|
||||
# Reformat to maxformatwidth chars or a 20 char width,
|
||||
# whichever is greater.
|
||||
format_width = max(limit - len(comment_header), 20)
|
||||
newdata = reformat_paragraph(data, format_width)
|
||||
# re-split and re-insert the comment header.
|
||||
newdata = newdata.split("\n")
|
||||
# If the block ends in a \n, we don't want the comment prefix
|
||||
# inserted after it. (Im not sure it makes sense to reformat a
|
||||
# comment block that is not made of complete lines, but whatever!)
|
||||
# Can't think of a clean solution, so we hack away
|
||||
block_suffix = ""
|
||||
if not newdata[-1]:
|
||||
block_suffix = "\n"
|
||||
newdata = newdata[:-1]
|
||||
return '\n'.join(comment_header+line for line in newdata) + block_suffix
|
||||
|
||||
def is_all_white(line):
|
||||
"""Return True if line is empty or all whitespace."""
|
||||
|
||||
return re.match(r"^\s*$", line) is not None
|
||||
|
||||
def get_indent(line):
|
||||
"""Return the initial space or tab indent of line."""
|
||||
return re.match(r"^([ \t]*)", line).group()
|
||||
|
||||
def get_comment_header(line):
|
||||
"""Return string with leading whitespace and '#' from line or ''.
|
||||
|
||||
A null return indicates that the line is not a comment line. A non-
|
||||
null return, such as ' #', will be used to find the other lines of
|
||||
a comment block with the same indent.
|
||||
"""
|
||||
m = re.match(r"^([ \t]*#*)", line)
|
||||
if m is None: return ""
|
||||
return m.group(1)
|
||||
|
||||
|
||||
# Copied from editor.py; importing it would cause an import cycle.
|
||||
_line_indent_re = re.compile(r'[ \t]*')
|
||||
|
||||
def get_line_indent(line, tabwidth):
|
||||
"""Return a line's indentation as (# chars, effective # of spaces).
|
||||
|
||||
The effective # of spaces is the length after properly "expanding"
|
||||
the tabs into spaces, as done by str.expandtabs(tabwidth).
|
||||
"""
|
||||
m = _line_indent_re.match(line)
|
||||
return m.end(), len(m.group().expandtabs(tabwidth))
|
||||
|
||||
|
||||
class FormatRegion:
|
||||
"Format selected text (region)."
|
||||
|
||||
def __init__(self, editwin):
|
||||
self.editwin = editwin
|
||||
|
||||
def get_region(self):
|
||||
"""Return line information about the selected text region.
|
||||
|
||||
If text is selected, the first and last indices will be
|
||||
for the selection. If there is no text selected, the
|
||||
indices will be the current cursor location.
|
||||
|
||||
Return a tuple containing (first index, last index,
|
||||
string representation of text, list of text lines).
|
||||
"""
|
||||
text = self.editwin.text
|
||||
first, last = self.editwin.get_selection_indices()
|
||||
if first and last:
|
||||
head = text.index(first + " linestart")
|
||||
tail = text.index(last + "-1c lineend +1c")
|
||||
else:
|
||||
head = text.index("insert linestart")
|
||||
tail = text.index("insert lineend +1c")
|
||||
chars = text.get(head, tail)
|
||||
lines = chars.split("\n")
|
||||
return head, tail, chars, lines
|
||||
|
||||
def set_region(self, head, tail, chars, lines):
|
||||
"""Replace the text between the given indices.
|
||||
|
||||
Args:
|
||||
head: Starting index of text to replace.
|
||||
tail: Ending index of text to replace.
|
||||
chars: Expected to be string of current text
|
||||
between head and tail.
|
||||
lines: List of new lines to insert between head
|
||||
and tail.
|
||||
"""
|
||||
text = self.editwin.text
|
||||
newchars = "\n".join(lines)
|
||||
if newchars == chars:
|
||||
text.bell()
|
||||
return
|
||||
text.tag_remove("sel", "1.0", "end")
|
||||
text.mark_set("insert", head)
|
||||
text.undo_block_start()
|
||||
text.delete(head, tail)
|
||||
text.insert(head, newchars)
|
||||
text.undo_block_stop()
|
||||
text.tag_add("sel", head, "insert")
|
||||
|
||||
def indent_region_event(self, event=None):
|
||||
"Indent region by indentwidth spaces."
|
||||
head, tail, chars, lines = self.get_region()
|
||||
for pos in range(len(lines)):
|
||||
line = lines[pos]
|
||||
if line:
|
||||
raw, effective = get_line_indent(line, self.editwin.tabwidth)
|
||||
effective = effective + self.editwin.indentwidth
|
||||
lines[pos] = self.editwin._make_blanks(effective) + line[raw:]
|
||||
self.set_region(head, tail, chars, lines)
|
||||
return "break"
|
||||
|
||||
def dedent_region_event(self, event=None):
|
||||
"Dedent region by indentwidth spaces."
|
||||
head, tail, chars, lines = self.get_region()
|
||||
for pos in range(len(lines)):
|
||||
line = lines[pos]
|
||||
if line:
|
||||
raw, effective = get_line_indent(line, self.editwin.tabwidth)
|
||||
effective = max(effective - self.editwin.indentwidth, 0)
|
||||
lines[pos] = self.editwin._make_blanks(effective) + line[raw:]
|
||||
self.set_region(head, tail, chars, lines)
|
||||
return "break"
|
||||
|
||||
def comment_region_event(self, event=None):
|
||||
"""Comment out each line in region.
|
||||
|
||||
## is appended to the beginning of each line to comment it out.
|
||||
"""
|
||||
head, tail, chars, lines = self.get_region()
|
||||
for pos in range(len(lines) - 1):
|
||||
line = lines[pos]
|
||||
lines[pos] = '##' + line
|
||||
self.set_region(head, tail, chars, lines)
|
||||
return "break"
|
||||
|
||||
def uncomment_region_event(self, event=None):
|
||||
"""Uncomment each line in region.
|
||||
|
||||
Remove ## or # in the first positions of a line. If the comment
|
||||
is not in the beginning position, this command will have no effect.
|
||||
"""
|
||||
head, tail, chars, lines = self.get_region()
|
||||
for pos in range(len(lines)):
|
||||
line = lines[pos]
|
||||
if not line:
|
||||
continue
|
||||
if line[:2] == '##':
|
||||
line = line[2:]
|
||||
elif line[:1] == '#':
|
||||
line = line[1:]
|
||||
lines[pos] = line
|
||||
self.set_region(head, tail, chars, lines)
|
||||
return "break"
|
||||
|
||||
def tabify_region_event(self, event=None):
|
||||
"Convert leading spaces to tabs for each line in selected region."
|
||||
head, tail, chars, lines = self.get_region()
|
||||
tabwidth = self._asktabwidth()
|
||||
if tabwidth is None:
|
||||
return
|
||||
for pos in range(len(lines)):
|
||||
line = lines[pos]
|
||||
if line:
|
||||
raw, effective = get_line_indent(line, tabwidth)
|
||||
ntabs, nspaces = divmod(effective, tabwidth)
|
||||
lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
|
||||
self.set_region(head, tail, chars, lines)
|
||||
return "break"
|
||||
|
||||
def untabify_region_event(self, event=None):
|
||||
"Expand tabs to spaces for each line in region."
|
||||
head, tail, chars, lines = self.get_region()
|
||||
tabwidth = self._asktabwidth()
|
||||
if tabwidth is None:
|
||||
return
|
||||
for pos in range(len(lines)):
|
||||
lines[pos] = lines[pos].expandtabs(tabwidth)
|
||||
self.set_region(head, tail, chars, lines)
|
||||
return "break"
|
||||
|
||||
def _asktabwidth(self):
|
||||
"Return value for tab width."
|
||||
return askinteger(
|
||||
"Tab width",
|
||||
"Columns per tab? (2-16)",
|
||||
parent=self.editwin.text,
|
||||
initialvalue=self.editwin.indentwidth,
|
||||
minvalue=2,
|
||||
maxvalue=16)
|
||||
|
||||
|
||||
class Indents:
|
||||
"Change future indents."
|
||||
|
||||
def __init__(self, editwin):
|
||||
self.editwin = editwin
|
||||
|
||||
def toggle_tabs_event(self, event):
|
||||
editwin = self.editwin
|
||||
usetabs = editwin.usetabs
|
||||
if askyesno(
|
||||
"Toggle tabs",
|
||||
"Turn tabs " + ("on", "off")[usetabs] +
|
||||
"?\nIndent width " +
|
||||
("will be", "remains at")[usetabs] + " 8." +
|
||||
"\n Note: a tab is always 8 columns",
|
||||
parent=editwin.text):
|
||||
editwin.usetabs = not usetabs
|
||||
# Try to prevent inconsistent indentation.
|
||||
# User must change indent width manually after using tabs.
|
||||
editwin.indentwidth = 8
|
||||
return "break"
|
||||
|
||||
def change_indentwidth_event(self, event):
|
||||
editwin = self.editwin
|
||||
new = askinteger(
|
||||
"Indent width",
|
||||
"New indent width (2-16)\n(Always use 8 when using tabs)",
|
||||
parent=editwin.text,
|
||||
initialvalue=editwin.indentwidth,
|
||||
minvalue=2,
|
||||
maxvalue=16)
|
||||
if new and new != editwin.indentwidth and not editwin.usetabs:
|
||||
editwin.indentwidth = new
|
||||
return "break"
|
||||
|
||||
|
||||
class Rstrip: # 'Strip Trailing Whitespace" on "Format" menu.
|
||||
def __init__(self, editwin):
|
||||
self.editwin = editwin
|
||||
|
||||
def do_rstrip(self, event=None):
|
||||
text = self.editwin.text
|
||||
undo = self.editwin.undo
|
||||
undo.undo_block_start()
|
||||
|
||||
end_line = int(float(text.index('end')))
|
||||
for cur in range(1, end_line):
|
||||
txt = text.get('%i.0' % cur, '%i.end' % cur)
|
||||
raw = len(txt)
|
||||
cut = len(txt.rstrip())
|
||||
# Since text.delete() marks file as changed, even if not,
|
||||
# only call it when needed to actually delete something.
|
||||
if cut < raw:
|
||||
text.delete('%i.%i' % (cur, cut), '%i.end' % cur)
|
||||
|
||||
if (text.get('end-2c') == '\n' # File ends with at least 1 newline;
|
||||
and not hasattr(self.editwin, 'interp')): # & is not Shell.
|
||||
# Delete extra user endlines.
|
||||
while (text.index('end-1c') > '1.0' # Stop if file empty.
|
||||
and text.get('end-3c') == '\n'):
|
||||
text.delete('end-3c')
|
||||
# Because tk indexes are slice indexes and never raise,
|
||||
# a file with only newlines will be emptied.
|
||||
# patchcheck.py does the same.
|
||||
|
||||
undo.undo_block_stop()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from unittest import main
|
||||
main('idlelib.idle_test.test_format', verbosity=2, exit=False)
|
||||
223
Dependencies/Python/Lib/idlelib/grep.py
vendored
Normal file
@@ -0,0 +1,223 @@
|
||||
"""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 # Frame imported in ...Base
|
||||
|
||||
from idlelib.searchbase import SearchDialogBase
|
||||
from idlelib import searchengine
|
||||
|
||||
# Importing OutputWindow here fails due to import loop
|
||||
# EditorWindow -> GrepDialog -> OutputWindow -> EditorWindow
|
||||
|
||||
|
||||
def grep(text, io=None, flist=None):
|
||||
"""Open the Find in Files dialog.
|
||||
|
||||
Module-level function to access the singleton GrepDialog
|
||||
instance and open the dialog. If text is selected, it is
|
||||
used as the search phrase; otherwise, the previous entry
|
||||
is used.
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def walk_error(msg):
|
||||
"Handle os.walk error."
|
||||
print(msg)
|
||||
|
||||
|
||||
def findfiles(folder, pattern, recursive):
|
||||
"""Generate file names in dir that match pattern.
|
||||
|
||||
Args:
|
||||
folder: Root directory to search.
|
||||
pattern: File pattern to match.
|
||||
recursive: True to include subdirectories.
|
||||
"""
|
||||
for dirpath, _, filenames in os.walk(folder, onerror=walk_error):
|
||||
yield from (os.path.join(dirpath, name)
|
||||
for name in filenames
|
||||
if fnmatch.fnmatch(name, pattern))
|
||||
if not recursive:
|
||||
break
|
||||
|
||||
|
||||
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:
|
||||
flist: filelist.Filelist instance for OutputWindow parent.
|
||||
globvar: String value of Entry widget for path to search.
|
||||
globent: Entry widget for globvar. Created in
|
||||
create_entries().
|
||||
recvar: Boolean value of Checkbutton widget for
|
||||
traversing through subdirectories.
|
||||
"""
|
||||
super().__init__(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.
|
||||
|
||||
Extend the SearchDialogBase open() to set the initial value
|
||||
for globvar.
|
||||
|
||||
Args:
|
||||
text: Multicall object containing the text information.
|
||||
searchphrase: String phrase to search.
|
||||
io: iomenu.IOBinding instance containing file path.
|
||||
"""
|
||||
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 Files."
|
||||
SearchDialogBase.create_command_buttons(self)
|
||||
self.make_button("Search Files", self.default_command, isdef=True)
|
||||
|
||||
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).
|
||||
|
||||
Args:
|
||||
prog: The compiled, cooked search pattern.
|
||||
path: String containing the search path.
|
||||
"""
|
||||
folder, filepat = os.path.split(path)
|
||||
if not folder:
|
||||
folder = os.curdir
|
||||
filelist = sorted(findfiles(folder, filepat, self.recvar.get()))
|
||||
self.close()
|
||||
pat = self.engine.getpat()
|
||||
print(f"Searching {pat!r} in {path} ...")
|
||||
hits = 0
|
||||
try:
|
||||
for fn in filelist:
|
||||
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 _grep_dialog(parent): # htest #
|
||||
from tkinter import Toplevel, Text, SEL
|
||||
from tkinter.ttk import Frame, 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)
|
||||
frame = Frame(top)
|
||||
frame.pack()
|
||||
text = Text(frame, height=5)
|
||||
text.pack()
|
||||
text.insert('1.0', 'import grep')
|
||||
|
||||
def show_grep_dialog():
|
||||
text.tag_add(SEL, "1.0", '1.end')
|
||||
grep(text, flist=flist)
|
||||
text.tag_remove(SEL, "1.0", '1.end')
|
||||
|
||||
button = Button(frame, 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)
|
||||
805
Dependencies/Python/Lib/idlelib/help.html
vendored
Normal file
@@ -0,0 +1,805 @@
|
||||
<section id="idle">
|
||||
<span id="idle-python-editor-and-shell"></span><h1>IDLE — Python editor and shell<a class="headerlink" href="#idle" title="Link to this heading">¶</a></h1>
|
||||
<p><strong>Source code:</strong> <a class="extlink-source reference external" href="https://github.com/python/cpython/tree/main/Lib/idlelib/">Lib/idlelib/</a></p>
|
||||
<span class="target" id="index-0"></span><hr class="docutils" />
|
||||
<p>IDLE is Python’s Integrated Development and Learning Environment.</p>
|
||||
<p>IDLE has the following features:</p>
|
||||
<ul class="simple">
|
||||
<li><p>cross-platform: works mostly the same on Windows, Unix, and macOS</p></li>
|
||||
<li><p>Python shell window (interactive interpreter) with colorizing
|
||||
of code input, output, and error messages</p></li>
|
||||
<li><p>multi-window text editor with multiple undo, Python colorizing,
|
||||
smart indent, call tips, auto completion, and other features</p></li>
|
||||
<li><p>search within any window, replace within editor windows, and search
|
||||
through multiple files (grep)</p></li>
|
||||
<li><p>debugger with persistent breakpoints, stepping, and viewing
|
||||
of global and local namespaces</p></li>
|
||||
<li><p>configuration, browsers, and other dialogs</p></li>
|
||||
</ul>
|
||||
<section id="menus">
|
||||
<h2>Menus<a class="headerlink" href="#menus" title="Link to this heading">¶</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 => 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 guidelines.</p>
|
||||
<section id="file-menu-shell-and-editor">
|
||||
<h3>File menu (Shell and Editor)<a class="headerlink" href="#file-menu-shell-and-editor" title="Link to this heading">¶</a></h3>
|
||||
<dl class="simple">
|
||||
<dt>New File</dt><dd><p>Create a new file editing window.</p>
|
||||
</dd>
|
||||
<dt>Open…</dt><dd><p>Open an existing file with an Open dialog.</p>
|
||||
</dd>
|
||||
<dt>Open Module…</dt><dd><p>Open an existing module (searches sys.path).</p>
|
||||
</dd>
|
||||
<dt>Recent Files</dt><dd><p>Open a list of recent files. Click one to open it.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
<dl class="simple" id="index-1">
|
||||
<dt>Module Browser</dt><dd><p>Show functions, classes, and methods in the current Editor file in a
|
||||
tree structure. In the shell, open a module first.</p>
|
||||
</dd>
|
||||
<dt>Path Browser</dt><dd><p>Show sys.path directories, modules, functions, classes and methods in a
|
||||
tree structure.</p>
|
||||
</dd>
|
||||
<dt>Save</dt><dd><p>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.</p>
|
||||
</dd>
|
||||
<dt>Save As…</dt><dd><p>Save the current window with a Save As dialog. The file saved becomes the
|
||||
new associated file for the window. (If your file namager is set to hide
|
||||
extensions, the current extension will be omitted in the file name box.
|
||||
If the new filename has no ‘.’, ‘.py’ and ‘.txt’ will be added for Python
|
||||
and text files, except that on macOS Aqua,’.py’ is added for all files.)</p>
|
||||
</dd>
|
||||
<dt>Save Copy As…</dt><dd><p>Save the current window to different file without changing the associated
|
||||
file. (See Save As note above about filename extensions.)</p>
|
||||
</dd>
|
||||
<dt>Print Window</dt><dd><p>Print the current window to the default printer.</p>
|
||||
</dd>
|
||||
<dt>Close Window</dt><dd><p>Close the current window (if an unsaved editor, ask to save; if an unsaved
|
||||
Shell, ask to quit execution). Calling <code class="docutils literal notranslate"><span class="pre">exit()</span></code> or <code class="docutils literal notranslate"><span class="pre">close()</span></code> in the Shell
|
||||
window also closes Shell. If this is the only window, also exit IDLE.</p>
|
||||
</dd>
|
||||
<dt>Exit IDLE</dt><dd><p>Close all windows and quit IDLE (ask to save unsaved edit windows).</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</section>
|
||||
<section id="edit-menu-shell-and-editor">
|
||||
<h3>Edit menu (Shell and Editor)<a class="headerlink" href="#edit-menu-shell-and-editor" title="Link to this heading">¶</a></h3>
|
||||
<dl class="simple">
|
||||
<dt>Undo</dt><dd><p>Undo the last change to the current window. A maximum of 1000 changes may
|
||||
be undone.</p>
|
||||
</dd>
|
||||
<dt>Redo</dt><dd><p>Redo the last undone change to the current window.</p>
|
||||
</dd>
|
||||
<dt>Select All</dt><dd><p>Select the entire contents of the current window.</p>
|
||||
</dd>
|
||||
<dt>Cut</dt><dd><p>Copy selection into the system-wide clipboard; then delete the selection.</p>
|
||||
</dd>
|
||||
<dt>Copy</dt><dd><p>Copy selection into the system-wide clipboard.</p>
|
||||
</dd>
|
||||
<dt>Paste</dt><dd><p>Insert contents of the system-wide clipboard into the current window.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
<p>The clipboard functions are also available in context menus.</p>
|
||||
<dl class="simple">
|
||||
<dt>Find…</dt><dd><p>Open a search dialog with many options</p>
|
||||
</dd>
|
||||
<dt>Find Again</dt><dd><p>Repeat the last search, if there is one.</p>
|
||||
</dd>
|
||||
<dt>Find Selection</dt><dd><p>Search for the currently selected string, if there is one.</p>
|
||||
</dd>
|
||||
<dt>Find in Files…</dt><dd><p>Open a file search dialog. Put results in a new output window.</p>
|
||||
</dd>
|
||||
<dt>Replace…</dt><dd><p>Open a search-and-replace dialog.</p>
|
||||
</dd>
|
||||
<dt>Go to Line</dt><dd><p>Move the cursor to the beginning of the line requested and make that
|
||||
line visible. A request past the end of the file goes to the end.
|
||||
Clear any selection and update the line and column status.</p>
|
||||
</dd>
|
||||
<dt>Show Completions</dt><dd><p>Open a scrollable list allowing selection of existing names. See
|
||||
<a class="reference internal" href="#completions"><span class="std std-ref">Completions</span></a> in the Editing and navigation section below.</p>
|
||||
</dd>
|
||||
<dt>Expand Word</dt><dd><p>Expand a prefix you have typed to match a full word in the same window;
|
||||
repeat to get a different expansion.</p>
|
||||
</dd>
|
||||
<dt>Show Call Tip</dt><dd><p>After an unclosed parenthesis for a function, open a small window with
|
||||
function parameter hints. See <a class="reference internal" href="#calltips"><span class="std std-ref">Calltips</span></a> in the
|
||||
Editing and navigation section below.</p>
|
||||
</dd>
|
||||
<dt>Show Surrounding Parens</dt><dd><p>Highlight the surrounding parenthesis.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</section>
|
||||
<section id="format-menu-editor-window-only">
|
||||
<span id="format-menu"></span><h3>Format menu (Editor window only)<a class="headerlink" href="#format-menu-editor-window-only" title="Link to this heading">¶</a></h3>
|
||||
<dl class="simple">
|
||||
<dt>Format Paragraph</dt><dd><p>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.</p>
|
||||
</dd>
|
||||
<dt>Indent Region</dt><dd><p>Shift selected lines right by the indent width (default 4 spaces).</p>
|
||||
</dd>
|
||||
<dt>Dedent Region</dt><dd><p>Shift selected lines left by the indent width (default 4 spaces).</p>
|
||||
</dd>
|
||||
<dt>Comment Out Region</dt><dd><p>Insert ## in front of selected lines.</p>
|
||||
</dd>
|
||||
<dt>Uncomment Region</dt><dd><p>Remove leading # or ## from selected lines.</p>
|
||||
</dd>
|
||||
<dt>Tabify Region</dt><dd><p>Turn <em>leading</em> stretches of spaces into tabs. (Note: We recommend using
|
||||
4 space blocks to indent Python code.)</p>
|
||||
</dd>
|
||||
<dt>Untabify Region</dt><dd><p>Turn <em>all</em> tabs into the correct number of spaces.</p>
|
||||
</dd>
|
||||
<dt>Toggle Tabs</dt><dd><p>Open a dialog to switch between indenting with spaces and tabs.</p>
|
||||
</dd>
|
||||
<dt>New Indent Width</dt><dd><p>Open a dialog to change indent width. The accepted default by the Python
|
||||
community is 4 spaces.</p>
|
||||
</dd>
|
||||
<dt>Strip Trailing Chitespace</dt><dd><p>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. Except for Shell windows,
|
||||
remove extra newlines at the end of the file.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</section>
|
||||
<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="Link to this heading">¶</a></h3>
|
||||
<dl class="simple" id="run-module">
|
||||
<dt>Run Module</dt><dd><p>Do <a class="reference internal" href="#check-module"><span class="std std-ref">Check Module</span></a>. 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.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
<dl class="simple" id="run-custom">
|
||||
<dt>Run… Customized</dt><dd><p>Same as <a class="reference internal" href="#run-module"><span class="std std-ref">Run Module</span></a>, but run the module with customized
|
||||
settings. <em>Command Line Arguments</em> extend <a class="reference internal" href="sys.html#sys.argv" title="sys.argv"><code class="xref py py-data docutils literal notranslate"><span class="pre">sys.argv</span></code></a> as if passed
|
||||
on a command line. The module can be run in the Shell without restarting.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
<dl class="simple" id="check-module">
|
||||
<dt>Check Module</dt><dd><p>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.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
<dl class="simple" id="python-shell">
|
||||
<dt>Python Shell</dt><dd><p>Open or wake up the Python Shell window.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</section>
|
||||
<section id="shell-menu-shell-window-only">
|
||||
<h3>Shell menu (Shell window only)<a class="headerlink" href="#shell-menu-shell-window-only" title="Link to this heading">¶</a></h3>
|
||||
<dl class="simple">
|
||||
<dt>View Last Restart</dt><dd><p>Scroll the shell window to the last Shell restart.</p>
|
||||
</dd>
|
||||
<dt>Restart Shell</dt><dd><p>Restart the shell to clean the environment and reset display and exception handling.</p>
|
||||
</dd>
|
||||
<dt>Previous History</dt><dd><p>Cycle through earlier commands in history which match the current entry.</p>
|
||||
</dd>
|
||||
<dt>Next History</dt><dd><p>Cycle through later commands in history which match the current entry.</p>
|
||||
</dd>
|
||||
<dt>Interrupt Execution</dt><dd><p>Stop a running program.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</section>
|
||||
<section id="debug-menu-shell-window-only">
|
||||
<h3>Debug menu (Shell window only)<a class="headerlink" href="#debug-menu-shell-window-only" title="Link to this heading">¶</a></h3>
|
||||
<dl class="simple">
|
||||
<dt>Go to File/Line</dt><dd><p>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.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
<dl class="simple" id="index-3">
|
||||
<dt>Debugger (toggle)</dt><dd><p>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.</p>
|
||||
</dd>
|
||||
<dt>Stack Viewer</dt><dd><p>Show the stack traceback of the last exception in a tree widget, with
|
||||
access to locals and globals.</p>
|
||||
</dd>
|
||||
<dt>Auto-open Stack Viewer</dt><dd><p>Toggle automatically opening the stack viewer on an unhandled exception.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</section>
|
||||
<section id="options-menu-shell-and-editor">
|
||||
<h3>Options menu (Shell and Editor)<a class="headerlink" href="#options-menu-shell-and-editor" title="Link to this heading">¶</a></h3>
|
||||
<dl class="simple">
|
||||
<dt>Configure IDLE</dt><dd><p>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. On macOS, open the
|
||||
configuration dialog by selecting Preferences in the application
|
||||
menu. For more details, see
|
||||
<a class="reference internal" href="#preferences"><span class="std std-ref">Setting preferences</span></a> under Help and preferences.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
<p>Most configuration options apply to all windows or all future windows.
|
||||
The option items below only apply to the active window.</p>
|
||||
<dl class="simple">
|
||||
<dt>Show/Hide Code Context (Editor Window only)</dt><dd><p>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. See
|
||||
<a class="reference internal" href="#code-context"><span class="std std-ref">Code Context</span></a> in the Editing and Navigation section
|
||||
below.</p>
|
||||
</dd>
|
||||
<dt>Show/Hide Line Numbers (Editor Window only)</dt><dd><p>Open a column to the left of the edit window which shows the number
|
||||
of each line of text. The default is off, which may be changed in the
|
||||
preferences (see <a class="reference internal" href="#preferences"><span class="std std-ref">Setting preferences</span></a>).</p>
|
||||
</dd>
|
||||
<dt>Zoom/Restore Height</dt><dd><p>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. The maximum height for a screen is determined by
|
||||
momentarily maximizing a window the first time one is zoomed on the screen.
|
||||
Changing screen settings may invalidate the saved height. This toggle has
|
||||
no effect when a window is maximized.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</section>
|
||||
<section id="window-menu-shell-and-editor">
|
||||
<h3>Window menu (Shell and Editor)<a class="headerlink" href="#window-menu-shell-and-editor" title="Link to this heading">¶</a></h3>
|
||||
<p>Lists the names of all open windows; select one to bring it to the foreground
|
||||
(deiconifying it if necessary).</p>
|
||||
</section>
|
||||
<section id="help-menu-shell-and-editor">
|
||||
<h3>Help menu (Shell and Editor)<a class="headerlink" href="#help-menu-shell-and-editor" title="Link to this heading">¶</a></h3>
|
||||
<dl class="simple">
|
||||
<dt>About IDLE</dt><dd><p>Display version, copyright, license, credits, and more.</p>
|
||||
</dd>
|
||||
<dt>IDLE Help</dt><dd><p>Display this IDLE document, detailing the menu options, basic editing and
|
||||
navigation, and other tips.</p>
|
||||
</dd>
|
||||
<dt>Python Docs</dt><dd><p>Access local Python documentation, if installed, or start a web browser
|
||||
and open docs.python.org showing the latest Python documentation.</p>
|
||||
</dd>
|
||||
<dt>Turtle Demo</dt><dd><p>Run the turtledemo module with example Python code and turtle drawings.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
<p>Additional help sources may be added here with the Configure IDLE dialog under
|
||||
the General tab. See the <a class="reference internal" href="#help-sources"><span class="std std-ref">Help sources</span></a> subsection below
|
||||
for more on Help menu choices.</p>
|
||||
</section>
|
||||
<section id="context-menus">
|
||||
<span id="index-4"></span><h3>Context menus<a class="headerlink" href="#context-menus" title="Link to this heading">¶</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="simple">
|
||||
<dt>Cut</dt><dd><p>Copy selection into the system-wide clipboard; then delete the selection.</p>
|
||||
</dd>
|
||||
<dt>Copy</dt><dd><p>Copy selection into the system-wide clipboard.</p>
|
||||
</dd>
|
||||
<dt>Paste</dt><dd><p>Insert contents of the system-wide clipboard into the current window.</p>
|
||||
</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 user’s <code class="docutils literal notranslate"><span class="pre">.idlerc</span></code>
|
||||
directory.</p>
|
||||
<dl class="simple">
|
||||
<dt>Set Breakpoint</dt><dd><p>Set a breakpoint on the current line.</p>
|
||||
</dd>
|
||||
<dt>Clear Breakpoint</dt><dd><p>Clear the breakpoint on that line.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
<p>Shell and Output windows also have the following.</p>
|
||||
<dl class="simple">
|
||||
<dt>Go to file/line</dt><dd><p>Same as in Debug menu.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
<p>The Shell window also has an output squeezing facility explained in the <em>Python
|
||||
Shell window</em> subsection below.</p>
|
||||
<dl class="simple">
|
||||
<dt>Squeeze</dt><dd><p>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.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</section>
|
||||
</section>
|
||||
<section id="editing-and-navigation">
|
||||
<span id="id1"></span><h2>Editing and Navigation<a class="headerlink" href="#editing-and-navigation" title="Link to this heading">¶</a></h2>
|
||||
<section id="editor-windows">
|
||||
<h3>Editor windows<a class="headerlink" href="#editor-windows" title="Link to this heading">¶</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>
|
||||
</section>
|
||||
<section id="key-bindings">
|
||||
<h3>Key bindings<a class="headerlink" href="#key-bindings" title="Link to this heading">¶</a></h3>
|
||||
<p>The IDLE insertion cursor is a thin vertical bar between character
|
||||
positions. When characters are entered, the insertion cursor and
|
||||
everything to its right moves right one character and
|
||||
the new character is entered in the new space.</p>
|
||||
<p>Several non-character keys move the cursor and possibly
|
||||
delete characters. Deletion does not puts text on the clipboard,
|
||||
but IDLE has an undo list. Wherever this doc discusses keys,
|
||||
‘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. (And all such discussions
|
||||
assume that the keys have not been re-bound to something else.)</p>
|
||||
<ul class="simple">
|
||||
<li><p>Arrow keys move the cursor one character or line.</p></li>
|
||||
<li><p><kbd class="kbd compound docutils literal notranslate"><kbd class="kbd docutils literal notranslate">C</kbd>-<kbd class="kbd docutils literal notranslate">LeftArrow</kbd></kbd> and <kbd class="kbd compound docutils literal notranslate"><kbd class="kbd docutils literal notranslate">C</kbd>-<kbd class="kbd docutils literal notranslate">RightArrow</kbd></kbd> moves left or right one word.</p></li>
|
||||
<li><p><kbd class="kbd docutils literal notranslate">Home</kbd> and <kbd class="kbd docutils literal notranslate">End</kbd> go to the beginning or end of the line.</p></li>
|
||||
<li><p><kbd class="kbd docutils literal notranslate">Page Up</kbd> and <kbd class="kbd docutils literal notranslate">Page Down</kbd> go up or down one screen.</p></li>
|
||||
<li><p><kbd class="kbd compound docutils literal notranslate"><kbd class="kbd docutils literal notranslate">C</kbd>-<kbd class="kbd docutils literal notranslate">Home</kbd></kbd> and <kbd class="kbd compound docutils literal notranslate"><kbd class="kbd docutils literal notranslate">C</kbd>-<kbd class="kbd docutils literal notranslate">End</kbd></kbd> go to beginning or end of the file.</p></li>
|
||||
<li><p><kbd class="kbd docutils literal notranslate">Backspace</kbd> and <kbd class="kbd docutils literal notranslate">Del</kbd> (or <kbd class="kbd compound docutils literal notranslate"><kbd class="kbd docutils literal notranslate">C</kbd>-<kbd class="kbd docutils literal notranslate">d</kbd></kbd>) delete the previous
|
||||
or next character.</p></li>
|
||||
<li><p><kbd class="kbd compound docutils literal notranslate"><kbd class="kbd docutils literal notranslate">C</kbd>-<kbd class="kbd docutils literal notranslate">Backspace</kbd></kbd> and <kbd class="kbd compound docutils literal notranslate"><kbd class="kbd docutils literal notranslate">C</kbd>-<kbd class="kbd docutils literal notranslate">Del</kbd></kbd> delete one word left or right.</p></li>
|
||||
<li><p><kbd class="kbd compound docutils literal notranslate"><kbd class="kbd docutils literal notranslate">C</kbd>-<kbd class="kbd docutils literal notranslate">k</kbd></kbd> deletes (‘kills’) everything to the right.</p></li>
|
||||
</ul>
|
||||
<p>Standard keybindings (like <kbd class="kbd compound docutils literal notranslate"><kbd class="kbd docutils literal notranslate">C</kbd>-<kbd class="kbd docutils literal notranslate">c</kbd></kbd> to copy and <kbd class="kbd compound docutils literal notranslate"><kbd class="kbd docutils literal notranslate">C</kbd>-<kbd class="kbd docutils literal notranslate">v</kbd></kbd> to paste)
|
||||
may work. Keybindings are selected in the Configure IDLE dialog.</p>
|
||||
</section>
|
||||
<section id="automatic-indentation">
|
||||
<h3>Automatic indentation<a class="headerlink" href="#automatic-indentation" title="Link to this heading">¶</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 on the
|
||||
<a class="reference internal" href="#format-menu"><span class="std std-ref">Format menu</span></a>.</p>
|
||||
</section>
|
||||
<section id="search-and-replace">
|
||||
<h3>Search and Replace<a class="headerlink" href="#search-and-replace" title="Link to this heading">¶</a></h3>
|
||||
<p>Any selection becomes a search target. However, only selections within
|
||||
a line work because searches are only performed within lines with the
|
||||
terminal newline removed. If <code class="docutils literal notranslate"><span class="pre">[x]</span> <span class="pre">Regular</span> <span class="pre">expression</span></code> is checked, the
|
||||
target is interpreted according to the Python re module.</p>
|
||||
</section>
|
||||
<section id="completions">
|
||||
<span id="id2"></span><h3>Completions<a class="headerlink" href="#completions" title="Link to this heading">¶</a></h3>
|
||||
<p>Completions are supplied, when requested and available, for module
|
||||
names, attributes of classes or functions, or filenames. Each request
|
||||
method displays a completion box with existing names. (See tab
|
||||
completions below for an exception.) For any box, change the name
|
||||
being completed and the item highlighted in the box by
|
||||
typing and deleting characters; by hitting <kbd class="kbd docutils literal notranslate">Up</kbd>, <kbd class="kbd docutils literal notranslate">Down</kbd>,
|
||||
<kbd class="kbd docutils literal notranslate">PageUp</kbd>, <kbd class="kbd docutils literal notranslate">PageDown</kbd>, <kbd class="kbd docutils literal notranslate">Home</kbd>, and <kbd class="kbd docutils literal notranslate">End</kbd> keys;
|
||||
and by a single click within the box. Close the box with <kbd class="kbd docutils literal notranslate">Escape</kbd>,
|
||||
<kbd class="kbd docutils literal notranslate">Enter</kbd>, and double <kbd class="kbd docutils literal notranslate">Tab</kbd> keys or clicks outside the box.
|
||||
A double click within the box selects and closes.</p>
|
||||
<p>One way to open a box is to type a key character and wait for a
|
||||
predefined interval. This defaults to 2 seconds; customize it
|
||||
in the settings dialog. (To prevent auto popups, set the delay to a
|
||||
large number of milliseconds, such as 100000000.) For imported module
|
||||
names or class or function attributes, type ‘.’.
|
||||
For filenames in the root directory, type <a class="reference internal" href="os.html#os.sep" title="os.sep"><code class="xref py py-data docutils literal notranslate"><span class="pre">os.sep</span></code></a> or
|
||||
<a class="reference internal" href="os.html#os.altsep" title="os.altsep"><code class="xref py py-data docutils literal notranslate"><span class="pre">os.altsep</span></code></a> immediately after an opening quote. (On Windows,
|
||||
one can specify a drive first.) Move into subdirectories by typing a
|
||||
directory name and a separator.</p>
|
||||
<p>Instead of waiting, or after a box is closed, open a completion box
|
||||
immediately with Show Completions on the Edit menu. The default hot
|
||||
key is <kbd class="kbd compound docutils literal notranslate"><kbd class="kbd docutils literal notranslate">C</kbd>-<kbd class="kbd docutils literal notranslate">space</kbd></kbd>. If one types a prefix for the desired name
|
||||
before opening the box, the first match or near miss is made visible.
|
||||
The result is the same as if one enters a prefix
|
||||
after the box is displayed. Show Completions after a quote completes
|
||||
filenames in the current directory instead of a root directory.</p>
|
||||
<p>Hitting <kbd class="kbd docutils literal notranslate">Tab</kbd> after a prefix usually has the same effect as Show
|
||||
Completions. (With no prefix, it indents.) However, if there is only
|
||||
one match to the prefix, that match is immediately added to the editor
|
||||
text without opening a box.</p>
|
||||
<p>Invoking ‘Show Completions’, or hitting <kbd class="kbd docutils literal notranslate">Tab</kbd> after a prefix,
|
||||
outside of a string and without a preceding ‘.’ opens a box with
|
||||
keywords, builtin names, and available module-level names.</p>
|
||||
<p>When editing code in an editor (as oppose to Shell), increase the
|
||||
available module-level names by running your code
|
||||
and not restarting the Shell thereafter. This is especially useful
|
||||
after adding imports at the top of a file. This also increases
|
||||
possible attribute completions.</p>
|
||||
<p>Completion boxes initially exclude names beginning with ‘_’ or, for
|
||||
modules, not included in ‘__all__’. The hidden names can be accessed
|
||||
by typing ‘_’ after ‘.’, either before or after the box is opened.</p>
|
||||
</section>
|
||||
<section id="calltips">
|
||||
<span id="id3"></span><h3>Calltips<a class="headerlink" href="#calltips" title="Link to this heading">¶</a></h3>
|
||||
<p>A calltip is shown automatically when one types <kbd class="kbd docutils literal notranslate">(</kbd> after the name
|
||||
of an <em>accessible</em> function. A function 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. Whenever the
|
||||
cursor is in the argument part of a definition, select Edit and “Show
|
||||
Call Tip” on the menu or enter its shortcut to display a calltip.</p>
|
||||
<p>The calltip consists of the function’s signature and docstring up to
|
||||
the latter’s first blank line or the fifth non-blank line. (Some builtin
|
||||
functions lack an accessible signature.) A ‘/’ or ‘*’ in the signature
|
||||
indicates that the preceding or following arguments are passed by
|
||||
position or name (keyword) only. Details are subject to change.</p>
|
||||
<p>In Shell, the accessible functions depends on what modules have been
|
||||
imported into the user process, including those imported by Idle itself,
|
||||
and which 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 itself import turtle. The menu entry and shortcut also do
|
||||
nothing. Enter <code class="docutils literal notranslate"><span class="pre">import</span> <span class="pre">turtle</span></code>. Thereafter, <code class="docutils literal notranslate"><span class="pre">turtle.write(</span></code>
|
||||
will display a calltip.</p>
|
||||
<p>In an editor, import statements have no effect until one runs the file.
|
||||
One might want to run a file after writing import statements, after
|
||||
adding function definitions, or after opening an existing file.</p>
|
||||
</section>
|
||||
<section id="code-context">
|
||||
<span id="id4"></span><h3>Code Context<a class="headerlink" href="#code-context" title="Link to this heading">¶</a></h3>
|
||||
<p>Within an editor window containing Python code, code context can be toggled
|
||||
in order to show or hide a pane at the top of the window. When shown, this
|
||||
pane freezes the opening lines for block code, such as those beginning with
|
||||
<code class="docutils literal notranslate"><span class="pre">class</span></code>, <code class="docutils literal notranslate"><span class="pre">def</span></code>, or <code class="docutils literal notranslate"><span class="pre">if</span></code> keywords, that would have otherwise scrolled
|
||||
out of view. The size of the pane will be expanded and contracted as needed
|
||||
to show the all current levels of context, up to the maximum number of
|
||||
lines defined in the Configure IDLE dialog (which defaults to 15). If there
|
||||
are no current context lines and the feature is toggled on, a single blank
|
||||
line will display. Clicking on a line in the context pane will move that
|
||||
line to the top of the editor.</p>
|
||||
<p>The text and background colors for the context pane can be configured under
|
||||
the Highlights tab in the Configure IDLE dialog.</p>
|
||||
</section>
|
||||
<section id="shell-window">
|
||||
<h3>Shell window<a class="headerlink" href="#shell-window" title="Link to this heading">¶</a></h3>
|
||||
<p>In IDLE’s Shell, enter, edit, and recall complete statements. (Most
|
||||
consoles and terminals only work with a single physical line at a time).</p>
|
||||
<p>Submit a single-line statement for execution by hitting <kbd class="kbd docutils literal notranslate">Return</kbd>
|
||||
with the cursor anywhere on the line. If a line is extended with
|
||||
Backslash (<kbd class="kbd docutils literal notranslate">\</kbd>), the cursor must be on the last physical line.
|
||||
Submit a multi-line compound statement by entering a blank line after
|
||||
the statement.</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>, as specified above.
|
||||
One may edit pasted code first.
|
||||
If one pastes more than 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>Lines containing <code class="docutils literal notranslate"><span class="pre">RESTART</span></code> mean that the user execution process has been
|
||||
re-started. This occurs when the user execution process has crashed,
|
||||
when one requests a restart on the Shell menu, or when one runs code
|
||||
in an editor window.</p>
|
||||
<p>The editing features described in previous subsections work when entering
|
||||
code interactively. IDLE’s Shell window also responds to the following:</p>
|
||||
<ul class="simple">
|
||||
<li><p><kbd class="kbd compound docutils literal notranslate"><kbd class="kbd docutils literal notranslate">C</kbd>-<kbd class="kbd docutils literal notranslate">c</kbd></kbd> attempts to interrupt statement execution (but may fail).</p></li>
|
||||
<li><p><kbd class="kbd compound docutils literal notranslate"><kbd class="kbd docutils literal notranslate">C</kbd>-<kbd class="kbd docutils literal notranslate">d</kbd></kbd> closes Shell if typed at a <code class="docutils literal notranslate"><span class="pre">>>></span></code> prompt.</p></li>
|
||||
<li><p><kbd class="kbd compound docutils literal notranslate"><kbd class="kbd docutils literal notranslate">Alt</kbd>-<kbd class="kbd docutils literal notranslate">p</kbd></kbd> and <kbd class="kbd compound docutils literal notranslate"><kbd class="kbd docutils literal notranslate">Alt</kbd>-<kbd class="kbd docutils literal notranslate">n</kbd></kbd> (<kbd class="kbd compound docutils literal notranslate"><kbd class="kbd docutils literal notranslate">C</kbd>-<kbd class="kbd docutils literal notranslate">p</kbd></kbd> and <kbd class="kbd compound docutils literal notranslate"><kbd class="kbd docutils literal notranslate">C</kbd>-<kbd class="kbd docutils literal notranslate">n</kbd></kbd> on macOS)
|
||||
retrieve to the current prompt the previous or next previously
|
||||
entered statement that matches anything already typed.</p></li>
|
||||
<li><p><kbd class="kbd docutils literal notranslate">Return</kbd> while the cursor is on any previous statement
|
||||
appends the latter to anything already typed at the prompt.</p></li>
|
||||
</ul>
|
||||
</section>
|
||||
<section id="text-colors">
|
||||
<h3>Text colors<a class="headerlink" href="#text-colors" title="Link to this heading">¶</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>IDLE also highlights the <a class="reference internal" href="../reference/lexical_analysis.html#soft-keywords"><span class="std std-ref">soft keywords</span></a> <a class="reference internal" href="../reference/compound_stmts.html#match"><code class="xref std std-keyword docutils literal notranslate"><span class="pre">match</span></code></a>,
|
||||
<a class="reference internal" href="../reference/compound_stmts.html#match"><code class="xref std std-keyword docutils literal notranslate"><span class="pre">case</span></code></a>, and <a class="reference internal" href="../reference/compound_stmts.html#wildcard-patterns"><code class="xref std std-keyword docutils literal notranslate"><span class="pre">_</span></code></a> in
|
||||
pattern-matching statements. However, this highlighting is not perfect and
|
||||
will be incorrect in some rare cases, including some <code class="docutils literal notranslate"><span class="pre">_</span></code>-s in <code class="docutils literal notranslate"><span class="pre">case</span></code>
|
||||
patterns.</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>
|
||||
</section>
|
||||
</section>
|
||||
<section id="startup-and-code-execution">
|
||||
<h2>Startup and Code Execution<a class="headerlink" href="#startup-and-code-execution" title="Link to this heading">¶</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 user’s home directory. Statements in this file will be
|
||||
executed in the Tk namespace, so this file is not useful for importing
|
||||
functions to be used from IDLE’s Python shell.</p>
|
||||
<section id="command-line-usage">
|
||||
<h3>Command line usage<a class="headerlink" href="#command-line-usage" title="Link to this heading">¶</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><p>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.</p></li>
|
||||
<li><p>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.</p></li>
|
||||
</ul>
|
||||
</section>
|
||||
<section id="startup-failure">
|
||||
<h3>Startup failure<a class="headerlink" href="#startup-failure" title="Link to this heading">¶</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
|
||||
usually 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>One specific connection failure on Unix systems results from
|
||||
misconfigured masquerading rules somewhere in a system’s network setup.
|
||||
When IDLE is started from a terminal, one will see a message starting
|
||||
with <code class="docutils literal notranslate"><span class="pre">**</span> <span class="pre">Invalid</span> <span class="pre">host:</span></code>.
|
||||
The valid value is <code class="docutils literal notranslate"><span class="pre">127.0.0.1</span> <span class="pre">(idlelib.rpc.LOCALHOST)</span></code>.
|
||||
One can diagnose with <code class="docutils literal notranslate"><span class="pre">tcpconnect</span> <span class="pre">-irv</span> <span class="pre">127.0.0.1</span> <span class="pre">6543</span></code> in one
|
||||
terminal window and <code class="docutils literal notranslate"><span class="pre">tcplisten</span> <span class="pre"><same</span> <span class="pre">args></span></code> in another.</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 check for one and stop it if there is. Sometimes a restart
|
||||
initiated by a program crash or Keyboard Interrupt (control-C) may fail
|
||||
to connect. Dismissing the error box or using 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
|
||||
<code class="docutils literal notranslate"><span class="pre">~/.idlerc/</span></code> (~ is one’s 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. Instead, use the
|
||||
configuration dialog, under Options. Once there is an error in a user
|
||||
configuration file, the best solution may be to delete it and start over
|
||||
with the settings dialog.</p>
|
||||
<p>If IDLE quits with no message, and it was not started from a console, try
|
||||
starting it from a console or terminal (<code class="docutils literal notranslate"><span class="pre">python</span> <span class="pre">-m</span> <span class="pre">idlelib</span></code>) and see if
|
||||
this results in an error message.</p>
|
||||
<p>On Unix-based systems with tcl/tk older than <code class="docutils literal notranslate"><span class="pre">8.6.11</span></code> (see
|
||||
<code class="docutils literal notranslate"><span class="pre">About</span> <span class="pre">IDLE</span></code>) certain characters of certain fonts can cause
|
||||
a tk failure with a message to the terminal. This can happen either
|
||||
if one starts IDLE to edit a file with such a character or later
|
||||
when entering such a character. If one cannot upgrade tcl/tk,
|
||||
then re-configure IDLE to use a font that works better.</p>
|
||||
</section>
|
||||
<section id="running-user-code">
|
||||
<h3>Running user code<a class="headerlink" href="#running-user-code" title="Link to this heading">¶</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.active_count()</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>Sending print output from one process to a text widget in another is
|
||||
slower than printing to a system terminal in the same process.
|
||||
This has the most effect when printing multiple arguments, as the string
|
||||
for each argument, each separator, the newline are sent separately.
|
||||
For development, this is usually not a problem, but if one wants to
|
||||
print faster in IDLE, format and join together everything one wants
|
||||
displayed together and then print a single string. Both format strings
|
||||
and <a class="reference internal" href="stdtypes.html#str.join" title="str.join"><code class="xref py py-meth docutils literal notranslate"><span class="pre">str.join()</span></code></a> can help combine fields and lines.</p>
|
||||
<p>IDLE’s 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. (On Windows,
|
||||
use <code class="docutils literal notranslate"><span class="pre">python</span></code> or <code class="docutils literal notranslate"><span class="pre">py</span></code> rather than <code class="docutils literal notranslate"><span class="pre">pythonw</span></code> or <code class="docutils literal notranslate"><span class="pre">pyw</span></code>.)
|
||||
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>,
|
||||
IDLE’s changes are lost and input from the keyboard and output to the screen
|
||||
will not work correctly.</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>The IDLE code running in the execution process adds frames to the call stack
|
||||
that would not be there otherwise. IDLE wraps <code class="docutils literal notranslate"><span class="pre">sys.getrecursionlimit</span></code> and
|
||||
<code class="docutils literal notranslate"><span class="pre">sys.setrecursionlimit</span></code> to reduce the effect of the additional stack
|
||||
frames.</p>
|
||||
<p>When user code raises SystemExit either directly or by calling sys.exit,
|
||||
IDLE returns to a Shell prompt instead of exiting.</p>
|
||||
</section>
|
||||
<section id="user-output-in-shell">
|
||||
<h3>User output in Shell<a class="headerlink" href="#user-output-in-shell" title="Link to this heading">¶</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 IDLE’s 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>A Tk Text widget, and hence IDLE’s Shell, displays characters (codepoints) in
|
||||
the BMP (Basic Multilingual Plane) subset of Unicode. Which characters are
|
||||
displayed with a proper glyph and which with a replacement box depends on the
|
||||
operating system and installed fonts. Tab characters cause the following text
|
||||
to begin after the next tab stop. (They occur every 8 ‘characters’). Newline
|
||||
characters cause following text to appear on a new line. Other control
|
||||
characters are ignored or displayed as a space, box, or something else,
|
||||
depending on the operating system and font. (Moving the text cursor through
|
||||
such output with arrow keys may exhibit some surprising spacing behavior.)</p>
|
||||
<div class="highlight-python3 notranslate"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="n">s</span> <span class="o">=</span> <span class="s1">'a</span><span class="se">\t</span><span class="s1">b</span><span class="se">\a</span><span class="s1"><</span><span class="se">\x02</span><span class="s1">><</span><span class="se">\r</span><span class="s1">></span><span class="se">\b</span><span class="s1">c</span><span class="se">\n</span><span class="s1">d'</span> <span class="c1"># Enter 22 chars.</span>
|
||||
<span class="gp">>>> </span><span class="nb">len</span><span class="p">(</span><span class="n">s</span><span class="p">)</span>
|
||||
<span class="go">14</span>
|
||||
<span class="gp">>>> </span><span class="n">s</span> <span class="c1"># Display repr(s)</span>
|
||||
<span class="go">'a\tb\x07<\x02><\r>\x08c\nd'</span>
|
||||
<span class="gp">>>> </span><span class="nb">print</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">end</span><span class="o">=</span><span class="s1">''</span><span class="p">)</span> <span class="c1"># Display s as is.</span>
|
||||
<span class="go"># Result varies by OS and font. Try it.</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">repr</span></code> function is used for interactive echo of expression
|
||||
values. It returns an altered version of the input string in which
|
||||
control codes, some BMP codepoints, and all non-BMP codepoints are
|
||||
replaced with escape codes. As demonstrated above, it allows one to
|
||||
identify the characters in a string, regardless of how they are displayed.</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>
|
||||
</section>
|
||||
<section id="developing-tkinter-applications">
|
||||
<h3>Developing tkinter applications<a class="headerlink" href="#developing-tkinter-applications" title="Link to this heading">¶</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 milliseconds.
|
||||
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">>>></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>
|
||||
</section>
|
||||
<section id="running-without-a-subprocess">
|
||||
<h3>Running without a subprocess<a class="headerlink" href="#running-without-a-subprocess" title="Link to this heading">¶</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">Deprecated since version 3.4.</span></p>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
<section id="help-and-preferences">
|
||||
<h2>Help and Preferences<a class="headerlink" href="#help-and-preferences" title="Link to this heading">¶</a></h2>
|
||||
<section id="help-sources">
|
||||
<span id="id5"></span><h3>Help sources<a class="headerlink" href="#help-sources" title="Link to this heading">¶</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 <code class="docutils literal notranslate"><span class="pre">docs.python.org/x.y</span></code>, 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>
|
||||
</section>
|
||||
<section id="setting-preferences">
|
||||
<span id="preferences"></span><h3>Setting preferences<a class="headerlink" href="#setting-preferences" title="Link to this heading">¶</a></h3>
|
||||
<p>The font preferences, highlighting, keys, and general preferences can be
|
||||
changed via Configure IDLE on the Option menu.
|
||||
Non-default user settings are saved in a <code class="docutils literal notranslate"><span class="pre">.idlerc</span></code> directory in the user’s
|
||||
home directory. Problems caused by bad user configuration files are solved
|
||||
by editing or deleting one or more of the files in <code class="docutils literal notranslate"><span class="pre">.idlerc</span></code>.</p>
|
||||
<p>On the Font tab, see the text sample for the effect of font face and size
|
||||
on multiple characters in multiple languages. Edit the sample to add
|
||||
other characters of personal interest. Use the sample to select
|
||||
monospaced fonts. If particular characters have problems in Shell or an
|
||||
editor, add them to the top of the sample and try changing first size
|
||||
and then font.</p>
|
||||
<p>On the Highlights and Keys tab, select a built-in or custom color theme
|
||||
and key set. To use a newer built-in color theme or key set with older
|
||||
IDLEs, save it as a new custom theme or key set and it well be accessible
|
||||
to older IDLEs.</p>
|
||||
</section>
|
||||
<section id="idle-on-macos">
|
||||
<h3>IDLE on macOS<a class="headerlink" href="#idle-on-macos" title="Link to this heading">¶</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>
|
||||
</section>
|
||||
<section id="extensions">
|
||||
<h3>Extensions<a class="headerlink" href="#extensions" title="Link to this heading">¶</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>
|
||||
</section>
|
||||
</section>
|
||||
<section id="module-idlelib">
|
||||
<span id="idlelib-implementation-of-idle-application"></span><h2>idlelib — implementation of IDLE application<a class="headerlink" href="#module-idlelib" title="Link to this heading">¶</a></h2>
|
||||
<p><strong>Source code:</strong> <a class="extlink-source reference external" href="https://github.com/python/cpython/tree/main/Lib/idlelib">Lib/idlelib</a></p>
|
||||
<hr class="docutils" />
|
||||
<p>The Lib/idlelib package implements the IDLE application. See the rest
|
||||
of this page for how to use IDLE.</p>
|
||||
<p>The files in idlelib are described in idlelib/README.txt. Access it
|
||||
either in idlelib or click Help => About IDLE on the IDLE menu. This
|
||||
file also maps IDLE menu items to the code that implements the item.
|
||||
Except for files listed under ‘Startup’, the idlelib code is ‘private’ in
|
||||
sense that feature changes can be backported (see <span class="target" id="index-7"></span><a class="pep reference external" href="https://peps.python.org/pep-0434/"><strong>PEP 434</strong></a>).</p>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
|
||||
297
Dependencies/Python/Lib/idlelib/help.py
vendored
Normal file
@@ -0,0 +1,297 @@
|
||||
""" help.py: Implement the Idle help menu.
|
||||
Contents are subject to revision at any time, without notice.
|
||||
|
||||
|
||||
Help => About IDLE: display 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 the text part of idle.html to help.html while 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, Text, Menu
|
||||
from tkinter.ttk import Frame, Menubutton, Scrollbar, Style
|
||||
from tkinter import font as tkfont
|
||||
|
||||
from idlelib.config import idleConf
|
||||
from idlelib.colorizer import color_config
|
||||
|
||||
## 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.hdrlink = False # Exclude html header links.
|
||||
self.level = 0 # Track indentation level.
|
||||
self.pre = False # Displaying preformatted text?
|
||||
self.hprefix = '' # Heading prefix (like '25.5'?) to remove.
|
||||
self.nested_dl = False # In a nested <dl>?
|
||||
self.simplelist = False # In a simple list (no double spacing)?
|
||||
self.toc = [] # Pair headers with text indexes for toc.
|
||||
self.header = '' # Text within header tags for toc.
|
||||
self.prevtag = None # Previous tag info (opener?, tag).
|
||||
|
||||
def indent(self, amt=1):
|
||||
"Change indent (+1, 0, -1) and tags."
|
||||
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 == 'p' and self.prevtag and not self.prevtag[0]:
|
||||
# Begin a new block for <p> tags after a closed tag.
|
||||
# Avoid extra lines, e.g. after <pre> tags.
|
||||
lastline = self.text.get('end-1c linestart', 'end-1c')
|
||||
s = '\n\n' if lastline and not lastline.isspace() else '\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* '
|
||||
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
|
||||
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']:
|
||||
self.header = ''
|
||||
self.text.insert('end', '\n\n')
|
||||
self.tags = tag
|
||||
self.text.insert('end', s, (self.tags, self.chartags))
|
||||
self.prevtag = (True, tag)
|
||||
|
||||
def handle_endtag(self, tag):
|
||||
"Handle endtags in help.html."
|
||||
if tag in ['h1', 'h2', 'h3']:
|
||||
assert self.level == 0
|
||||
indent = (' ' if tag == 'h3' else
|
||||
' ' if tag == 'h2' else
|
||||
'')
|
||||
self.toc.append((indent+self.header, self.text.index('insert')))
|
||||
self.tags = ''
|
||||
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(-1)
|
||||
self.prevtag = (False, tag)
|
||||
|
||||
def handle_data(self, data):
|
||||
"Handle date segments in help.html."
|
||||
if not self.hdrlink:
|
||||
d = data if self.pre else data.replace('\n', ' ')
|
||||
if self.tags == 'h1':
|
||||
try:
|
||||
self.hprefix = d[:d.index(' ')]
|
||||
if not self.hprefix.isdigit():
|
||||
self.hprefix = ''
|
||||
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'])
|
||||
color_config(self)
|
||||
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))
|
||||
preback = self['selectbackground']
|
||||
self.tag_configure('preblock', font=(fixedfont, 10), lmargin1=25,
|
||||
background=preback)
|
||||
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)
|
||||
self.text = text = HelpText(self, filename)
|
||||
self.style = Style(parent)
|
||||
self['style'] = 'helpframe.TFrame'
|
||||
self.style.configure('helpframe.TFrame', 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) # Only expand the text widget.
|
||||
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)
|
||||
self.frame = HelpFrame(self, filename)
|
||||
self.frame.grid(column=0, row=0, sticky='nsew')
|
||||
self.grid_columnconfigure(0, weight=1)
|
||||
self.grid_rowconfigure(0, weight=1)
|
||||
|
||||
|
||||
def copy_strip(): # pragma: no cover
|
||||
"""Copy the text part of idle.html to idlelib/help.html while 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 change
|
||||
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 procedure 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, 'r', encoding="utf-8") as inn, open(dst, 'w', encoding="utf-8") as out:
|
||||
copy = False
|
||||
for line in inn:
|
||||
if '<section id="idle">' in line: copy = True
|
||||
if '<div class="clearer">' in line: break
|
||||
if copy: out.write(line.strip() + '\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): # pragma: no cover
|
||||
# Try copy_strip, present message.
|
||||
return
|
||||
return HelpWindow(parent, filename, 'IDLE Doc (%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)
|
||||
211
Dependencies/Python/Lib/idlelib/help_about.py
vendored
Normal file
@@ -0,0 +1,211 @@
|
||||
"""About Dialog for IDLE
|
||||
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import webbrowser
|
||||
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
|
||||
|
||||
pyver = python_version()
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
bits = '64' if sys.maxsize > 2**32 else '32'
|
||||
else:
|
||||
bits = 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 {pyver} ({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)
|
||||
|
||||
tkpatch = self._root().getvar('tk_patchLevel')
|
||||
ext = '.png' if tkpatch >= '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)
|
||||
|
||||
forums_url = "https://discuss.python.org"
|
||||
forums = Button(frame_background, text='Python (and IDLE) Discussion', width=35,
|
||||
highlightbackground=self.bg,
|
||||
command=lambda: webbrowser.open(forums_url))
|
||||
forums.grid(row=6, column=0, sticky=W, padx=10, pady=10)
|
||||
|
||||
|
||||
docs_url = ("https://docs.python.org/%d.%d/library/idle.html" %
|
||||
sys.version_info[:2])
|
||||
docs = Button(frame_background, text='IDLE Documentation', width=35,
|
||||
highlightbackground=self.bg,
|
||||
command=lambda: webbrowser.open(docs_url))
|
||||
docs.grid(row=7, column=0, columnspan=2, sticky=W, padx=10, pady=10)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
tclver = str(self.info_patchlevel())
|
||||
tkver = ' and ' + tkpatch if tkpatch != tclver else ''
|
||||
versions = f"Python {pyver} with tcl/tk {tclver}{tkver}"
|
||||
vers = Label(frame_background, text=versions, fg=self.fg, bg=self.bg)
|
||||
vers.grid(row=9, column=0, sticky=W, padx=10, 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)
|
||||
|
||||
idle = Label(frame_background, text='IDLE', fg=self.fg, bg=self.bg)
|
||||
idle.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', 'News3.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)
|
||||
106
Dependencies/Python/Lib/idlelib/history.py
vendored
Normal 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 earliest 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 statement 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 True:
|
||||
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, "stdin")
|
||||
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, "stdin")
|
||||
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)
|
||||
312
Dependencies/Python/Lib/idlelib/hyperparser.py
vendored
Normal 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.prompt_last_line:
|
||||
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 True:
|
||||
# Eat whitespaces, comments, and if postdot_phase is False - a dot
|
||||
while True:
|
||||
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
Dependencies/Python/Lib/idlelib/idle.bat
vendored
Normal 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
Dependencies/Python/Lib/idlelib/idle.py
vendored
Normal 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
Dependencies/Python/Lib/idlelib/idle.pyw
vendored
Normal 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()
|
||||
241
Dependencies/Python/Lib/idlelib/idle_test/README.txt
vendored
Normal file
@@ -0,0 +1,241 @@
|
||||
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
|
||||
|
||||
IDLE tests are 'discovered' by idlelib.idle_test.__init__.load_tests
|
||||
when this is 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 (GH-62281).
|
||||
|
||||
To test subsets of modules, see idlelib.idle_test.__init__. This
|
||||
can be used to find refleaks or possible sources of "Theme changed"
|
||||
tcl messages (GH-71383).
|
||||
|
||||
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.)
|
||||
27
Dependencies/Python/Lib/idlelib/idle_test/__init__.py
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
"""idlelib.idle_test implements 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 (-v)".
|
||||
|
||||
This package and its contained modules are subject to change and
|
||||
any direct use is at your own risk.
|
||||
"""
|
||||
from os.path import dirname
|
||||
|
||||
# test_idle imports load_tests for test discovery (default all).
|
||||
# To run subsets of idlelib module tests, insert '[<chars>]' after '_'.
|
||||
# Example: insert '[ac]' for modules beginning with 'a' or 'c'.
|
||||
# Additional .discover/.addTest pairs with separate inserts work.
|
||||
# Example: pairs with 'c' and 'g' test c* files and grep.
|
||||
|
||||
def load_tests(loader, standard_tests, pattern):
|
||||
this_dir = dirname(__file__)
|
||||
top_dir = dirname(dirname(this_dir))
|
||||
module_tests = loader.discover(start_dir=this_dir,
|
||||
pattern='test_*.py', # Insert here.
|
||||
top_level_dir=top_dir)
|
||||
standard_tests.addTests(module_tests)
|
||||
## module_tests = loader.discover(start_dir=this_dir,
|
||||
## pattern='test_*.py', # Insert here.
|
||||
## top_level_dir=top_dir)
|
||||
## standard_tests.addTests(module_tests)
|
||||
return standard_tests
|
||||
4
Dependencies/Python/Lib/idlelib/idle_test/example_noext
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
#!usr/bin/env python
|
||||
|
||||
def example_function(some_argument):
|
||||
pass
|
||||
4
Dependencies/Python/Lib/idlelib/idle_test/example_stub.pyi
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
# An example file to test recognition of a .pyi file as Python source code.
|
||||
|
||||
class Example:
|
||||
def method(self, argument1: str, argument2: list[int]) -> None: ...
|
||||
442
Dependencies/Python/Lib/idlelib/idle_test/htest.py
vendored
Normal file
@@ -0,0 +1,442 @@
|
||||
"""Run human tests of Idle's window, dialog, and popup widgets.
|
||||
|
||||
run(*tests) Create a master Tk() htest 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 master window to end testing.
|
||||
|
||||
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' or 'master'. When called, the
|
||||
first argument will be the root window. X must create a child
|
||||
Toplevel(parent/master) (or subclass thereof). The Toplevel may be a
|
||||
test widget or dialog, in which case the callable is the corresponding
|
||||
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 '_' and be lowercase.
|
||||
|
||||
|
||||
End the module with
|
||||
|
||||
if __name__ == '__main__':
|
||||
<run unittest.main with 'exit=False'>
|
||||
from idlelib.idle_test.htest import run
|
||||
run(callable) # There could be multiple comma-separated callables.
|
||||
|
||||
To have wrapper functions ignored by coverage reports, tag the def
|
||||
header like so: "def _wrapper(parent): # htest #". Use the same tag
|
||||
for htest lines in widget code. 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.
|
||||
|
||||
callable_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 line if not.
|
||||
msg: master window hints about testing the widget.
|
||||
|
||||
|
||||
TODO test these modules and classes:
|
||||
autocomplete_w.AutoCompleteWindow
|
||||
debugger.Debugger
|
||||
outwin.OutputWindow (indirectly being tested with grep test)
|
||||
pyshell.PyShellEditorWindow
|
||||
"""
|
||||
|
||||
import idlelib.pyshell # Set Windows DPI awareness before Tk().
|
||||
from importlib import import_module
|
||||
import textwrap
|
||||
import tkinter as tk
|
||||
from tkinter.ttk import Scrollbar
|
||||
tk.NoDefaultRoot()
|
||||
|
||||
AboutDialog_spec = {
|
||||
'file': 'help_about',
|
||||
'kwds': {'title': 'help_about test',
|
||||
'_htest': True,
|
||||
},
|
||||
'msg': "Click on URL to open in default browser.\n"
|
||||
"Verify x.y.z versions and test each button, including Close.\n "
|
||||
}
|
||||
|
||||
# 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"
|
||||
}
|
||||
|
||||
_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."
|
||||
}
|
||||
|
||||
CustomRun_spec = {
|
||||
'file': 'query',
|
||||
'kwds': {'title': 'Customize query.py Run',
|
||||
'_htest': True},
|
||||
'msg': "Enter with <Return> or [OK]. Print valid entry to Shell\n"
|
||||
"Arguments are parsed into a list\n"
|
||||
"Mode is currently restart True or False\n"
|
||||
"Close dialog with valid entry, <Escape>, [Cancel], [X]"
|
||||
}
|
||||
|
||||
_debug_object_browser_spec = {
|
||||
'file': 'debugobj',
|
||||
'kwds': {},
|
||||
'msg': "Double click on items up to the lowest level.\n"
|
||||
"Attributes of the objects and related information "
|
||||
"will be displayed side-by-side at each level."
|
||||
}
|
||||
|
||||
# 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."
|
||||
}
|
||||
|
||||
GetKeysWindow_spec = {
|
||||
'file': 'config_key',
|
||||
'kwds': {'title': 'Test keybindings',
|
||||
'action': 'find-again',
|
||||
'current_key_sequences': [['<Control-Key-g>', '<Key-F3>', '<Control-Key-G>']],
|
||||
'_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' in the search results\n "
|
||||
"should open that file in 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"
|
||||
"<Escape>, [Cancel], or [X] 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 should trigger a print to console or IDLE Shell.\n"
|
||||
"Entering 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 elsewhere."
|
||||
}
|
||||
|
||||
_module_browser_spec = {
|
||||
'file': 'browser',
|
||||
'kwds': {},
|
||||
'msg': textwrap.dedent("""
|
||||
"Inspect names of module, class(with superclass if applicable),
|
||||
"methods and functions. Toggle nested items. Double clicking
|
||||
"on items prints a traceback for an exception that is ignored.""")
|
||||
}
|
||||
|
||||
_multistatus_bar_spec = {
|
||||
'file': 'statusbar',
|
||||
'kwds': {},
|
||||
'msg': "Ensure presence of multi-status bar below text area.\n"
|
||||
"Click 'Update Status' to change the status text"
|
||||
}
|
||||
|
||||
PathBrowser_spec = {
|
||||
'file': 'pathbrowser',
|
||||
'kwds': {'_htest': True},
|
||||
'msg': "Test for correct display of all paths in sys.path.\n"
|
||||
"Toggle nested items out to 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'."
|
||||
}
|
||||
|
||||
_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."
|
||||
}
|
||||
|
||||
_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."
|
||||
}
|
||||
|
||||
show_idlehelp_spec = {
|
||||
'file': 'help',
|
||||
'kwds': {},
|
||||
'msg': "If the help text displays, this works.\n"
|
||||
"Text is selectable. Window is scrollable."
|
||||
}
|
||||
|
||||
_sidebar_number_scrolling_spec = {
|
||||
'file': 'sidebar',
|
||||
'kwds': {},
|
||||
'msg': textwrap.dedent("""\
|
||||
1. Click on the line numbers and drag down below the edge of the
|
||||
window, moving the mouse a bit and then leaving it there for a
|
||||
while. The text and line numbers should gradually scroll down,
|
||||
with the selection updated continuously.
|
||||
|
||||
2. With the lines still selected, click on a line number above
|
||||
or below the selected lines. Only the line whose number was
|
||||
clicked should be selected.
|
||||
|
||||
3. Repeat step #1, dragging to above the window. The text and
|
||||
line numbers should gradually scroll up, with the selection
|
||||
updated continuously.
|
||||
|
||||
4. Repeat step #2, clicking a line number below the selection."""),
|
||||
}
|
||||
|
||||
_stackbrowser_spec = {
|
||||
'file': 'stackviewer',
|
||||
'kwds': {},
|
||||
'msg': "A stacktrace for a NameError exception.\n"
|
||||
"Should have NameError and 1 traceback line."
|
||||
}
|
||||
|
||||
_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 up to 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',
|
||||
'contents': '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):
|
||||
"Run callables in 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 = [] # Make list of (spec, callable) tuples.
|
||||
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 key, dic in globals().items():
|
||||
if key.endswith('_spec'):
|
||||
test_name = key[:-5]
|
||||
test_spec = dic
|
||||
test_spec['name'] = test_name
|
||||
mod = import_module('idlelib.' + test_spec['file'])
|
||||
test = getattr(mod, test_name)
|
||||
test_list.append((test_spec, test))
|
||||
test_list.reverse() # So can pop in proper order in next_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_name.set('Test ' + test_spec['name'])
|
||||
|
||||
text['state'] = 'normal' # Enable text replacement.
|
||||
text.delete('1.0', 'end')
|
||||
text.insert("1.0", test_spec['msg'])
|
||||
text['state'] = 'disabled' # Restore read-only property.
|
||||
|
||||
def run_test(_=None):
|
||||
widget = callable_object(root, **test_kwds)
|
||||
try:
|
||||
print(widget.result) # Only true for query classes(?).
|
||||
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()
|
||||
61
Dependencies/Python/Lib/idlelib/idle_test/mock_idle.py
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
'''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,
|
||||
text=None): # Allow real Text with mock Editor.
|
||||
self.text = text or 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
|
||||
307
Dependencies/Python/Lib/idlelib/idle_test/mock_tk.py
vendored
Normal file
@@ -0,0 +1,307 @@
|
||||
"""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.
|
||||
"""
|
||||
import re
|
||||
from _tkinter import TclError
|
||||
|
||||
|
||||
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 examine.
|
||||
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.
|
||||
|
||||
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.messagebox
|
||||
showerror = Mbox.showerror # example, for attribute access in test methods
|
||||
|
||||
class Test(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
module.messagebox = Mbox
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
module.messagebox = orig_mbox
|
||||
---
|
||||
For 'ask' functions, set func.result return value before calling the method
|
||||
that uses the message function. When messagebox functions are the
|
||||
only GUI calls 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
|
||||
|
||||
|
||||
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
|
||||
if m := re.fullmatch(r'end-(\d*)c', char, re.A): # Used by hyperparser.
|
||||
return line, linelength - int(m.group(1))
|
||||
|
||||
# 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
|
||||
30
Dependencies/Python/Lib/idlelib/idle_test/template.py
vendored
Normal 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)
|
||||
305
Dependencies/Python/Lib/idlelib/idle_test/test_autocomplete.py
vendored
Normal file
@@ -0,0 +1,305 @@
|
||||
"Test autocomplete, coverage 93%."
|
||||
|
||||
import unittest
|
||||
from unittest.mock import Mock, patch
|
||||
from test.support import requires
|
||||
from tkinter import Tk, Text
|
||||
import os
|
||||
import __main__
|
||||
|
||||
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.prompt_last_line = '>>>' # Currently not used by autocomplete.
|
||||
|
||||
|
||||
class AutoCompleteTest(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
requires('gui')
|
||||
cls.root = Tk()
|
||||
cls.root.withdraw()
|
||||
cls.text = Text(cls.root)
|
||||
cls.editor = DummyEditwin(cls.root, cls.text)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
del cls.editor, cls.text
|
||||
cls.root.update_idletasks()
|
||||
cls.root.destroy()
|
||||
del cls.root
|
||||
|
||||
def setUp(self):
|
||||
self.text.delete('1.0', 'end')
|
||||
self.autocomplete = ac.AutoComplete(self.editor)
|
||||
|
||||
def test_init(self):
|
||||
self.assertEqual(self.autocomplete.editwin, self.editor)
|
||||
self.assertEqual(self.autocomplete.text, self.text)
|
||||
|
||||
def test_make_autocomplete_window(self):
|
||||
testwin = self.autocomplete._make_autocomplete_window()
|
||||
self.assertIsInstance(testwin, acw.AutoCompleteWindow)
|
||||
|
||||
def test_remove_autocomplete_window(self):
|
||||
acp = self.autocomplete
|
||||
acp.autocompletewindow = m = Mock()
|
||||
acp._remove_autocomplete_window()
|
||||
m.hide_window.assert_called_once()
|
||||
self.assertIsNone(acp.autocompletewindow)
|
||||
|
||||
def test_force_open_completions_event(self):
|
||||
# Call _open_completions and break.
|
||||
acp = self.autocomplete
|
||||
open_c = Func()
|
||||
acp.open_completions = open_c
|
||||
self.assertEqual(acp.force_open_completions_event('event'), 'break')
|
||||
self.assertEqual(open_c.args[0], ac.FORCE)
|
||||
|
||||
def test_autocomplete_event(self):
|
||||
Equal = self.assertEqual
|
||||
acp = self.autocomplete
|
||||
|
||||
# Result of autocomplete event: If modified tab, None.
|
||||
ev = Event(mc_state=True)
|
||||
self.assertIsNone(acp.autocomplete_event(ev))
|
||||
del ev.mc_state
|
||||
|
||||
# If tab after whitespace, None.
|
||||
self.text.insert('1.0', ' """Docstring.\n ')
|
||||
self.assertIsNone(acp.autocomplete_event(ev))
|
||||
self.text.delete('1.0', 'end')
|
||||
|
||||
# If active autocomplete window, complete() and 'break'.
|
||||
self.text.insert('1.0', 're.')
|
||||
acp.autocompletewindow = mock = Mock()
|
||||
mock.is_active = Mock(return_value=True)
|
||||
Equal(acp.autocomplete_event(ev), 'break')
|
||||
mock.complete.assert_called_once()
|
||||
acp.autocompletewindow = None
|
||||
|
||||
# If no active autocomplete window, open_completions(), None/break.
|
||||
open_c = Func(result=False)
|
||||
acp.open_completions = open_c
|
||||
Equal(acp.autocomplete_event(ev), None)
|
||||
Equal(open_c.args[0], ac.TAB)
|
||||
open_c.result = True
|
||||
Equal(acp.autocomplete_event(ev), 'break')
|
||||
Equal(open_c.args[0], ac.TAB)
|
||||
|
||||
def test_try_open_completions_event(self):
|
||||
Equal = self.assertEqual
|
||||
text = self.text
|
||||
acp = self.autocomplete
|
||||
trycompletions = acp.try_open_completions_event
|
||||
after = Func(result='after1')
|
||||
acp.text.after = after
|
||||
|
||||
# If no text or trigger, after not called.
|
||||
trycompletions()
|
||||
Equal(after.called, 0)
|
||||
text.insert('1.0', 're')
|
||||
trycompletions()
|
||||
Equal(after.called, 0)
|
||||
|
||||
# Attribute needed, no existing callback.
|
||||
text.insert('insert', ' re.')
|
||||
acp._delayed_completion_id = None
|
||||
trycompletions()
|
||||
Equal(acp._delayed_completion_index, text.index('insert'))
|
||||
Equal(after.args,
|
||||
(acp.popupwait, acp._delayed_open_completions, ac.TRY_A))
|
||||
cb1 = acp._delayed_completion_id
|
||||
Equal(cb1, 'after1')
|
||||
|
||||
# File needed, existing callback cancelled.
|
||||
text.insert('insert', ' "./Lib/')
|
||||
after.result = 'after2'
|
||||
cancel = Func()
|
||||
acp.text.after_cancel = cancel
|
||||
trycompletions()
|
||||
Equal(acp._delayed_completion_index, text.index('insert'))
|
||||
Equal(cancel.args, (cb1,))
|
||||
Equal(after.args,
|
||||
(acp.popupwait, acp._delayed_open_completions, ac.TRY_F))
|
||||
Equal(acp._delayed_completion_id, 'after2')
|
||||
|
||||
def test_delayed_open_completions(self):
|
||||
Equal = self.assertEqual
|
||||
acp = self.autocomplete
|
||||
open_c = Func()
|
||||
acp.open_completions = open_c
|
||||
self.text.insert('1.0', '"dict.')
|
||||
|
||||
# Set autocomplete._delayed_completion_id to None.
|
||||
# Text index changed, don't call open_completions.
|
||||
acp._delayed_completion_id = 'after'
|
||||
acp._delayed_completion_index = self.text.index('insert+1c')
|
||||
acp._delayed_open_completions('dummy')
|
||||
self.assertIsNone(acp._delayed_completion_id)
|
||||
Equal(open_c.called, 0)
|
||||
|
||||
# Text index unchanged, call open_completions.
|
||||
acp._delayed_completion_index = self.text.index('insert')
|
||||
acp._delayed_open_completions((1, 2, 3, ac.FILES))
|
||||
self.assertEqual(open_c.args[0], (1, 2, 3, ac.FILES))
|
||||
|
||||
def test_oc_cancel_comment(self):
|
||||
none = self.assertIsNone
|
||||
acp = self.autocomplete
|
||||
|
||||
# Comment is in neither code or string.
|
||||
acp._delayed_completion_id = 'after'
|
||||
after = Func(result='after')
|
||||
acp.text.after_cancel = after
|
||||
self.text.insert(1.0, '# comment')
|
||||
none(acp.open_completions(ac.TAB)) # From 'else' after 'elif'.
|
||||
none(acp._delayed_completion_id)
|
||||
|
||||
def test_oc_no_list(self):
|
||||
acp = self.autocomplete
|
||||
fetch = Func(result=([],[]))
|
||||
acp.fetch_completions = fetch
|
||||
self.text.insert('1.0', 'object')
|
||||
self.assertIsNone(acp.open_completions(ac.TAB))
|
||||
self.text.insert('insert', '.')
|
||||
self.assertIsNone(acp.open_completions(ac.TAB))
|
||||
self.assertEqual(fetch.called, 2)
|
||||
|
||||
|
||||
def test_open_completions_none(self):
|
||||
# Test other two None returns.
|
||||
none = self.assertIsNone
|
||||
acp = self.autocomplete
|
||||
|
||||
# No object for attributes or need call not allowed.
|
||||
self.text.insert(1.0, '.')
|
||||
none(acp.open_completions(ac.TAB))
|
||||
self.text.insert('insert', ' int().')
|
||||
none(acp.open_completions(ac.TAB))
|
||||
|
||||
# Blank or quote trigger 'if complete ...'.
|
||||
self.text.delete(1.0, 'end')
|
||||
self.assertFalse(acp.open_completions(ac.TAB))
|
||||
self.text.insert('1.0', '"')
|
||||
self.assertFalse(acp.open_completions(ac.TAB))
|
||||
self.text.delete('1.0', 'end')
|
||||
|
||||
class dummy_acw:
|
||||
__init__ = Func()
|
||||
show_window = Func(result=False)
|
||||
hide_window = Func()
|
||||
|
||||
def test_open_completions(self):
|
||||
# Test completions of files and attributes.
|
||||
acp = self.autocomplete
|
||||
fetch = Func(result=(['tem'],['tem', '_tem']))
|
||||
acp.fetch_completions = fetch
|
||||
def make_acw(): return self.dummy_acw()
|
||||
acp._make_autocomplete_window = make_acw
|
||||
|
||||
self.text.insert('1.0', 'int.')
|
||||
acp.open_completions(ac.TAB)
|
||||
self.assertIsInstance(acp.autocompletewindow, self.dummy_acw)
|
||||
self.text.delete('1.0', 'end')
|
||||
|
||||
# Test files.
|
||||
self.text.insert('1.0', '"t')
|
||||
self.assertTrue(acp.open_completions(ac.TAB))
|
||||
self.text.delete('1.0', 'end')
|
||||
|
||||
def test_completion_kwds(self):
|
||||
self.assertIn('and', ac.completion_kwds)
|
||||
self.assertIn('case', ac.completion_kwds)
|
||||
self.assertNotIn('None', ac.completion_kwds)
|
||||
|
||||
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 '.'.
|
||||
acp = self.autocomplete
|
||||
small, large = acp.fetch_completions(
|
||||
'', ac.ATTRS)
|
||||
if hasattr(__main__, '__file__') and __main__.__file__ != ac.__file__:
|
||||
self.assertNotIn('AutoComplete', small) # See issue 36405.
|
||||
|
||||
# Test attributes
|
||||
s, b = acp.fetch_completions('', ac.ATTRS)
|
||||
self.assertLess(len(small), len(large))
|
||||
self.assertTrue(all(filter(lambda x: x.startswith('_'), s)))
|
||||
self.assertTrue(any(filter(lambda x: x.startswith('_'), b)))
|
||||
|
||||
# Test smalll should respect to __all__.
|
||||
with patch.dict('__main__.__dict__', {'__all__': ['a', 'b']}):
|
||||
s, b = acp.fetch_completions('', ac.ATTRS)
|
||||
self.assertEqual(s, ['a', 'b'])
|
||||
self.assertIn('__name__', b) # From __main__.__dict__.
|
||||
self.assertIn('sum', b) # From __main__.__builtins__.__dict__.
|
||||
self.assertIn('nonlocal', b) # From keyword.kwlist.
|
||||
pos = b.index('False') # Test False not included twice.
|
||||
self.assertNotEqual(b[pos+1], 'False')
|
||||
|
||||
# Test attributes with name entity.
|
||||
mock = Mock()
|
||||
mock._private = Mock()
|
||||
with patch.dict('__main__.__dict__', {'foo': mock}):
|
||||
s, b = acp.fetch_completions('foo', ac.ATTRS)
|
||||
self.assertNotIn('_private', s)
|
||||
self.assertIn('_private', b)
|
||||
self.assertEqual(s, [i for i in sorted(dir(mock)) if i[:1] != '_'])
|
||||
self.assertEqual(b, sorted(dir(mock)))
|
||||
|
||||
# Test files
|
||||
def _listdir(path):
|
||||
# This will be patch and used in fetch_completions.
|
||||
if path == '.':
|
||||
return ['foo', 'bar', '.hidden']
|
||||
return ['monty', 'python', '.hidden']
|
||||
|
||||
with patch.object(os, 'listdir', _listdir):
|
||||
s, b = acp.fetch_completions('', ac.FILES)
|
||||
self.assertEqual(s, ['bar', 'foo'])
|
||||
self.assertEqual(b, ['.hidden', 'bar', 'foo'])
|
||||
|
||||
s, b = acp.fetch_completions('~', ac.FILES)
|
||||
self.assertEqual(s, ['monty', 'python'])
|
||||
self.assertEqual(b, ['.hidden', 'monty', 'python'])
|
||||
|
||||
def test_get_entity(self):
|
||||
# Test that a name is in the namespace of sys.modules and
|
||||
# __main__.__dict__.
|
||||
acp = self.autocomplete
|
||||
Equal = self.assertEqual
|
||||
|
||||
Equal(acp.get_entity('int'), int)
|
||||
|
||||
# Test name from sys.modules.
|
||||
mock = Mock()
|
||||
with patch.dict('sys.modules', {'tempfile': mock}):
|
||||
Equal(acp.get_entity('tempfile'), mock)
|
||||
|
||||
# Test name from __main__.__dict__.
|
||||
di = {'foo': 10, 'bar': 20}
|
||||
with patch.dict('__main__.__dict__', {'d': di}):
|
||||
Equal(acp.get_entity('d'), di)
|
||||
|
||||
# Test name not in namespace.
|
||||
with patch.dict('__main__.__dict__', {}):
|
||||
with self.assertRaises(NameError):
|
||||
acp.get_entity('not_exist')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(verbosity=2)
|
||||
32
Dependencies/Python/Lib/idlelib/idle_test/test_autocomplete_w.py
vendored
Normal 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, tags=None)
|
||||
|
||||
@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)
|
||||
155
Dependencies/Python/Lib/idlelib/idle_test/test_autoexpand.py
vendored
Normal 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 DummyEditwin:
|
||||
# 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(DummyEditwin(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)
|
||||
257
Dependencies/Python/Lib/idlelib/idle_test/test_browser.py
vendored
Normal file
@@ -0,0 +1,257 @@
|
||||
"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 idlelib.util import py_extensions
|
||||
|
||||
from collections import deque
|
||||
import os.path
|
||||
import 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
|
||||
|
||||
def test_is_browseable_extension(self):
|
||||
path = "/path/to/file"
|
||||
for ext in py_extensions:
|
||||
with self.subTest(ext=ext):
|
||||
filename = f'{path}{ext}'
|
||||
actual = browser.is_browseable_extension(filename)
|
||||
expected = ext not in browser.browseable_extension_blocklist
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
|
||||
# Nested tree same as in test_pyclbr.py except for supers on C0. C1.
|
||||
mb = pyclbr
|
||||
module, fname = 'test', 'test.py'
|
||||
C0 = mb.Class(module, 'C0', ['base'], fname, 1, end_lineno=9)
|
||||
F1 = mb._nest_function(C0, 'F1', 3, 5)
|
||||
C1 = mb._nest_class(C0, 'C1', 6, 9, [''])
|
||||
C2 = mb._nest_class(C1, 'C2', 7, 9)
|
||||
F3 = mb._nest_function(C2, 'F3', 9, 9)
|
||||
f0 = mb.Function(module, 'f0', fname, 11, end_lineno=15)
|
||||
f1 = mb._nest_function(f0, 'f1', 12, 14)
|
||||
f2 = mb._nest_function(f1, 'f2', 13, 13)
|
||||
c1 = mb._nest_class(f0, 'c1', 15, 15)
|
||||
mock_pyclbr_tree = {'C0': C0, 'f0': f0}
|
||||
|
||||
# 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, [C0, f0])
|
||||
eq(tcl[0].name, 'C0(base)')
|
||||
eq(tcl[1].name, 'f0')
|
||||
# Check that second call does not change suffix.
|
||||
tcl = list(transform(mock_pyclbr_tree, 'test'))
|
||||
eq(tcl[0].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_once_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)
|
||||
372
Dependencies/Python/Lib/idlelib/idle_test/test_calltip.py
vendored
Normal file
@@ -0,0 +1,372 @@
|
||||
"Test calltip, coverage 76%"
|
||||
|
||||
from idlelib import calltip
|
||||
import unittest
|
||||
from unittest.mock import Mock
|
||||
import textwrap
|
||||
import types
|
||||
import re
|
||||
from idlelib.idle_test.mock_tk import Text
|
||||
from test.support import MISSING_C_DOCSTRINGS
|
||||
|
||||
|
||||
# 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)"
|
||||
def nd(self): pass # No doc.
|
||||
# attaching .tip to wrapped methods does not work
|
||||
@classmethod
|
||||
def cm(cls, a): 'doc'
|
||||
@staticmethod
|
||||
def sm(b): 'doc'
|
||||
|
||||
|
||||
tc = TC()
|
||||
default_tip = calltip._default_callable_argspec
|
||||
get_spec = calltip.get_argspec
|
||||
|
||||
|
||||
class Get_argspecTest(unittest.TestCase):
|
||||
# The get_spec 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.
|
||||
|
||||
@unittest.skipIf(MISSING_C_DOCSTRINGS,
|
||||
"Signature information for builtins requires docstrings")
|
||||
def test_builtins(self):
|
||||
|
||||
def tiptest(obj, out):
|
||||
self.assertEqual(get_spec(obj), out)
|
||||
|
||||
# Python class that inherits builtin methods
|
||||
class List(list): "List() doc"
|
||||
|
||||
# Simulate builtin with no docstring for default tip test
|
||||
class SB: __call__ = None
|
||||
|
||||
if List.__doc__ is not None:
|
||||
tiptest(List,
|
||||
f'(iterable=(), /)'
|
||||
f'\n{List.__doc__}')
|
||||
tiptest(list.__new__,
|
||||
'(*args, **kwargs)\n'
|
||||
'Create and return a new object. '
|
||||
'See help(type) for accurate signature.')
|
||||
tiptest(list.__init__,
|
||||
'(self, /, *args, **kwargs)\n'
|
||||
'Initialize self. See help(type(self)) for accurate signature.')
|
||||
append_doc = "\nAppend object to the end of the list."
|
||||
tiptest(list.append, '(self, object, /)' + append_doc)
|
||||
tiptest(List.append, '(self, object, /)' + append_doc)
|
||||
tiptest([].append, '(object, /)' + append_doc)
|
||||
# The use of 'object' above matches the signature text.
|
||||
|
||||
tiptest(types.MethodType,
|
||||
'(function, instance, /)\n'
|
||||
'Create a bound instance method object.')
|
||||
tiptest(SB(), default_tip)
|
||||
|
||||
p = re.compile('')
|
||||
tiptest(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''')
|
||||
tiptest(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(get_spec(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=' [...]')
|
||||
Object for wrapping/filling text. The public interface consists of
|
||||
the wrap() and fill() methods; the other methods are just there for
|
||||
subclasses to override in order to tweak the default behaviour.
|
||||
If you want to completely replace the main wrapping algorithm,
|
||||
you\'ll probably have to override _wrap_chunks().''')
|
||||
|
||||
def test_properly_formatted(self):
|
||||
|
||||
def foo(s='a'*100):
|
||||
pass
|
||||
|
||||
def bar(s='a'*100):
|
||||
"""Hello Guido"""
|
||||
pass
|
||||
|
||||
def baz(s='a'*100, z='b'*100):
|
||||
pass
|
||||
|
||||
indent = calltip._INDENT
|
||||
|
||||
sfoo = "(s='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"\
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" + indent + "aaaaaaaaa"\
|
||||
"aaaaaaaaaa')"
|
||||
sbar = "(s='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"\
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" + indent + "aaaaaaaaa"\
|
||||
"aaaaaaaaaa')\nHello Guido"
|
||||
sbaz = "(s='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"\
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" + indent + "aaaaaaaaa"\
|
||||
"aaaaaaaaaa', z='bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"\
|
||||
"bbbbbbbbbbbbbbbbb\n" + indent + "bbbbbbbbbbbbbbbbbbbbbb"\
|
||||
"bbbbbbbbbbbbbbbbbbbbbb')"
|
||||
|
||||
for func,doc in [(foo, sfoo), (bar, sbar), (baz, sbaz)]:
|
||||
with self.subTest(func=func, doc=doc):
|
||||
self.assertEqual(get_spec(func), doc)
|
||||
|
||||
def test_docline_truncation(self):
|
||||
def f(): pass
|
||||
f.__doc__ = 'a'*300
|
||||
self.assertEqual(get_spec(f), f"()\n{'a'*(calltip._MAX_COLS-3) + '...'}")
|
||||
|
||||
@unittest.skipIf(MISSING_C_DOCSTRINGS,
|
||||
"Signature information for builtins requires docstrings")
|
||||
def test_multiline_docstring(self):
|
||||
# Test fewer lines than max.
|
||||
self.assertEqual(get_spec(range),
|
||||
"range(stop) -> range object\n"
|
||||
"range(start, stop[, step]) -> range object")
|
||||
|
||||
# Test max lines
|
||||
self.assertEqual(get_spec(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''')
|
||||
|
||||
def test_multiline_docstring_2(self):
|
||||
# Test more than max lines
|
||||
def f(): pass
|
||||
f.__doc__ = 'a\n' * 15
|
||||
self.assertEqual(get_spec(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):
|
||||
with self.subTest(func=func):
|
||||
self.assertEqual(get_spec(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__):
|
||||
with self.subTest(meth=meth):
|
||||
self.assertEqual(get_spec(meth), meth.tip + doc)
|
||||
self.assertEqual(get_spec(TC.cm), "(a)" + doc)
|
||||
self.assertEqual(get_spec(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)"),):
|
||||
with self.subTest(meth=meth, mtip=mtip):
|
||||
self.assertEqual(get_spec(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)"),):
|
||||
with self.subTest(meth=meth, mtip=mtip):
|
||||
self.assertEqual(get_spec(meth), mtip)
|
||||
|
||||
def test_invalid_method_get_spec(self):
|
||||
class C:
|
||||
def m2(**kwargs): pass
|
||||
class Test:
|
||||
def __call__(*, a): pass
|
||||
|
||||
mtip = calltip._invalid_method
|
||||
self.assertEqual(get_spec(C().m2), mtip)
|
||||
self.assertEqual(get_spec(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):
|
||||
for meth, mtip in ((TC.nd, "(self)"), (tc.nd, "()")):
|
||||
with self.subTest(meth=meth, mtip=mtip):
|
||||
self.assertEqual(get_spec(meth), mtip)
|
||||
|
||||
def test_buggy_getattr_class(self):
|
||||
class NoCall:
|
||||
def __getattr__(self, name): # Not invoked for class attribute.
|
||||
raise IndexError # Bug.
|
||||
class CallA(NoCall):
|
||||
def __call__(self, ci): # Bug does not matter.
|
||||
pass
|
||||
class CallB(NoCall):
|
||||
def __call__(oui, a, b, c): # Non-standard 'self'.
|
||||
pass
|
||||
|
||||
for meth, mtip in ((NoCall, default_tip), (CallA, default_tip),
|
||||
(NoCall(), ''), (CallA(), '(ci)'),
|
||||
(CallB(), '(a, b, c)')):
|
||||
with self.subTest(meth=meth, mtip=mtip):
|
||||
self.assertEqual(get_spec(meth), mtip)
|
||||
|
||||
def test_metaclass_class(self): # Failure case for issue 38689.
|
||||
class Type(type): # Type() requires 3 type args, returns class.
|
||||
__class__ = property({}.__getitem__, {}.__setitem__)
|
||||
class Object(metaclass=Type):
|
||||
__slots__ = '__class__'
|
||||
for meth, mtip in ((Type, get_spec(type)), (Object, default_tip),
|
||||
(Object(), '')):
|
||||
with self.subTest(meth=meth, mtip=mtip):
|
||||
self.assertEqual(get_spec(meth), mtip)
|
||||
|
||||
def test_non_callables(self):
|
||||
for obj in (0, 0.0, '0', b'0', [], {}):
|
||||
with self.subTest(obj=obj):
|
||||
self.assertEqual(get_spec(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)
|
||||
|
||||
|
||||
# Test the 9 Calltip methods.
|
||||
# open_calltip is about half the code; the others are fairly trivial.
|
||||
# The default mocks are what are needed for open_calltip.
|
||||
|
||||
class mock_Shell:
|
||||
"Return mock sufficient to pass to hyperparser."
|
||||
def __init__(self, text):
|
||||
text.tag_prevrange = Mock(return_value=None)
|
||||
self.text = text
|
||||
self.prompt_last_line = ">>> "
|
||||
self.indentwidth = 4
|
||||
self.tabwidth = 8
|
||||
|
||||
|
||||
class mock_TipWindow:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def showtip(self, text, parenleft, parenright):
|
||||
self.args = parenleft, parenright
|
||||
self.parenline, self.parencol = map(int, parenleft.split('.'))
|
||||
|
||||
|
||||
class WrappedCalltip(calltip.Calltip):
|
||||
def _make_tk_calltip_window(self):
|
||||
return mock_TipWindow()
|
||||
|
||||
def remove_calltip_window(self, event=None):
|
||||
if self.active_calltip: # Setup to None.
|
||||
self.active_calltip = None
|
||||
self.tips_removed += 1 # Setup to 0.
|
||||
|
||||
def fetch_tip(self, expression):
|
||||
return 'tip'
|
||||
|
||||
|
||||
class CalltipTest(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.text = Text()
|
||||
cls.ct = WrappedCalltip(mock_Shell(cls.text))
|
||||
|
||||
def setUp(self):
|
||||
self.text.delete('1.0', 'end') # Insert and call
|
||||
self.ct.active_calltip = None
|
||||
# Test .active_calltip, +args
|
||||
self.ct.tips_removed = 0
|
||||
|
||||
def open_close(self, testfunc):
|
||||
# Open-close template with testfunc called in between.
|
||||
opentip = self.ct.open_calltip
|
||||
self.text.insert(1.0, 'f(')
|
||||
opentip(False)
|
||||
self.tip = self.ct.active_calltip
|
||||
testfunc(self) ###
|
||||
self.text.insert('insert', ')')
|
||||
opentip(False)
|
||||
self.assertIsNone(self.ct.active_calltip, None)
|
||||
|
||||
def test_open_close(self):
|
||||
def args(self):
|
||||
self.assertEqual(self.tip.args, ('1.1', '1.end'))
|
||||
self.open_close(args)
|
||||
|
||||
def test_repeated_force(self):
|
||||
def force(self):
|
||||
for char in 'abc':
|
||||
self.text.insert('insert', 'a')
|
||||
self.ct.open_calltip(True)
|
||||
self.ct.open_calltip(True)
|
||||
self.assertIs(self.ct.active_calltip, self.tip)
|
||||
self.open_close(force)
|
||||
|
||||
def test_repeated_parens(self):
|
||||
def parens(self):
|
||||
for context in "a", "'":
|
||||
with self.subTest(context=context):
|
||||
self.text.insert('insert', context)
|
||||
for char in '(()())':
|
||||
self.text.insert('insert', char)
|
||||
self.assertIs(self.ct.active_calltip, self.tip)
|
||||
self.text.insert('insert', "'")
|
||||
self.open_close(parens)
|
||||
|
||||
def test_comment_parens(self):
|
||||
def comment(self):
|
||||
self.text.insert('insert', "# ")
|
||||
for char in '(()())':
|
||||
self.text.insert('insert', char)
|
||||
self.assertIs(self.ct.active_calltip, self.tip)
|
||||
self.text.insert('insert', "\n")
|
||||
self.open_close(comment)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(verbosity=2)
|
||||
29
Dependencies/Python/Lib/idlelib/idle_test/test_calltip_w.py
vendored
Normal 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)
|
||||
455
Dependencies/Python/Lib/idlelib/idle_test/test_codecontext.py
vendored
Normal file
@@ -0,0 +1,455 @@
|
||||
"Test codecontext, coverage 100%"
|
||||
|
||||
from idlelib import codecontext
|
||||
import unittest
|
||||
import unittest.mock
|
||||
from test.support import requires
|
||||
from tkinter import NSEW, 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
|
||||
self.label = ''
|
||||
|
||||
def getlineno(self, index):
|
||||
return int(float(self.text.index(index)))
|
||||
|
||||
def update_menu_label(self, **kwargs):
|
||||
self.label = kwargs['label']
|
||||
|
||||
|
||||
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.grid(row=1, column=1, sticky=NSEW)
|
||||
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.text['font'] = 'TkFixedFont'
|
||||
self.cc = codecontext.CodeContext(self.editor)
|
||||
|
||||
self.highlight_cfg = {"background": '#abcdef',
|
||||
"foreground": '#123456'}
|
||||
orig_idleConf_GetHighlight = codecontext.idleConf.GetHighlight
|
||||
def mock_idleconf_GetHighlight(theme, element):
|
||||
if element == 'context':
|
||||
return self.highlight_cfg
|
||||
return orig_idleConf_GetHighlight(theme, element)
|
||||
GetHighlight_patcher = unittest.mock.patch.object(
|
||||
codecontext.idleConf, 'GetHighlight', mock_idleconf_GetHighlight)
|
||||
GetHighlight_patcher.start()
|
||||
self.addCleanup(GetHighlight_patcher.stop)
|
||||
|
||||
self.font_override = 'TkFixedFont'
|
||||
def mock_idleconf_GetFont(root, configType, section):
|
||||
return self.font_override
|
||||
GetFont_patcher = unittest.mock.patch.object(
|
||||
codecontext.idleConf, 'GetFont', mock_idleconf_GetFont)
|
||||
GetFont_patcher.start()
|
||||
self.addCleanup(GetFont_patcher.stop)
|
||||
|
||||
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.text['font'], ed.text['font'])
|
||||
self.assertIsNone(cc.context)
|
||||
eq(cc.info, [(0, -1, '', False)])
|
||||
eq(cc.topvisible, 1)
|
||||
self.assertIsNone(self.cc.t1)
|
||||
|
||||
def test_del(self):
|
||||
self.cc.__del__()
|
||||
|
||||
def test_del_with_timer(self):
|
||||
timer = self.cc.t1 = self.text.after(10000, lambda: None)
|
||||
self.cc.__del__()
|
||||
with self.assertRaises(TclError) as cm:
|
||||
self.root.tk.call('after', 'info', timer)
|
||||
self.assertIn("doesn't exist", str(cm.exception))
|
||||
|
||||
def test_reload(self):
|
||||
codecontext.CodeContext.reload()
|
||||
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.
|
||||
toggle()
|
||||
self.assertIsNotNone(cc.context)
|
||||
eq(cc.context['font'], self.text['font'])
|
||||
eq(cc.context['fg'], self.highlight_cfg['foreground'])
|
||||
eq(cc.context['bg'], self.highlight_cfg['background'])
|
||||
eq(cc.context.get('1.0', 'end-1c'), '')
|
||||
eq(cc.editwin.label, 'Hide Code Context')
|
||||
eq(self.root.tk.call('after', 'info', self.cc.t1)[1], 'timer')
|
||||
|
||||
# Toggle off.
|
||||
toggle()
|
||||
self.assertIsNone(cc.context)
|
||||
eq(cc.editwin.label, 'Show Code Context')
|
||||
self.assertIsNone(self.cc.t1)
|
||||
|
||||
# Scroll down and toggle back on.
|
||||
line11_context = '\n'.join(x[2] for x in cc.get_context(11)[0])
|
||||
cc.text.yview(11)
|
||||
toggle()
|
||||
eq(cc.context.get('1.0', 'end-1c'), line11_context)
|
||||
|
||||
# Toggle off and on again.
|
||||
toggle()
|
||||
toggle()
|
||||
eq(cc.context.get('1.0', 'end-1c'), line11_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('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('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('12.0')
|
||||
cc.update_code_context()
|
||||
eq(cc.topvisible, 12)
|
||||
cc.context.mark_set('insert', '1.0')
|
||||
jump()
|
||||
eq(cc.topvisible, 8)
|
||||
|
||||
# Context selection stops jump.
|
||||
cc.text.yview('5.0')
|
||||
cc.update_code_context()
|
||||
cc.context.tag_add('sel', '1.0', '2.0')
|
||||
cc.context.mark_set('insert', '1.0')
|
||||
jump() # Without selection, to line 2.
|
||||
eq(cc.topvisible, 5)
|
||||
|
||||
@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_font(self):
|
||||
eq = self.assertEqual
|
||||
cc = self.cc
|
||||
|
||||
orig_font = cc.text['font']
|
||||
test_font = 'TkTextFont'
|
||||
self.assertNotEqual(orig_font, test_font)
|
||||
|
||||
# Ensure code context is not active.
|
||||
if cc.context is not None:
|
||||
cc.toggle_code_context_event()
|
||||
|
||||
self.font_override = test_font
|
||||
# Nothing breaks or changes with inactive code context.
|
||||
cc.update_font()
|
||||
|
||||
# Activate code context, previous font change is immediately effective.
|
||||
cc.toggle_code_context_event()
|
||||
eq(cc.context['font'], test_font)
|
||||
|
||||
# Call the font update, change is picked up.
|
||||
self.font_override = orig_font
|
||||
cc.update_font()
|
||||
eq(cc.context['font'], orig_font)
|
||||
|
||||
def test_highlight_colors(self):
|
||||
eq = self.assertEqual
|
||||
cc = self.cc
|
||||
|
||||
orig_colors = dict(self.highlight_cfg)
|
||||
test_colors = {'background': '#222222', 'foreground': '#ffff00'}
|
||||
|
||||
def assert_colors_are_equal(colors):
|
||||
eq(cc.context['background'], colors['background'])
|
||||
eq(cc.context['foreground'], colors['foreground'])
|
||||
|
||||
# Ensure code context is not active.
|
||||
if cc.context:
|
||||
cc.toggle_code_context_event()
|
||||
|
||||
self.highlight_cfg = test_colors
|
||||
# Nothing breaks with inactive code context.
|
||||
cc.update_highlight_colors()
|
||||
|
||||
# Activate code context, previous colors change is immediately effective.
|
||||
cc.toggle_code_context_event()
|
||||
assert_colors_are_equal(test_colors)
|
||||
|
||||
# Call colors update with no change to the configured colors.
|
||||
cc.update_highlight_colors()
|
||||
assert_colors_are_equal(test_colors)
|
||||
|
||||
# Call the colors update with code context active, change is picked up.
|
||||
self.highlight_cfg = orig_colors
|
||||
cc.update_highlight_colors()
|
||||
assert_colors_are_equal(orig_colors)
|
||||
|
||||
|
||||
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)
|
||||
622
Dependencies/Python/Lib/idlelib/idle_test/test_colorizer.py
vendored
Normal file
@@ -0,0 +1,622 @@
|
||||
"Test colorizer, coverage 99%."
|
||||
from idlelib import colorizer
|
||||
from test.support import requires
|
||||
import unittest
|
||||
from unittest import mock
|
||||
from idlelib.idle_test.tkinter_testing_utils import run_in_tk_mainloop
|
||||
|
||||
from functools import partial
|
||||
import textwrap
|
||||
from tkinter import Tk, Text
|
||||
from idlelib import config
|
||||
from idlelib.percolator import Percolator
|
||||
|
||||
|
||||
usercfg = colorizer.idleConf.userCfg
|
||||
testcfg = {
|
||||
'main': config.IdleUserConfParser(''),
|
||||
'highlight': config.IdleUserConfParser(''),
|
||||
'keys': config.IdleUserConfParser(''),
|
||||
'extensions': config.IdleUserConfParser(''),
|
||||
}
|
||||
|
||||
source = textwrap.dedent("""\
|
||||
if True: int ('1') # keyword, builtin, string, comment
|
||||
elif False: print(0) # 'string' in comment
|
||||
else: float(None) # if in comment
|
||||
if iF + If + IF: 'keyword matching must respect case'
|
||||
if'': x or'' # valid keyword-string no-space combinations
|
||||
async def f(): await g()
|
||||
# Strings should be entirely colored, including quotes.
|
||||
'x', '''x''', "x", \"""x\"""
|
||||
'abc\\
|
||||
def'
|
||||
'''abc\\
|
||||
def'''
|
||||
# All valid prefixes for unicode and byte strings should be colored.
|
||||
r'x', u'x', R'x', U'x', f'x', F'x'
|
||||
fr'x', Fr'x', fR'x', FR'x', rf'x', rF'x', Rf'x', RF'x'
|
||||
b'x',B'x', br'x',Br'x',bR'x',BR'x', rb'x', rB'x',Rb'x',RB'x'
|
||||
# Invalid combinations of legal characters should be half colored.
|
||||
ur'x', ru'x', uf'x', fu'x', UR'x', ufr'x', rfu'x', xf'x', fx'x'
|
||||
match point:
|
||||
case (x, 0) as _:
|
||||
print(f"X={x}")
|
||||
case [_, [_], "_",
|
||||
_]:
|
||||
pass
|
||||
case _ if ("a" if _ else set()): pass
|
||||
case _:
|
||||
raise ValueError("Not a point _")
|
||||
'''
|
||||
case _:'''
|
||||
"match x:"
|
||||
""")
|
||||
|
||||
|
||||
def setUpModule():
|
||||
colorizer.idleConf.userCfg = testcfg
|
||||
|
||||
|
||||
def tearDownModule():
|
||||
colorizer.idleConf.userCfg = usercfg
|
||||
|
||||
|
||||
class FunctionTest(unittest.TestCase):
|
||||
|
||||
def test_any(self):
|
||||
self.assertEqual(colorizer.any('test', ('a', 'b', 'cd')),
|
||||
'(?P<test>a|b|cd)')
|
||||
|
||||
def test_make_pat(self):
|
||||
# Tested in more detail by testing prog.
|
||||
self.assertTrue(colorizer.make_pat())
|
||||
|
||||
def test_prog(self):
|
||||
prog = colorizer.prog
|
||||
eq = self.assertEqual
|
||||
line = 'def f():\n print("hello")\n'
|
||||
m = prog.search(line)
|
||||
eq(m.groupdict()['KEYWORD'], 'def')
|
||||
m = prog.search(line, m.end())
|
||||
eq(m.groupdict()['SYNC'], '\n')
|
||||
m = prog.search(line, m.end())
|
||||
eq(m.groupdict()['BUILTIN'], 'print')
|
||||
m = prog.search(line, m.end())
|
||||
eq(m.groupdict()['STRING'], '"hello"')
|
||||
m = prog.search(line, m.end())
|
||||
eq(m.groupdict()['SYNC'], '\n')
|
||||
|
||||
def test_idprog(self):
|
||||
idprog = colorizer.idprog
|
||||
m = idprog.match('nospace')
|
||||
self.assertIsNone(m)
|
||||
m = idprog.match(' space')
|
||||
self.assertEqual(m.group(0), ' space')
|
||||
|
||||
|
||||
class ColorConfigTest(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
requires('gui')
|
||||
root = cls.root = Tk()
|
||||
root.withdraw()
|
||||
cls.text = Text(root)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
del cls.text
|
||||
cls.root.update_idletasks()
|
||||
cls.root.destroy()
|
||||
del cls.root
|
||||
|
||||
def test_color_config(self):
|
||||
text = self.text
|
||||
eq = self.assertEqual
|
||||
colorizer.color_config(text)
|
||||
# Uses IDLE Classic theme as default.
|
||||
eq(text['background'], '#ffffff')
|
||||
eq(text['foreground'], '#000000')
|
||||
eq(text['selectbackground'], 'gray')
|
||||
eq(text['selectforeground'], '#000000')
|
||||
eq(text['insertbackground'], 'black')
|
||||
eq(text['inactiveselectbackground'], 'gray')
|
||||
|
||||
|
||||
class ColorDelegatorInstantiationTest(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
requires('gui')
|
||||
root = cls.root = Tk()
|
||||
root.withdraw()
|
||||
cls.text = Text(root)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
del cls.text
|
||||
cls.root.update_idletasks()
|
||||
cls.root.destroy()
|
||||
del cls.root
|
||||
|
||||
def setUp(self):
|
||||
self.color = colorizer.ColorDelegator()
|
||||
|
||||
def tearDown(self):
|
||||
self.color.close()
|
||||
self.text.delete('1.0', 'end')
|
||||
self.color.resetcache()
|
||||
del self.color
|
||||
|
||||
def test_init(self):
|
||||
color = self.color
|
||||
self.assertIsInstance(color, colorizer.ColorDelegator)
|
||||
|
||||
def test_init_state(self):
|
||||
# init_state() is called during the instantiation of
|
||||
# ColorDelegator in setUp().
|
||||
color = self.color
|
||||
self.assertIsNone(color.after_id)
|
||||
self.assertTrue(color.allow_colorizing)
|
||||
self.assertFalse(color.colorizing)
|
||||
self.assertFalse(color.stop_colorizing)
|
||||
|
||||
|
||||
class ColorDelegatorTest(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
requires('gui')
|
||||
root = cls.root = Tk()
|
||||
root.withdraw()
|
||||
text = cls.text = Text(root)
|
||||
cls.percolator = Percolator(text)
|
||||
# Delegator stack = [Delegator(text)]
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.percolator.close()
|
||||
del cls.percolator, cls.text
|
||||
cls.root.update_idletasks()
|
||||
cls.root.destroy()
|
||||
del cls.root
|
||||
|
||||
def setUp(self):
|
||||
self.color = colorizer.ColorDelegator()
|
||||
self.percolator.insertfilter(self.color)
|
||||
# Calls color.setdelegate(Delegator(text)).
|
||||
|
||||
def tearDown(self):
|
||||
self.color.close()
|
||||
self.percolator.removefilter(self.color)
|
||||
self.text.delete('1.0', 'end')
|
||||
self.color.resetcache()
|
||||
del self.color
|
||||
|
||||
def test_setdelegate(self):
|
||||
# Called in setUp when filter is attached to percolator.
|
||||
color = self.color
|
||||
self.assertIsInstance(color.delegate, colorizer.Delegator)
|
||||
# It is too late to mock notify_range, so test side effect.
|
||||
self.assertEqual(self.root.tk.call(
|
||||
'after', 'info', color.after_id)[1], 'timer')
|
||||
|
||||
def test_LoadTagDefs(self):
|
||||
highlight = partial(config.idleConf.GetHighlight, theme='IDLE Classic')
|
||||
for tag, colors in self.color.tagdefs.items():
|
||||
with self.subTest(tag=tag):
|
||||
self.assertIn('background', colors)
|
||||
self.assertIn('foreground', colors)
|
||||
if tag not in ('SYNC', 'TODO'):
|
||||
self.assertEqual(colors, highlight(element=tag.lower()))
|
||||
|
||||
def test_config_colors(self):
|
||||
text = self.text
|
||||
highlight = partial(config.idleConf.GetHighlight, theme='IDLE Classic')
|
||||
for tag in self.color.tagdefs:
|
||||
for plane in ('background', 'foreground'):
|
||||
with self.subTest(tag=tag, plane=plane):
|
||||
if tag in ('SYNC', 'TODO'):
|
||||
self.assertEqual(text.tag_cget(tag, plane), '')
|
||||
else:
|
||||
self.assertEqual(text.tag_cget(tag, plane),
|
||||
highlight(element=tag.lower())[plane])
|
||||
# 'sel' is marked as the highest priority.
|
||||
self.assertEqual(text.tag_names()[-1], 'sel')
|
||||
|
||||
@mock.patch.object(colorizer.ColorDelegator, 'notify_range')
|
||||
def test_insert(self, mock_notify):
|
||||
text = self.text
|
||||
# Initial text.
|
||||
text.insert('insert', 'foo')
|
||||
self.assertEqual(text.get('1.0', 'end'), 'foo\n')
|
||||
mock_notify.assert_called_with('1.0', '1.0+3c')
|
||||
# Additional text.
|
||||
text.insert('insert', 'barbaz')
|
||||
self.assertEqual(text.get('1.0', 'end'), 'foobarbaz\n')
|
||||
mock_notify.assert_called_with('1.3', '1.3+6c')
|
||||
|
||||
@mock.patch.object(colorizer.ColorDelegator, 'notify_range')
|
||||
def test_delete(self, mock_notify):
|
||||
text = self.text
|
||||
# Initialize text.
|
||||
text.insert('insert', 'abcdefghi')
|
||||
self.assertEqual(text.get('1.0', 'end'), 'abcdefghi\n')
|
||||
# Delete single character.
|
||||
text.delete('1.7')
|
||||
self.assertEqual(text.get('1.0', 'end'), 'abcdefgi\n')
|
||||
mock_notify.assert_called_with('1.7')
|
||||
# Delete multiple characters.
|
||||
text.delete('1.3', '1.6')
|
||||
self.assertEqual(text.get('1.0', 'end'), 'abcgi\n')
|
||||
mock_notify.assert_called_with('1.3')
|
||||
|
||||
def test_notify_range(self):
|
||||
text = self.text
|
||||
color = self.color
|
||||
eq = self.assertEqual
|
||||
|
||||
# Colorizing already scheduled.
|
||||
save_id = color.after_id
|
||||
eq(self.root.tk.call('after', 'info', save_id)[1], 'timer')
|
||||
self.assertFalse(color.colorizing)
|
||||
self.assertFalse(color.stop_colorizing)
|
||||
self.assertTrue(color.allow_colorizing)
|
||||
|
||||
# Coloring scheduled and colorizing in progress.
|
||||
color.colorizing = True
|
||||
color.notify_range('1.0', 'end')
|
||||
self.assertFalse(color.stop_colorizing)
|
||||
eq(color.after_id, save_id)
|
||||
|
||||
# No colorizing scheduled and colorizing in progress.
|
||||
text.after_cancel(save_id)
|
||||
color.after_id = None
|
||||
color.notify_range('1.0', '1.0+3c')
|
||||
self.assertTrue(color.stop_colorizing)
|
||||
self.assertIsNotNone(color.after_id)
|
||||
eq(self.root.tk.call('after', 'info', color.after_id)[1], 'timer')
|
||||
# New event scheduled.
|
||||
self.assertNotEqual(color.after_id, save_id)
|
||||
|
||||
# No colorizing scheduled and colorizing off.
|
||||
text.after_cancel(color.after_id)
|
||||
color.after_id = None
|
||||
color.allow_colorizing = False
|
||||
color.notify_range('1.4', '1.4+10c')
|
||||
# Nothing scheduled when colorizing is off.
|
||||
self.assertIsNone(color.after_id)
|
||||
|
||||
def test_toggle_colorize_event(self):
|
||||
color = self.color
|
||||
eq = self.assertEqual
|
||||
|
||||
# Starts with colorizing allowed and scheduled.
|
||||
self.assertFalse(color.colorizing)
|
||||
self.assertFalse(color.stop_colorizing)
|
||||
self.assertTrue(color.allow_colorizing)
|
||||
eq(self.root.tk.call('after', 'info', color.after_id)[1], 'timer')
|
||||
|
||||
# Toggle colorizing off.
|
||||
color.toggle_colorize_event()
|
||||
self.assertIsNone(color.after_id)
|
||||
self.assertFalse(color.colorizing)
|
||||
self.assertFalse(color.stop_colorizing)
|
||||
self.assertFalse(color.allow_colorizing)
|
||||
|
||||
# Toggle on while colorizing in progress (doesn't add timer).
|
||||
color.colorizing = True
|
||||
color.toggle_colorize_event()
|
||||
self.assertIsNone(color.after_id)
|
||||
self.assertTrue(color.colorizing)
|
||||
self.assertFalse(color.stop_colorizing)
|
||||
self.assertTrue(color.allow_colorizing)
|
||||
|
||||
# Toggle off while colorizing in progress.
|
||||
color.toggle_colorize_event()
|
||||
self.assertIsNone(color.after_id)
|
||||
self.assertTrue(color.colorizing)
|
||||
self.assertTrue(color.stop_colorizing)
|
||||
self.assertFalse(color.allow_colorizing)
|
||||
|
||||
# Toggle on while colorizing not in progress.
|
||||
color.colorizing = False
|
||||
color.toggle_colorize_event()
|
||||
eq(self.root.tk.call('after', 'info', color.after_id)[1], 'timer')
|
||||
self.assertFalse(color.colorizing)
|
||||
self.assertTrue(color.stop_colorizing)
|
||||
self.assertTrue(color.allow_colorizing)
|
||||
|
||||
@mock.patch.object(colorizer.ColorDelegator, 'recolorize_main')
|
||||
def test_recolorize(self, mock_recmain):
|
||||
text = self.text
|
||||
color = self.color
|
||||
eq = self.assertEqual
|
||||
# Call recolorize manually and not scheduled.
|
||||
text.after_cancel(color.after_id)
|
||||
|
||||
# No delegate.
|
||||
save_delegate = color.delegate
|
||||
color.delegate = None
|
||||
color.recolorize()
|
||||
mock_recmain.assert_not_called()
|
||||
color.delegate = save_delegate
|
||||
|
||||
# Toggle off colorizing.
|
||||
color.allow_colorizing = False
|
||||
color.recolorize()
|
||||
mock_recmain.assert_not_called()
|
||||
color.allow_colorizing = True
|
||||
|
||||
# Colorizing in progress.
|
||||
color.colorizing = True
|
||||
color.recolorize()
|
||||
mock_recmain.assert_not_called()
|
||||
color.colorizing = False
|
||||
|
||||
# Colorizing is done, but not completed, so rescheduled.
|
||||
color.recolorize()
|
||||
self.assertFalse(color.stop_colorizing)
|
||||
self.assertFalse(color.colorizing)
|
||||
mock_recmain.assert_called()
|
||||
eq(mock_recmain.call_count, 1)
|
||||
# Rescheduled when TODO tag still exists.
|
||||
eq(self.root.tk.call('after', 'info', color.after_id)[1], 'timer')
|
||||
|
||||
# No changes to text, so no scheduling added.
|
||||
text.tag_remove('TODO', '1.0', 'end')
|
||||
color.recolorize()
|
||||
self.assertFalse(color.stop_colorizing)
|
||||
self.assertFalse(color.colorizing)
|
||||
mock_recmain.assert_called()
|
||||
eq(mock_recmain.call_count, 2)
|
||||
self.assertIsNone(color.after_id)
|
||||
|
||||
@mock.patch.object(colorizer.ColorDelegator, 'notify_range')
|
||||
def test_recolorize_main(self, mock_notify):
|
||||
text = self.text
|
||||
color = self.color
|
||||
eq = self.assertEqual
|
||||
|
||||
text.insert('insert', source)
|
||||
expected = (('1.0', ('KEYWORD',)), ('1.2', ()), ('1.3', ('KEYWORD',)),
|
||||
('1.7', ()), ('1.9', ('BUILTIN',)), ('1.14', ('STRING',)),
|
||||
('1.19', ('COMMENT',)),
|
||||
('2.1', ('KEYWORD',)), ('2.18', ()), ('2.25', ('COMMENT',)),
|
||||
('3.6', ('BUILTIN',)), ('3.12', ('KEYWORD',)), ('3.21', ('COMMENT',)),
|
||||
('4.0', ('KEYWORD',)), ('4.3', ()), ('4.6', ()),
|
||||
('5.2', ('STRING',)), ('5.8', ('KEYWORD',)), ('5.10', ('STRING',)),
|
||||
('6.0', ('KEYWORD',)), ('6.10', ('DEFINITION',)), ('6.11', ()),
|
||||
('8.0', ('STRING',)), ('8.4', ()), ('8.5', ('STRING',)),
|
||||
('8.12', ()), ('8.14', ('STRING',)),
|
||||
('19.0', ('KEYWORD',)),
|
||||
('20.4', ('KEYWORD',)), ('20.16', ('KEYWORD',)),# ('20.19', ('KEYWORD',)),
|
||||
#('22.4', ('KEYWORD',)), ('22.10', ('KEYWORD',)), ('22.14', ('KEYWORD',)), ('22.19', ('STRING',)),
|
||||
#('23.12', ('KEYWORD',)),
|
||||
('24.8', ('KEYWORD',)),
|
||||
('25.4', ('KEYWORD',)), ('25.9', ('KEYWORD',)),
|
||||
('25.11', ('KEYWORD',)), ('25.15', ('STRING',)),
|
||||
('25.19', ('KEYWORD',)), ('25.22', ()),
|
||||
('25.24', ('KEYWORD',)), ('25.29', ('BUILTIN',)), ('25.37', ('KEYWORD',)),
|
||||
('26.4', ('KEYWORD',)), ('26.9', ('KEYWORD',)),# ('26.11', ('KEYWORD',)), ('26.14', (),),
|
||||
('27.25', ('STRING',)), ('27.38', ('STRING',)),
|
||||
('29.0', ('STRING',)),
|
||||
('30.1', ('STRING',)),
|
||||
# SYNC at the end of every line.
|
||||
('1.55', ('SYNC',)), ('2.50', ('SYNC',)), ('3.34', ('SYNC',)),
|
||||
)
|
||||
|
||||
# Nothing marked to do therefore no tags in text.
|
||||
text.tag_remove('TODO', '1.0', 'end')
|
||||
color.recolorize_main()
|
||||
for tag in text.tag_names():
|
||||
with self.subTest(tag=tag):
|
||||
eq(text.tag_ranges(tag), ())
|
||||
|
||||
# Source marked for processing.
|
||||
text.tag_add('TODO', '1.0', 'end')
|
||||
# Check some indexes.
|
||||
color.recolorize_main()
|
||||
for index, expected_tags in expected:
|
||||
with self.subTest(index=index):
|
||||
eq(text.tag_names(index), expected_tags)
|
||||
|
||||
# Check for some tags for ranges.
|
||||
eq(text.tag_nextrange('TODO', '1.0'), ())
|
||||
eq(text.tag_nextrange('KEYWORD', '1.0'), ('1.0', '1.2'))
|
||||
eq(text.tag_nextrange('COMMENT', '2.0'), ('2.22', '2.43'))
|
||||
eq(text.tag_nextrange('SYNC', '2.0'), ('2.43', '3.0'))
|
||||
eq(text.tag_nextrange('STRING', '2.0'), ('4.17', '4.53'))
|
||||
eq(text.tag_nextrange('STRING', '8.0'), ('8.0', '8.3'))
|
||||
eq(text.tag_nextrange('STRING', '8.3'), ('8.5', '8.12'))
|
||||
eq(text.tag_nextrange('STRING', '8.12'), ('8.14', '8.17'))
|
||||
eq(text.tag_nextrange('STRING', '8.17'), ('8.19', '8.26'))
|
||||
eq(text.tag_nextrange('SYNC', '8.0'), ('8.26', '9.0'))
|
||||
eq(text.tag_nextrange('SYNC', '30.0'), ('30.10', '32.0'))
|
||||
|
||||
def _assert_highlighting(self, source, tag_ranges):
|
||||
"""Check highlighting of a given piece of code.
|
||||
|
||||
This inserts just this code into the Text widget. It will then
|
||||
check that the resulting highlighting tag ranges exactly match
|
||||
those described in the given `tag_ranges` dict.
|
||||
|
||||
Note that the irrelevant tags 'sel', 'TODO' and 'SYNC' are
|
||||
ignored.
|
||||
"""
|
||||
text = self.text
|
||||
|
||||
with mock.patch.object(colorizer.ColorDelegator, 'notify_range'):
|
||||
text.delete('1.0', 'end-1c')
|
||||
text.insert('insert', source)
|
||||
text.tag_add('TODO', '1.0', 'end-1c')
|
||||
self.color.recolorize_main()
|
||||
|
||||
# Make a dict with highlighting tag ranges in the Text widget.
|
||||
text_tag_ranges = {}
|
||||
for tag in set(text.tag_names()) - {'sel', 'TODO', 'SYNC'}:
|
||||
indexes = [rng.string for rng in text.tag_ranges(tag)]
|
||||
for index_pair in zip(indexes[::2], indexes[1::2]):
|
||||
text_tag_ranges.setdefault(tag, []).append(index_pair)
|
||||
|
||||
self.assertEqual(text_tag_ranges, tag_ranges)
|
||||
|
||||
with mock.patch.object(colorizer.ColorDelegator, 'notify_range'):
|
||||
text.delete('1.0', 'end-1c')
|
||||
|
||||
def test_def_statement(self):
|
||||
# empty def
|
||||
self._assert_highlighting('def', {'KEYWORD': [('1.0', '1.3')]})
|
||||
|
||||
# def followed by identifier
|
||||
self._assert_highlighting('def foo:', {'KEYWORD': [('1.0', '1.3')],
|
||||
'DEFINITION': [('1.4', '1.7')]})
|
||||
|
||||
# def followed by partial identifier
|
||||
self._assert_highlighting('def fo', {'KEYWORD': [('1.0', '1.3')],
|
||||
'DEFINITION': [('1.4', '1.6')]})
|
||||
|
||||
# def followed by non-keyword
|
||||
self._assert_highlighting('def ++', {'KEYWORD': [('1.0', '1.3')]})
|
||||
|
||||
def test_match_soft_keyword(self):
|
||||
# empty match
|
||||
self._assert_highlighting('match', {'KEYWORD': [('1.0', '1.5')]})
|
||||
|
||||
# match followed by partial identifier
|
||||
self._assert_highlighting('match fo', {'KEYWORD': [('1.0', '1.5')]})
|
||||
|
||||
# match followed by identifier and colon
|
||||
self._assert_highlighting('match foo:', {'KEYWORD': [('1.0', '1.5')]})
|
||||
|
||||
# match followed by keyword
|
||||
self._assert_highlighting('match and', {'KEYWORD': [('1.6', '1.9')]})
|
||||
|
||||
# match followed by builtin with keyword prefix
|
||||
self._assert_highlighting('match int:', {'KEYWORD': [('1.0', '1.5')],
|
||||
'BUILTIN': [('1.6', '1.9')]})
|
||||
|
||||
# match followed by non-text operator
|
||||
self._assert_highlighting('match^', {})
|
||||
self._assert_highlighting('match @', {})
|
||||
|
||||
# match followed by colon
|
||||
self._assert_highlighting('match :', {})
|
||||
|
||||
# match followed by comma
|
||||
self._assert_highlighting('match\t,', {})
|
||||
|
||||
# match followed by a lone underscore
|
||||
self._assert_highlighting('match _:', {'KEYWORD': [('1.0', '1.5')]})
|
||||
|
||||
def test_case_soft_keyword(self):
|
||||
# empty case
|
||||
self._assert_highlighting('case', {'KEYWORD': [('1.0', '1.4')]})
|
||||
|
||||
# case followed by partial identifier
|
||||
self._assert_highlighting('case fo', {'KEYWORD': [('1.0', '1.4')]})
|
||||
|
||||
# case followed by identifier and colon
|
||||
self._assert_highlighting('case foo:', {'KEYWORD': [('1.0', '1.4')]})
|
||||
|
||||
# case followed by keyword
|
||||
self._assert_highlighting('case and', {'KEYWORD': [('1.5', '1.8')]})
|
||||
|
||||
# case followed by builtin with keyword prefix
|
||||
self._assert_highlighting('case int:', {'KEYWORD': [('1.0', '1.4')],
|
||||
'BUILTIN': [('1.5', '1.8')]})
|
||||
|
||||
# case followed by non-text operator
|
||||
self._assert_highlighting('case^', {})
|
||||
self._assert_highlighting('case @', {})
|
||||
|
||||
# case followed by colon
|
||||
self._assert_highlighting('case :', {})
|
||||
|
||||
# case followed by comma
|
||||
self._assert_highlighting('case\t,', {})
|
||||
|
||||
# case followed by a lone underscore
|
||||
self._assert_highlighting('case _:', {'KEYWORD': [('1.0', '1.4'),
|
||||
('1.5', '1.6')]})
|
||||
|
||||
def test_long_multiline_string(self):
|
||||
source = textwrap.dedent('''\
|
||||
"""a
|
||||
b
|
||||
c
|
||||
d
|
||||
e"""
|
||||
''')
|
||||
self._assert_highlighting(source, {'STRING': [('1.0', '5.4')]})
|
||||
|
||||
@run_in_tk_mainloop(delay=50)
|
||||
def test_incremental_editing(self):
|
||||
text = self.text
|
||||
eq = self.assertEqual
|
||||
|
||||
# Simulate typing 'inte'. During this, the highlighting should
|
||||
# change from normal to keyword to builtin to normal.
|
||||
text.insert('insert', 'i')
|
||||
yield
|
||||
eq(text.tag_nextrange('BUILTIN', '1.0'), ())
|
||||
eq(text.tag_nextrange('KEYWORD', '1.0'), ())
|
||||
|
||||
text.insert('insert', 'n')
|
||||
yield
|
||||
eq(text.tag_nextrange('BUILTIN', '1.0'), ())
|
||||
eq(text.tag_nextrange('KEYWORD', '1.0'), ('1.0', '1.2'))
|
||||
|
||||
text.insert('insert', 't')
|
||||
yield
|
||||
eq(text.tag_nextrange('BUILTIN', '1.0'), ('1.0', '1.3'))
|
||||
eq(text.tag_nextrange('KEYWORD', '1.0'), ())
|
||||
|
||||
text.insert('insert', 'e')
|
||||
yield
|
||||
eq(text.tag_nextrange('BUILTIN', '1.0'), ())
|
||||
eq(text.tag_nextrange('KEYWORD', '1.0'), ())
|
||||
|
||||
# Simulate deleting three characters from the end of 'inte'.
|
||||
# During this, the highlighting should change from normal to
|
||||
# builtin to keyword to normal.
|
||||
text.delete('insert-1c', 'insert')
|
||||
yield
|
||||
eq(text.tag_nextrange('BUILTIN', '1.0'), ('1.0', '1.3'))
|
||||
eq(text.tag_nextrange('KEYWORD', '1.0'), ())
|
||||
|
||||
text.delete('insert-1c', 'insert')
|
||||
yield
|
||||
eq(text.tag_nextrange('BUILTIN', '1.0'), ())
|
||||
eq(text.tag_nextrange('KEYWORD', '1.0'), ('1.0', '1.2'))
|
||||
|
||||
text.delete('insert-1c', 'insert')
|
||||
yield
|
||||
eq(text.tag_nextrange('BUILTIN', '1.0'), ())
|
||||
eq(text.tag_nextrange('KEYWORD', '1.0'), ())
|
||||
|
||||
@mock.patch.object(colorizer.ColorDelegator, 'recolorize')
|
||||
@mock.patch.object(colorizer.ColorDelegator, 'notify_range')
|
||||
def test_removecolors(self, mock_notify, mock_recolorize):
|
||||
text = self.text
|
||||
color = self.color
|
||||
text.insert('insert', source)
|
||||
|
||||
color.recolorize_main()
|
||||
# recolorize_main doesn't add these tags.
|
||||
text.tag_add("ERROR", "1.0")
|
||||
text.tag_add("TODO", "1.0")
|
||||
text.tag_add("hit", "1.0")
|
||||
for tag in color.tagdefs:
|
||||
with self.subTest(tag=tag):
|
||||
self.assertNotEqual(text.tag_ranges(tag), ())
|
||||
|
||||
color.removecolors()
|
||||
for tag in color.tagdefs:
|
||||
with self.subTest(tag=tag):
|
||||
self.assertEqual(text.tag_ranges(tag), ())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(verbosity=2)
|
||||
805
Dependencies/Python/Lib/idlelib/idle_test/test_config.py
vendored
Normal file
@@ -0,0 +1,805 @@
|
||||
"""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/configdata/cfgparser.1 from test_configparser.
|
||||
config_path = findfile('cfgparser.1', subdir='configdata')
|
||||
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_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) 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, conf.config_types)
|
||||
self.assertCountEqual(conf.userCfg, 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 cfg_type, parser in conf.defaultCfg.items():
|
||||
self.assertEqual(parser.file,
|
||||
os.path.join(idle_dir, f'config-{cfg_type}.def'))
|
||||
for cfg_type, parser in conf.userCfg.items():
|
||||
self.assertEqual(parser.file,
|
||||
os.path.join(conf.userdir or '#', f'config-{cfg_type}.cfg'))
|
||||
|
||||
def test_load_cfg_files(self):
|
||||
conf = self.new_config(_utest=True)
|
||||
|
||||
# Borrow test/configdata/cfgparser.1 from test_configparser.
|
||||
config_path = findfile('cfgparser.1', subdir='configdata')
|
||||
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'})
|
||||
|
||||
# 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):
|
||||
# TODO: finish.
|
||||
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()
|
||||
|
||||
# Conflict 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)
|
||||
356
Dependencies/Python/Lib/idlelib/idle_test/test_config_key.py
vendored
Normal file
@@ -0,0 +1,356 @@
|
||||
"""Test config_key, coverage 98%.
|
||||
|
||||
Coverage is effectively 100%. Tkinter dialog is mocked, Mac-only line
|
||||
may be skipped, and dummy function in bind test should not be called.
|
||||
Not tested: exit with 'self.advanced or self.keys_ok(keys) ...' False.
|
||||
"""
|
||||
|
||||
from idlelib import config_key
|
||||
from test.support import requires
|
||||
import unittest
|
||||
from unittest import mock
|
||||
from tkinter import Tk, TclError
|
||||
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, keys_ok, bind_ok."
|
||||
|
||||
class Validator(config_key.GetKeysFrame):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
class list_keys_final:
|
||||
get = Func()
|
||||
self.list_keys_final = list_keys_final
|
||||
get_modifiers = 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, '<<Test>>', keylist)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
del cls.dialog
|
||||
cls.root.update_idletasks()
|
||||
cls.root.destroy()
|
||||
del 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.key_string.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.key_string.set('<Key-F11>')
|
||||
self.dialog.list_keys_final.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.keys_ok('<Control-Shift'))
|
||||
self.assertIn('Missing the final', self.dialog.showerror.message)
|
||||
|
||||
def test_keys_no_modifier_bad(self):
|
||||
self.dialog.list_keys_final.get.result = 'A'
|
||||
self.assertFalse(self.dialog.keys_ok('<Key-A>'))
|
||||
self.assertIn('No modifier', self.dialog.showerror.message)
|
||||
|
||||
def test_keys_no_modifier_ok(self):
|
||||
self.dialog.list_keys_final.get.result = 'F11'
|
||||
self.assertTrue(self.dialog.keys_ok('<Key-F11>'))
|
||||
self.assertEqual(self.dialog.showerror.message, '')
|
||||
|
||||
def test_keys_shift_bad(self):
|
||||
self.dialog.list_keys_final.get.result = 'a'
|
||||
self.dialog.get_modifiers.result = ['Shift']
|
||||
self.assertFalse(self.dialog.keys_ok('<a>'))
|
||||
self.assertIn('shift modifier', self.dialog.showerror.message)
|
||||
self.dialog.get_modifiers.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.list_keys_final.get.result = final
|
||||
self.dialog.get_modifiers.result = mods
|
||||
self.assertFalse(self.dialog.keys_ok(seq))
|
||||
self.assertIn('already in use', self.dialog.showerror.message)
|
||||
self.dialog.get_modifiers.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)
|
||||
|
||||
|
||||
class ToggleLevelTest(unittest.TestCase):
|
||||
"Test toggle between Basic and Advanced frames."
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
requires('gui')
|
||||
cls.root = Tk()
|
||||
cls.root.withdraw()
|
||||
cls.dialog = config_key.GetKeysFrame(cls.root, '<<Test>>', [])
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
del cls.dialog
|
||||
cls.root.update_idletasks()
|
||||
cls.root.destroy()
|
||||
del cls.root
|
||||
|
||||
def test_toggle_level(self):
|
||||
dialog = self.dialog
|
||||
|
||||
def stackorder():
|
||||
"""Get the stack order of the children of the frame.
|
||||
|
||||
winfo_children() stores the children in stack order, so
|
||||
this can be used to check whether a frame is above or
|
||||
below another one.
|
||||
"""
|
||||
for index, child in enumerate(dialog.winfo_children()):
|
||||
if child._name == 'keyseq_basic':
|
||||
basic = index
|
||||
if child._name == 'keyseq_advanced':
|
||||
advanced = index
|
||||
return basic, advanced
|
||||
|
||||
# New window starts at basic level.
|
||||
self.assertFalse(dialog.advanced)
|
||||
self.assertIn('Advanced', dialog.button_level['text'])
|
||||
basic, advanced = stackorder()
|
||||
self.assertGreater(basic, advanced)
|
||||
|
||||
# Toggle to advanced.
|
||||
dialog.toggle_level()
|
||||
self.assertTrue(dialog.advanced)
|
||||
self.assertIn('Basic', dialog.button_level['text'])
|
||||
basic, advanced = stackorder()
|
||||
self.assertGreater(advanced, basic)
|
||||
|
||||
# Toggle to basic.
|
||||
dialog.button_level.invoke()
|
||||
self.assertFalse(dialog.advanced)
|
||||
self.assertIn('Advanced', dialog.button_level['text'])
|
||||
basic, advanced = stackorder()
|
||||
self.assertGreater(basic, advanced)
|
||||
|
||||
|
||||
class KeySelectionTest(unittest.TestCase):
|
||||
"Test selecting key on Basic frames."
|
||||
|
||||
class Basic(config_key.GetKeysFrame):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
class list_keys_final:
|
||||
get = Func()
|
||||
select_clear = Func()
|
||||
yview = Func()
|
||||
self.list_keys_final = list_keys_final
|
||||
def set_modifiers_for_platform(self):
|
||||
self.modifiers = ['foo', 'bar', 'BAZ']
|
||||
self.modifier_label = {'BAZ': 'ZZZ'}
|
||||
showerror = Mbox_func()
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
requires('gui')
|
||||
cls.root = Tk()
|
||||
cls.root.withdraw()
|
||||
cls.dialog = cls.Basic(cls.root, '<<Test>>', [])
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
del cls.dialog
|
||||
cls.root.update_idletasks()
|
||||
cls.root.destroy()
|
||||
del cls.root
|
||||
|
||||
def setUp(self):
|
||||
self.dialog.clear_key_seq()
|
||||
|
||||
def test_get_modifiers(self):
|
||||
dialog = self.dialog
|
||||
gm = dialog.get_modifiers
|
||||
eq = self.assertEqual
|
||||
|
||||
# Modifiers are set on/off by invoking the checkbutton.
|
||||
dialog.modifier_checkbuttons['foo'].invoke()
|
||||
eq(gm(), ['foo'])
|
||||
|
||||
dialog.modifier_checkbuttons['BAZ'].invoke()
|
||||
eq(gm(), ['foo', 'BAZ'])
|
||||
|
||||
dialog.modifier_checkbuttons['foo'].invoke()
|
||||
eq(gm(), ['BAZ'])
|
||||
|
||||
@mock.patch.object(config_key.GetKeysFrame, 'get_modifiers')
|
||||
def test_build_key_string(self, mock_modifiers):
|
||||
dialog = self.dialog
|
||||
key = dialog.list_keys_final
|
||||
string = dialog.key_string.get
|
||||
eq = self.assertEqual
|
||||
|
||||
key.get.result = 'a'
|
||||
mock_modifiers.return_value = []
|
||||
dialog.build_key_string()
|
||||
eq(string(), '<Key-a>')
|
||||
|
||||
mock_modifiers.return_value = ['mymod']
|
||||
dialog.build_key_string()
|
||||
eq(string(), '<mymod-Key-a>')
|
||||
|
||||
key.get.result = ''
|
||||
mock_modifiers.return_value = ['mymod', 'test']
|
||||
dialog.build_key_string()
|
||||
eq(string(), '<mymod-test>')
|
||||
|
||||
@mock.patch.object(config_key.GetKeysFrame, 'get_modifiers')
|
||||
def test_final_key_selected(self, mock_modifiers):
|
||||
dialog = self.dialog
|
||||
key = dialog.list_keys_final
|
||||
string = dialog.key_string.get
|
||||
eq = self.assertEqual
|
||||
|
||||
mock_modifiers.return_value = ['Shift']
|
||||
key.get.result = '{'
|
||||
dialog.final_key_selected()
|
||||
eq(string(), '<Shift-Key-braceleft>')
|
||||
|
||||
|
||||
class CancelWindowTest(unittest.TestCase):
|
||||
"Simulate user clicking [Cancel] button."
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
requires('gui')
|
||||
cls.root = Tk()
|
||||
cls.root.withdraw()
|
||||
cls.dialog = config_key.GetKeysWindow(
|
||||
cls.root, 'Title', '<<Test>>', [], _utest=True)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.dialog.cancel()
|
||||
del cls.dialog
|
||||
cls.root.update_idletasks()
|
||||
cls.root.destroy()
|
||||
del cls.root
|
||||
|
||||
@mock.patch.object(config_key.GetKeysFrame, 'ok')
|
||||
def test_cancel(self, mock_frame_ok):
|
||||
self.assertEqual(self.dialog.winfo_class(), 'Toplevel')
|
||||
self.dialog.button_cancel.invoke()
|
||||
with self.assertRaises(TclError):
|
||||
self.dialog.winfo_class()
|
||||
self.assertEqual(self.dialog.result, '')
|
||||
mock_frame_ok.assert_not_called()
|
||||
|
||||
|
||||
class OKWindowTest(unittest.TestCase):
|
||||
"Simulate user clicking [OK] button."
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
requires('gui')
|
||||
cls.root = Tk()
|
||||
cls.root.withdraw()
|
||||
cls.dialog = config_key.GetKeysWindow(
|
||||
cls.root, 'Title', '<<Test>>', [], _utest=True)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.dialog.cancel()
|
||||
del cls.dialog
|
||||
cls.root.update_idletasks()
|
||||
cls.root.destroy()
|
||||
del cls.root
|
||||
|
||||
@mock.patch.object(config_key.GetKeysFrame, 'ok')
|
||||
def test_ok(self, mock_frame_ok):
|
||||
self.assertEqual(self.dialog.winfo_class(), 'Toplevel')
|
||||
self.dialog.button_ok.invoke()
|
||||
with self.assertRaises(TclError):
|
||||
self.dialog.winfo_class()
|
||||
mock_frame_ok.assert_called()
|
||||
|
||||
|
||||
class WindowResultTest(unittest.TestCase):
|
||||
"Test window result get and set."
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
requires('gui')
|
||||
cls.root = Tk()
|
||||
cls.root.withdraw()
|
||||
cls.dialog = config_key.GetKeysWindow(
|
||||
cls.root, 'Title', '<<Test>>', [], _utest=True)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.dialog.cancel()
|
||||
del cls.dialog
|
||||
cls.root.update_idletasks()
|
||||
cls.root.destroy()
|
||||
del cls.root
|
||||
|
||||
def test_result(self):
|
||||
dialog = self.dialog
|
||||
eq = self.assertEqual
|
||||
|
||||
dialog.result = ''
|
||||
eq(dialog.result, '')
|
||||
eq(dialog.frame.result,'')
|
||||
|
||||
dialog.result = 'bar'
|
||||
eq(dialog.result,'bar')
|
||||
eq(dialog.frame.result,'bar')
|
||||
|
||||
dialog.frame.result = 'foo'
|
||||
eq(dialog.result, 'foo')
|
||||
eq(dialog.frame.result,'foo')
|
||||
|
||||
|
||||
class HelperTest(unittest.TestCase):
|
||||
"Test module level helper functions."
|
||||
|
||||
def test_translate_key(self):
|
||||
tr = config_key.translate_key
|
||||
eq = self.assertEqual
|
||||
|
||||
# Letters return unchanged with no 'Shift'.
|
||||
eq(tr('q', []), 'Key-q')
|
||||
eq(tr('q', ['Control', 'Alt']), 'Key-q')
|
||||
|
||||
# 'Shift' uppercases single lowercase letters.
|
||||
eq(tr('q', ['Shift']), 'Key-Q')
|
||||
eq(tr('q', ['Control', 'Shift']), 'Key-Q')
|
||||
eq(tr('q', ['Control', 'Alt', 'Shift']), 'Key-Q')
|
||||
|
||||
# Convert key name to keysym.
|
||||
eq(tr('Page Up', []), 'Key-Prior')
|
||||
# 'Shift' doesn't change case when it's not a single char.
|
||||
eq(tr('*', ['Shift']), 'Key-asterisk')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(verbosity=2)
|
||||
1581
Dependencies/Python/Lib/idlelib/idle_test/test_configdialog.py
vendored
Normal file
298
Dependencies/Python/Lib/idlelib/idle_test/test_debugger.py
vendored
Normal file
@@ -0,0 +1,298 @@
|
||||
"""Test debugger, coverage 66%
|
||||
|
||||
Try to make tests pass with draft bdbx, which may replace bdb in 3.13+.
|
||||
"""
|
||||
|
||||
from idlelib import debugger
|
||||
from collections import namedtuple
|
||||
from textwrap import dedent
|
||||
from tkinter import Tk
|
||||
|
||||
from test.support import requires
|
||||
from test.support.testcase import ExtraAssertions
|
||||
import unittest
|
||||
from unittest import mock
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
"""A test python script for the debug tests."""
|
||||
TEST_CODE = dedent("""
|
||||
i = 1
|
||||
i += 2
|
||||
if i == 3:
|
||||
print(i)
|
||||
""")
|
||||
|
||||
|
||||
class MockFrame:
|
||||
"Minimal mock frame."
|
||||
|
||||
def __init__(self, code, lineno):
|
||||
self.f_code = code
|
||||
self.f_lineno = lineno
|
||||
|
||||
|
||||
class IdbTest(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.gui = Mock()
|
||||
cls.idb = debugger.Idb(cls.gui)
|
||||
|
||||
# Create test and code objects to simulate a debug session.
|
||||
code_obj = compile(TEST_CODE, 'idlelib/file.py', mode='exec')
|
||||
frame1 = MockFrame(code_obj, 1)
|
||||
frame1.f_back = None
|
||||
frame2 = MockFrame(code_obj, 2)
|
||||
frame2.f_back = frame1
|
||||
cls.frame = frame2
|
||||
cls.msg = 'file.py:2: <module>()'
|
||||
|
||||
def test_init(self):
|
||||
self.assertIs(self.idb.gui, self.gui)
|
||||
# Won't test super call since two Bdbs are very different.
|
||||
|
||||
def test_user_line(self):
|
||||
# Test that .user_line() creates a string message for a frame.
|
||||
self.gui.interaction = Mock()
|
||||
self.idb.user_line(self.frame)
|
||||
self.gui.interaction.assert_called_once_with(self.msg, self.frame)
|
||||
|
||||
def test_user_exception(self):
|
||||
# Test that .user_exception() creates a string message for a frame.
|
||||
exc_info = (type(ValueError), ValueError(), None)
|
||||
self.gui.interaction = Mock()
|
||||
self.idb.user_exception(self.frame, exc_info)
|
||||
self.gui.interaction.assert_called_once_with(
|
||||
self.msg, self.frame, exc_info)
|
||||
|
||||
|
||||
class FunctionTest(unittest.TestCase):
|
||||
# Test module functions together.
|
||||
|
||||
def test_functions(self):
|
||||
rpc_obj = compile(TEST_CODE,'rpc.py', mode='exec')
|
||||
rpc_frame = MockFrame(rpc_obj, 2)
|
||||
rpc_frame.f_back = rpc_frame
|
||||
self.assertTrue(debugger._in_rpc_code(rpc_frame))
|
||||
self.assertEqual(debugger._frame2message(rpc_frame),
|
||||
'rpc.py:2: <module>()')
|
||||
|
||||
code_obj = compile(TEST_CODE, 'idlelib/debugger.py', mode='exec')
|
||||
code_frame = MockFrame(code_obj, 1)
|
||||
code_frame.f_back = None
|
||||
self.assertFalse(debugger._in_rpc_code(code_frame))
|
||||
self.assertEqual(debugger._frame2message(code_frame),
|
||||
'debugger.py:1: <module>()')
|
||||
|
||||
code_frame.f_back = code_frame
|
||||
self.assertFalse(debugger._in_rpc_code(code_frame))
|
||||
code_frame.f_back = rpc_frame
|
||||
self.assertTrue(debugger._in_rpc_code(code_frame))
|
||||
|
||||
|
||||
class DebuggerTest(unittest.TestCase):
|
||||
"Tests for Debugger that do not need a real root."
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.pyshell = Mock()
|
||||
cls.pyshell.root = Mock()
|
||||
cls.idb = Mock()
|
||||
with patch.object(debugger.Debugger, 'make_gui'):
|
||||
cls.debugger = debugger.Debugger(cls.pyshell, cls.idb)
|
||||
cls.debugger.root = Mock()
|
||||
|
||||
def test_cont(self):
|
||||
self.debugger.cont()
|
||||
self.idb.set_continue.assert_called_once()
|
||||
|
||||
def test_step(self):
|
||||
self.debugger.step()
|
||||
self.idb.set_step.assert_called_once()
|
||||
|
||||
def test_quit(self):
|
||||
self.debugger.quit()
|
||||
self.idb.set_quit.assert_called_once()
|
||||
|
||||
def test_next(self):
|
||||
with patch.object(self.debugger, 'frame') as frame:
|
||||
self.debugger.next()
|
||||
self.idb.set_next.assert_called_once_with(frame)
|
||||
|
||||
def test_ret(self):
|
||||
with patch.object(self.debugger, 'frame') as frame:
|
||||
self.debugger.ret()
|
||||
self.idb.set_return.assert_called_once_with(frame)
|
||||
|
||||
def test_clear_breakpoint(self):
|
||||
self.debugger.clear_breakpoint('test.py', 4)
|
||||
self.idb.clear_break.assert_called_once_with('test.py', 4)
|
||||
|
||||
def test_clear_file_breaks(self):
|
||||
self.debugger.clear_file_breaks('test.py')
|
||||
self.idb.clear_all_file_breaks.assert_called_once_with('test.py')
|
||||
|
||||
def test_set_load_breakpoints(self):
|
||||
# Test the .load_breakpoints() method calls idb.
|
||||
FileIO = namedtuple('FileIO', 'filename')
|
||||
|
||||
class MockEditWindow(object):
|
||||
def __init__(self, fn, breakpoints):
|
||||
self.io = FileIO(fn)
|
||||
self.breakpoints = breakpoints
|
||||
|
||||
self.pyshell.flist = Mock()
|
||||
self.pyshell.flist.inversedict = (
|
||||
MockEditWindow('test1.py', [4, 4]),
|
||||
MockEditWindow('test2.py', [13, 44, 45]),
|
||||
)
|
||||
self.debugger.set_breakpoint('test0.py', 1)
|
||||
self.idb.set_break.assert_called_once_with('test0.py', 1)
|
||||
self.debugger.load_breakpoints() # Call set_breakpoint 5 times.
|
||||
self.idb.set_break.assert_has_calls(
|
||||
[mock.call('test0.py', 1),
|
||||
mock.call('test1.py', 4),
|
||||
mock.call('test1.py', 4),
|
||||
mock.call('test2.py', 13),
|
||||
mock.call('test2.py', 44),
|
||||
mock.call('test2.py', 45)])
|
||||
|
||||
def test_sync_source_line(self):
|
||||
# Test that .sync_source_line() will set the flist.gotofileline with fixed frame.
|
||||
test_code = compile(TEST_CODE, 'test_sync.py', 'exec')
|
||||
test_frame = MockFrame(test_code, 1)
|
||||
self.debugger.frame = test_frame
|
||||
|
||||
self.debugger.flist = Mock()
|
||||
with patch('idlelib.debugger.os.path.exists', return_value=True):
|
||||
self.debugger.sync_source_line()
|
||||
self.debugger.flist.gotofileline.assert_called_once_with('test_sync.py', 1)
|
||||
|
||||
|
||||
class DebuggerGuiTest(unittest.TestCase):
|
||||
"""Tests for debugger.Debugger that need tk root.
|
||||
|
||||
close needs debugger.top set in make_gui.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
requires('gui')
|
||||
cls.root = root = Tk()
|
||||
root.withdraw()
|
||||
cls.pyshell = Mock()
|
||||
cls.pyshell.root = root
|
||||
cls.idb = Mock()
|
||||
# stack tests fail with debugger here.
|
||||
## cls.debugger = debugger.Debugger(cls.pyshell, cls.idb)
|
||||
## cls.debugger.root = root
|
||||
## # real root needed for real make_gui
|
||||
## # run, interacting, abort_loop
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.root.destroy()
|
||||
del cls.root
|
||||
|
||||
def setUp(self):
|
||||
self.debugger = debugger.Debugger(self.pyshell, self.idb)
|
||||
self.debugger.root = self.root
|
||||
# real root needed for real make_gui
|
||||
# run, interacting, abort_loop
|
||||
|
||||
def test_run_debugger(self):
|
||||
self.debugger.run(1, 'two')
|
||||
self.idb.run.assert_called_once_with(1, 'two')
|
||||
self.assertEqual(self.debugger.interacting, 0)
|
||||
|
||||
def test_close(self):
|
||||
# Test closing the window in an idle state.
|
||||
self.debugger.close()
|
||||
self.pyshell.close_debugger.assert_called_once()
|
||||
|
||||
def test_show_stack(self):
|
||||
self.debugger.show_stack()
|
||||
self.assertEqual(self.debugger.stackviewer.gui, self.debugger)
|
||||
|
||||
def test_show_stack_with_frame(self):
|
||||
test_frame = MockFrame(None, None)
|
||||
self.debugger.frame = test_frame
|
||||
|
||||
# Reset the stackviewer to force it to be recreated.
|
||||
self.debugger.stackviewer = None
|
||||
self.idb.get_stack.return_value = ([], 0)
|
||||
self.debugger.show_stack()
|
||||
|
||||
# Check that the newly created stackviewer has the test gui as a field.
|
||||
self.assertEqual(self.debugger.stackviewer.gui, self.debugger)
|
||||
self.idb.get_stack.assert_called_once_with(test_frame, None)
|
||||
|
||||
|
||||
class StackViewerTest(unittest.TestCase, ExtraAssertions):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
requires('gui')
|
||||
cls.root = Tk()
|
||||
cls.root.withdraw()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.root.destroy()
|
||||
del cls.root
|
||||
|
||||
def setUp(self):
|
||||
self.code = compile(TEST_CODE, 'test_stackviewer.py', 'exec')
|
||||
self.stack = [
|
||||
(MockFrame(self.code, 1), 1),
|
||||
(MockFrame(self.code, 2), 2)
|
||||
]
|
||||
# Create a stackviewer and load the test stack.
|
||||
self.sv = debugger.StackViewer(self.root, None, None)
|
||||
self.sv.load_stack(self.stack)
|
||||
|
||||
def test_init(self):
|
||||
# Test creation of StackViewer.
|
||||
gui = None
|
||||
flist = None
|
||||
master_window = self.root
|
||||
sv = debugger.StackViewer(master_window, flist, gui)
|
||||
self.assertHasAttr(sv, 'stack')
|
||||
|
||||
def test_load_stack(self):
|
||||
# Test the .load_stack() method against a fixed test stack.
|
||||
# Check the test stack is assigned and the list contains the repr of them.
|
||||
self.assertEqual(self.sv.stack, self.stack)
|
||||
self.assertTrue('?.<module>(), line 1:' in self.sv.get(0))
|
||||
self.assertEqual(self.sv.get(1), '?.<module>(), line 2: ')
|
||||
|
||||
def test_show_source(self):
|
||||
# Test the .show_source() method against a fixed test stack.
|
||||
# Patch out the file list to monitor it
|
||||
self.sv.flist = Mock()
|
||||
# Patch out isfile to pretend file exists.
|
||||
with patch('idlelib.debugger.os.path.isfile', return_value=True) as isfile:
|
||||
self.sv.show_source(1)
|
||||
isfile.assert_called_once_with('test_stackviewer.py')
|
||||
self.sv.flist.open.assert_called_once_with('test_stackviewer.py')
|
||||
|
||||
|
||||
class NameSpaceTest(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
requires('gui')
|
||||
cls.root = Tk()
|
||||
cls.root.withdraw()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.root.destroy()
|
||||
del cls.root
|
||||
|
||||
def test_init(self):
|
||||
debugger.NamespaceViewer(self.root, 'Test')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(verbosity=2)
|
||||
36
Dependencies/Python/Lib/idlelib/idle_test/test_debugger_r.py
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
"Test debugger_r, coverage 30%."
|
||||
|
||||
from idlelib import debugger_r
|
||||
import unittest
|
||||
|
||||
# Boilerplate likely to be needed for future test classes.
|
||||
##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()
|
||||
|
||||
# GUIProxy, IdbAdapter, FrameProxy, CodeProxy, DictProxy,
|
||||
# GUIAdapter, IdbProxy, and 7 functions still need tests.
|
||||
|
||||
class IdbAdapterTest(unittest.TestCase):
|
||||
|
||||
def test_dict_item_noattr(self): # Issue 33065.
|
||||
|
||||
class BinData:
|
||||
def __repr__(self):
|
||||
return self.length
|
||||
|
||||
debugger_r.dicttable[0] = {'BinData': BinData()}
|
||||
idb = debugger_r.IdbAdapter(None)
|
||||
self.assertTrue(idb.dict_item(0, 'BinData'))
|
||||
debugger_r.dicttable.clear()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(verbosity=2)
|
||||
57
Dependencies/Python/Lib/idlelib/idle_test/test_debugobj.py
vendored
Normal 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]) # keys() is a range.
|
||||
|
||||
|
||||
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]) # keys() is a sorted list.
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(verbosity=2)
|
||||
22
Dependencies/Python/Lib/idlelib/idle_test/test_debugobj_r.py
vendored
Normal 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)
|
||||
44
Dependencies/Python/Lib/idlelib/idle_test/test_delegator.py
vendored
Normal 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)
|
||||
74
Dependencies/Python/Lib/idlelib/idle_test/test_editmenu.py
vendored
Normal 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)
|
||||
241
Dependencies/Python/Lib/idlelib/idle_test/test_editor.py
vendored
Normal file
@@ -0,0 +1,241 @@
|
||||
"Test editor, coverage 53%."
|
||||
|
||||
from idlelib import editor
|
||||
import unittest
|
||||
from collections import namedtuple
|
||||
from test.support import requires
|
||||
from tkinter import Tk, Text
|
||||
|
||||
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 GetLineIndentTest(unittest.TestCase):
|
||||
def test_empty_lines(self):
|
||||
for tabwidth in [1, 2, 4, 6, 8]:
|
||||
for line in ['', '\n']:
|
||||
with self.subTest(line=line, tabwidth=tabwidth):
|
||||
self.assertEqual(
|
||||
editor.get_line_indent(line, tabwidth=tabwidth),
|
||||
(0, 0),
|
||||
)
|
||||
|
||||
def test_tabwidth_4(self):
|
||||
# (line, (raw, effective))
|
||||
tests = (('no spaces', (0, 0)),
|
||||
# Internal space isn't counted.
|
||||
(' space test', (4, 4)),
|
||||
('\ttab test', (1, 4)),
|
||||
('\t\tdouble tabs test', (2, 8)),
|
||||
# Different results when mixing tabs and spaces.
|
||||
(' \tmixed test', (5, 8)),
|
||||
(' \t mixed test', (5, 6)),
|
||||
('\t mixed test', (5, 8)),
|
||||
# Spaces not divisible by tabwidth.
|
||||
(' \tmixed test', (3, 4)),
|
||||
(' \t mixed test', (3, 5)),
|
||||
('\t mixed test', (3, 6)),
|
||||
# Only checks spaces and tabs.
|
||||
('\nnewline test', (0, 0)))
|
||||
|
||||
for line, expected in tests:
|
||||
with self.subTest(line=line):
|
||||
self.assertEqual(
|
||||
editor.get_line_indent(line, tabwidth=4),
|
||||
expected,
|
||||
)
|
||||
|
||||
def test_tabwidth_8(self):
|
||||
# (line, (raw, effective))
|
||||
tests = (('no spaces', (0, 0)),
|
||||
# Internal space isn't counted.
|
||||
(' space test', (8, 8)),
|
||||
('\ttab test', (1, 8)),
|
||||
('\t\tdouble tabs test', (2, 16)),
|
||||
# Different results when mixing tabs and spaces.
|
||||
(' \tmixed test', (9, 16)),
|
||||
(' \t mixed test', (9, 10)),
|
||||
('\t mixed test', (9, 16)),
|
||||
# Spaces not divisible by tabwidth.
|
||||
(' \tmixed test', (3, 8)),
|
||||
(' \t mixed test', (3, 9)),
|
||||
('\t mixed test', (3, 10)),
|
||||
# Only checks spaces and tabs.
|
||||
('\nnewline test', (0, 0)))
|
||||
|
||||
for line, expected in tests:
|
||||
with self.subTest(line=line):
|
||||
self.assertEqual(
|
||||
editor.get_line_indent(line, tabwidth=8),
|
||||
expected,
|
||||
)
|
||||
|
||||
|
||||
def insert(text, string):
|
||||
text.delete('1.0', 'end')
|
||||
text.insert('end', string)
|
||||
text.update_idletasks() # Force update for colorizer to finish.
|
||||
|
||||
|
||||
class IndentAndNewlineTest(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
requires('gui')
|
||||
cls.root = Tk()
|
||||
cls.root.withdraw()
|
||||
cls.window = Editor(root=cls.root)
|
||||
cls.window.indentwidth = 2
|
||||
cls.window.tabwidth = 2
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.window._close()
|
||||
del cls.window
|
||||
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_indent_and_newline_event(self):
|
||||
eq = self.assertEqual
|
||||
w = self.window
|
||||
text = w.text
|
||||
get = text.get
|
||||
nl = w.newline_and_indent_event
|
||||
|
||||
TestInfo = namedtuple('Tests', ['label', 'text', 'expected', 'mark'])
|
||||
|
||||
tests = (TestInfo('Empty line inserts with no indent.',
|
||||
' \n def __init__(self):',
|
||||
'\n \n def __init__(self):\n',
|
||||
'1.end'),
|
||||
TestInfo('Inside bracket before space, deletes space.',
|
||||
' def f1(self, a, b):',
|
||||
' def f1(self,\n a, b):\n',
|
||||
'1.14'),
|
||||
TestInfo('Inside bracket after space, deletes space.',
|
||||
' def f1(self, a, b):',
|
||||
' def f1(self,\n a, b):\n',
|
||||
'1.15'),
|
||||
TestInfo('Inside string with one line - no indent.',
|
||||
' """Docstring."""',
|
||||
' """Docstring.\n"""\n',
|
||||
'1.15'),
|
||||
TestInfo('Inside string with more than one line.',
|
||||
' """Docstring.\n Docstring Line 2"""',
|
||||
' """Docstring.\n Docstring Line 2\n """\n',
|
||||
'2.18'),
|
||||
TestInfo('Backslash with one line.',
|
||||
'a =\\',
|
||||
'a =\\\n \n',
|
||||
'1.end'),
|
||||
TestInfo('Backslash with more than one line.',
|
||||
'a =\\\n multiline\\',
|
||||
'a =\\\n multiline\\\n \n',
|
||||
'2.end'),
|
||||
TestInfo('Block opener - indents +1 level.',
|
||||
' def f1(self):\n pass',
|
||||
' def f1(self):\n \n pass\n',
|
||||
'1.end'),
|
||||
TestInfo('Block closer - dedents -1 level.',
|
||||
' def f1(self):\n pass',
|
||||
' def f1(self):\n pass\n \n',
|
||||
'2.end'),
|
||||
)
|
||||
|
||||
for test in tests:
|
||||
with self.subTest(label=test.label):
|
||||
insert(text, test.text)
|
||||
text.mark_set('insert', test.mark)
|
||||
nl(event=None)
|
||||
eq(get('1.0', 'end'), test.expected)
|
||||
|
||||
# Selected text.
|
||||
insert(text, ' def f1(self, a, b):\n return a + b')
|
||||
text.tag_add('sel', '1.17', '1.end')
|
||||
nl(None)
|
||||
# Deletes selected text before adding new line.
|
||||
eq(get('1.0', 'end'), ' def f1(self, a,\n \n return a + b\n')
|
||||
|
||||
|
||||
class IndentSearcherTest(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
requires('gui')
|
||||
cls.root = Tk()
|
||||
cls.root.withdraw()
|
||||
cls.text = Text(cls.root)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.root.destroy()
|
||||
del cls.root
|
||||
|
||||
def test_searcher(self):
|
||||
text = self.text
|
||||
searcher = (self.text)
|
||||
test_info = (# text, (block, indent))
|
||||
("", (None, None)),
|
||||
("[1,", (None, None)), # TokenError
|
||||
("if 1:\n", ('if 1:\n', None)),
|
||||
("if 1:\n 2\n 3\n", ('if 1:\n', ' 2\n')),
|
||||
)
|
||||
for code, expected_pair in test_info:
|
||||
with self.subTest(code=code):
|
||||
insert(text, code)
|
||||
actual_pair = editor.IndentSearcher(text).run()
|
||||
self.assertEqual(actual_pair, expected_pair)
|
||||
|
||||
|
||||
class RMenuTest(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
requires('gui')
|
||||
cls.root = Tk()
|
||||
cls.root.withdraw()
|
||||
cls.window = Editor(root=cls.root)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.window._close()
|
||||
del cls.window
|
||||
cls.root.update_idletasks()
|
||||
for id in cls.root.tk.call('after', 'info'):
|
||||
cls.root.after_cancel(id)
|
||||
cls.root.destroy()
|
||||
del cls.root
|
||||
|
||||
class DummyRMenu:
|
||||
def tk_popup(x, y): pass
|
||||
|
||||
def test_rclick(self):
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(verbosity=2)
|
||||
33
Dependencies/Python/Lib/idlelib/idle_test/test_filelist.py
vendored
Normal 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)
|
||||
668
Dependencies/Python/Lib/idlelib/idle_test/test_format.py
vendored
Normal file
@@ -0,0 +1,668 @@
|
||||
"Test format, coverage 99%."
|
||||
|
||||
from idlelib import format as ft
|
||||
import unittest
|
||||
from unittest import mock
|
||||
from test.support import requires
|
||||
from tkinter import Tk, Text
|
||||
from idlelib.editor import EditorWindow
|
||||
from idlelib.idle_test.mock_idle import Editor as MockEditor
|
||||
|
||||
|
||||
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(ft.is_all_white(''))
|
||||
self.assertTrue(ft.is_all_white('\t\n\r\f\v'))
|
||||
self.assertFalse(ft.is_all_white(self.test_comment))
|
||||
|
||||
def test_get_indent(self):
|
||||
Equal = self.assertEqual
|
||||
Equal(ft.get_indent(self.test_comment), '')
|
||||
Equal(ft.get_indent(self.trailingws_comment), '')
|
||||
Equal(ft.get_indent(self.leadingws_comment), ' ')
|
||||
Equal(ft.get_indent(self.leadingws_nocomment), ' ')
|
||||
|
||||
def test_get_comment_header(self):
|
||||
Equal = self.assertEqual
|
||||
# Test comment strings
|
||||
Equal(ft.get_comment_header(self.test_comment), '#')
|
||||
Equal(ft.get_comment_header(self.trailingws_comment), '#')
|
||||
Equal(ft.get_comment_header(self.leadingws_comment), ' #')
|
||||
# Test non-comment strings
|
||||
Equal(ft.get_comment_header(self.leadingws_nocomment), ' ')
|
||||
Equal(ft.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(ft.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 = ft.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 = ft.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 = ft.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 = ft.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 = ft.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')
|
||||
|
||||
|
||||
class DummyEditwin:
|
||||
def __init__(self, root, text):
|
||||
self.root = root
|
||||
self.text = text
|
||||
self.indentwidth = 4
|
||||
self.tabwidth = 4
|
||||
self.usetabs = False
|
||||
self.context_use_ps1 = True
|
||||
|
||||
_make_blanks = EditorWindow._make_blanks
|
||||
get_selection_indices = EditorWindow.get_selection_indices
|
||||
|
||||
|
||||
class FormatRegionTest(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
requires('gui')
|
||||
cls.root = Tk()
|
||||
cls.root.withdraw()
|
||||
cls.text = Text(cls.root)
|
||||
cls.text.undo_block_start = mock.Mock()
|
||||
cls.text.undo_block_stop = mock.Mock()
|
||||
cls.editor = DummyEditwin(cls.root, cls.text)
|
||||
cls.formatter = ft.FormatRegion(cls.editor)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
del cls.text, cls.formatter, cls.editor
|
||||
cls.root.update_idletasks()
|
||||
cls.root.destroy()
|
||||
del cls.root
|
||||
|
||||
def setUp(self):
|
||||
self.text.insert('1.0', self.code_sample)
|
||||
|
||||
def tearDown(self):
|
||||
self.text.delete('1.0', 'end')
|
||||
|
||||
code_sample = """\
|
||||
# WS line needed for test.
|
||||
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
|
||||
"""
|
||||
|
||||
def test_get_region(self):
|
||||
get = self.formatter.get_region
|
||||
text = self.text
|
||||
eq = self.assertEqual
|
||||
|
||||
# Add selection.
|
||||
text.tag_add('sel', '7.0', '10.0')
|
||||
expected_lines = ['',
|
||||
' def compare(self):',
|
||||
' if a > b:',
|
||||
'']
|
||||
eq(get(), ('7.0', '10.0', '\n'.join(expected_lines), expected_lines))
|
||||
|
||||
# Remove selection.
|
||||
text.tag_remove('sel', '1.0', 'end')
|
||||
eq(get(), ('15.0', '16.0', '\n', ['', '']))
|
||||
|
||||
def test_set_region(self):
|
||||
set_ = self.formatter.set_region
|
||||
text = self.text
|
||||
eq = self.assertEqual
|
||||
|
||||
save_bell = text.bell
|
||||
text.bell = mock.Mock()
|
||||
line6 = self.code_sample.splitlines()[5]
|
||||
line10 = self.code_sample.splitlines()[9]
|
||||
|
||||
text.tag_add('sel', '6.0', '11.0')
|
||||
head, tail, chars, lines = self.formatter.get_region()
|
||||
|
||||
# No changes.
|
||||
set_(head, tail, chars, lines)
|
||||
text.bell.assert_called_once()
|
||||
eq(text.get('6.0', '11.0'), chars)
|
||||
eq(text.get('sel.first', 'sel.last'), chars)
|
||||
text.tag_remove('sel', '1.0', 'end')
|
||||
|
||||
# Alter selected lines by changing lines and adding a newline.
|
||||
newstring = 'added line 1\n\n\n\n'
|
||||
newlines = newstring.split('\n')
|
||||
set_('7.0', '10.0', chars, newlines)
|
||||
# Selection changed.
|
||||
eq(text.get('sel.first', 'sel.last'), newstring)
|
||||
# Additional line added, so last index is changed.
|
||||
eq(text.get('7.0', '11.0'), newstring)
|
||||
# Before and after lines unchanged.
|
||||
eq(text.get('6.0', '7.0-1c'), line6)
|
||||
eq(text.get('11.0', '12.0-1c'), line10)
|
||||
text.tag_remove('sel', '1.0', 'end')
|
||||
|
||||
text.bell = save_bell
|
||||
|
||||
def test_indent_region_event(self):
|
||||
indent = self.formatter.indent_region_event
|
||||
text = self.text
|
||||
eq = self.assertEqual
|
||||
|
||||
text.tag_add('sel', '7.0', '10.0')
|
||||
indent()
|
||||
# Blank lines aren't affected by indent.
|
||||
eq(text.get('7.0', '10.0'), ('\n def compare(self):\n if a > b:\n'))
|
||||
|
||||
def test_dedent_region_event(self):
|
||||
dedent = self.formatter.dedent_region_event
|
||||
text = self.text
|
||||
eq = self.assertEqual
|
||||
|
||||
text.tag_add('sel', '7.0', '10.0')
|
||||
dedent()
|
||||
# Blank lines aren't affected by dedent.
|
||||
eq(text.get('7.0', '10.0'), ('\ndef compare(self):\n if a > b:\n'))
|
||||
|
||||
def test_comment_region_event(self):
|
||||
comment = self.formatter.comment_region_event
|
||||
text = self.text
|
||||
eq = self.assertEqual
|
||||
|
||||
text.tag_add('sel', '7.0', '10.0')
|
||||
comment()
|
||||
eq(text.get('7.0', '10.0'), ('##\n## def compare(self):\n## if a > b:\n'))
|
||||
|
||||
def test_uncomment_region_event(self):
|
||||
comment = self.formatter.comment_region_event
|
||||
uncomment = self.formatter.uncomment_region_event
|
||||
text = self.text
|
||||
eq = self.assertEqual
|
||||
|
||||
text.tag_add('sel', '7.0', '10.0')
|
||||
comment()
|
||||
uncomment()
|
||||
eq(text.get('7.0', '10.0'), ('\n def compare(self):\n if a > b:\n'))
|
||||
|
||||
# Only remove comments at the beginning of a line.
|
||||
text.tag_remove('sel', '1.0', 'end')
|
||||
text.tag_add('sel', '3.0', '4.0')
|
||||
uncomment()
|
||||
eq(text.get('3.0', '3.end'), (' # Class comment.'))
|
||||
|
||||
self.formatter.set_region('3.0', '4.0', '', ['# Class comment.', ''])
|
||||
uncomment()
|
||||
eq(text.get('3.0', '3.end'), (' Class comment.'))
|
||||
|
||||
@mock.patch.object(ft.FormatRegion, "_asktabwidth")
|
||||
def test_tabify_region_event(self, _asktabwidth):
|
||||
tabify = self.formatter.tabify_region_event
|
||||
text = self.text
|
||||
eq = self.assertEqual
|
||||
|
||||
text.tag_add('sel', '7.0', '10.0')
|
||||
# No tabwidth selected.
|
||||
_asktabwidth.return_value = None
|
||||
self.assertIsNone(tabify())
|
||||
|
||||
_asktabwidth.return_value = 3
|
||||
self.assertIsNotNone(tabify())
|
||||
eq(text.get('7.0', '10.0'), ('\n\t def compare(self):\n\t\t if a > b:\n'))
|
||||
|
||||
@mock.patch.object(ft.FormatRegion, "_asktabwidth")
|
||||
def test_untabify_region_event(self, _asktabwidth):
|
||||
untabify = self.formatter.untabify_region_event
|
||||
text = self.text
|
||||
eq = self.assertEqual
|
||||
|
||||
text.tag_add('sel', '7.0', '10.0')
|
||||
# No tabwidth selected.
|
||||
_asktabwidth.return_value = None
|
||||
self.assertIsNone(untabify())
|
||||
|
||||
_asktabwidth.return_value = 2
|
||||
self.formatter.tabify_region_event()
|
||||
_asktabwidth.return_value = 3
|
||||
self.assertIsNotNone(untabify())
|
||||
eq(text.get('7.0', '10.0'), ('\n def compare(self):\n if a > b:\n'))
|
||||
|
||||
@mock.patch.object(ft, "askinteger")
|
||||
def test_ask_tabwidth(self, askinteger):
|
||||
ask = self.formatter._asktabwidth
|
||||
askinteger.return_value = 10
|
||||
self.assertEqual(ask(), 10)
|
||||
|
||||
|
||||
class IndentsTest(unittest.TestCase):
|
||||
|
||||
@mock.patch.object(ft, "askyesno")
|
||||
def test_toggle_tabs(self, askyesno):
|
||||
editor = DummyEditwin(None, None) # usetabs == False.
|
||||
indents = ft.Indents(editor)
|
||||
askyesno.return_value = True
|
||||
|
||||
indents.toggle_tabs_event(None)
|
||||
self.assertEqual(editor.usetabs, True)
|
||||
self.assertEqual(editor.indentwidth, 8)
|
||||
|
||||
indents.toggle_tabs_event(None)
|
||||
self.assertEqual(editor.usetabs, False)
|
||||
self.assertEqual(editor.indentwidth, 8)
|
||||
|
||||
@mock.patch.object(ft, "askinteger")
|
||||
def test_change_indentwidth(self, askinteger):
|
||||
editor = DummyEditwin(None, None) # indentwidth == 4.
|
||||
indents = ft.Indents(editor)
|
||||
|
||||
askinteger.return_value = None
|
||||
indents.change_indentwidth_event(None)
|
||||
self.assertEqual(editor.indentwidth, 4)
|
||||
|
||||
askinteger.return_value = 3
|
||||
indents.change_indentwidth_event(None)
|
||||
self.assertEqual(editor.indentwidth, 3)
|
||||
|
||||
askinteger.return_value = 5
|
||||
editor.usetabs = True
|
||||
indents.change_indentwidth_event(None)
|
||||
self.assertEqual(editor.indentwidth, 3)
|
||||
|
||||
|
||||
class RstripTest(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
requires('gui')
|
||||
cls.root = Tk()
|
||||
cls.root.withdraw()
|
||||
cls.text = Text(cls.root)
|
||||
cls.editor = MockEditor(text=cls.text)
|
||||
cls.do_rstrip = ft.Rstrip(cls.editor).do_rstrip
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
del cls.text, cls.do_rstrip, cls.editor
|
||||
cls.root.update_idletasks()
|
||||
cls.root.destroy()
|
||||
del cls.root
|
||||
|
||||
def tearDown(self):
|
||||
self.text.delete('1.0', 'end-1c')
|
||||
|
||||
def test_rstrip_lines(self):
|
||||
original = (
|
||||
"Line with an ending tab \n"
|
||||
"Line ending in 5 spaces \n"
|
||||
"Linewithnospaces\n"
|
||||
" indented line\n"
|
||||
" indented line with trailing space \n"
|
||||
" \n")
|
||||
stripped = (
|
||||
"Line with an ending tab\n"
|
||||
"Line ending in 5 spaces\n"
|
||||
"Linewithnospaces\n"
|
||||
" indented line\n"
|
||||
" indented line with trailing space\n")
|
||||
|
||||
self.text.insert('1.0', original)
|
||||
self.do_rstrip()
|
||||
self.assertEqual(self.text.get('1.0', 'insert'), stripped)
|
||||
|
||||
def test_rstrip_end(self):
|
||||
text = self.text
|
||||
for code in ('', '\n', '\n\n\n'):
|
||||
with self.subTest(code=code):
|
||||
text.insert('1.0', code)
|
||||
self.do_rstrip()
|
||||
self.assertEqual(text.get('1.0','end-1c'), '')
|
||||
for code in ('a\n', 'a\n\n', 'a\n\n\n'):
|
||||
with self.subTest(code=code):
|
||||
text.delete('1.0', 'end-1c')
|
||||
text.insert('1.0', code)
|
||||
self.do_rstrip()
|
||||
self.assertEqual(text.get('1.0','end-1c'), 'a\n')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(verbosity=2, exit=2)
|
||||
157
Dependencies/Python/Lib/idlelib/idle_test/test_grep.py
vendored
Normal file
@@ -0,0 +1,157 @@
|
||||
""" !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 import grep
|
||||
import unittest
|
||||
from test.support import captured_stdout
|
||||
from test.support.testcase import ExtraAssertions
|
||||
from idlelib.idle_test.mock_tk import Var
|
||||
import os
|
||||
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 = grep.GrepDialog.grep_it
|
||||
# Other stuff needed
|
||||
recvar = Var(False)
|
||||
engine = searchengine
|
||||
def close(self): # gui method
|
||||
pass
|
||||
|
||||
_grep = Dummy_grep()
|
||||
|
||||
|
||||
class FindfilesTest(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.realpath = os.path.realpath(__file__)
|
||||
cls.path = os.path.dirname(cls.realpath)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
del cls.realpath, cls.path
|
||||
|
||||
def test_invaliddir(self):
|
||||
with captured_stdout() as s:
|
||||
filelist = list(grep.findfiles('invaliddir', '*.*', False))
|
||||
self.assertEqual(filelist, [])
|
||||
self.assertIn('invalid', s.getvalue())
|
||||
|
||||
def test_curdir(self):
|
||||
# Test os.curdir.
|
||||
ff = grep.findfiles
|
||||
save_cwd = os.getcwd()
|
||||
os.chdir(self.path)
|
||||
filename = 'test_grep.py'
|
||||
filelist = list(ff(os.curdir, filename, False))
|
||||
self.assertIn(os.path.join(os.curdir, filename), filelist)
|
||||
os.chdir(save_cwd)
|
||||
|
||||
def test_base(self):
|
||||
ff = grep.findfiles
|
||||
readme = os.path.join(self.path, 'README.txt')
|
||||
|
||||
# Check for Python files in path where this file lives.
|
||||
filelist = list(ff(self.path, '*.py', False))
|
||||
# This directory has many Python files.
|
||||
self.assertGreater(len(filelist), 10)
|
||||
self.assertIn(self.realpath, filelist)
|
||||
self.assertNotIn(readme, filelist)
|
||||
|
||||
# Look for .txt files in path where this file lives.
|
||||
filelist = list(ff(self.path, '*.txt', False))
|
||||
self.assertNotEqual(len(filelist), 0)
|
||||
self.assertNotIn(self.realpath, filelist)
|
||||
self.assertIn(readme, filelist)
|
||||
|
||||
# Look for non-matching pattern.
|
||||
filelist = list(ff(self.path, 'grep.*', False))
|
||||
self.assertEqual(len(filelist), 0)
|
||||
self.assertNotIn(self.realpath, filelist)
|
||||
|
||||
def test_recurse(self):
|
||||
ff = grep.findfiles
|
||||
parent = os.path.dirname(self.path)
|
||||
grepfile = os.path.join(parent, 'grep.py')
|
||||
pat = '*.py'
|
||||
|
||||
# Get Python files only in parent directory.
|
||||
filelist = list(ff(parent, pat, False))
|
||||
parent_size = len(filelist)
|
||||
# Lots of Python files in idlelib.
|
||||
self.assertGreater(parent_size, 20)
|
||||
self.assertIn(grepfile, filelist)
|
||||
# Without subdirectories, this file isn't returned.
|
||||
self.assertNotIn(self.realpath, filelist)
|
||||
|
||||
# Include subdirectories.
|
||||
filelist = list(ff(parent, pat, True))
|
||||
# More files found now.
|
||||
self.assertGreater(len(filelist), parent_size)
|
||||
self.assertIn(grepfile, filelist)
|
||||
# This file exists in list now.
|
||||
self.assertIn(self.realpath, filelist)
|
||||
|
||||
# Check another level up the tree.
|
||||
parent = os.path.dirname(parent)
|
||||
filelist = list(ff(parent, '*.py', True))
|
||||
self.assertIn(self.realpath, filelist)
|
||||
|
||||
|
||||
class Grep_itTest(unittest.TestCase, ExtraAssertions):
|
||||
# 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.assertStartsWith(lines[4], '(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)
|
||||
36
Dependencies/Python/Lib/idlelib/idle_test/test_help.py
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
"Test help, coverage 94%."
|
||||
|
||||
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 IdleDocTest(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
"By itself, this tests that file parsed without exception."
|
||||
cls.root = root = Tk()
|
||||
root.withdraw()
|
||||
cls.window = help.show_idlehelp(root)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
del cls.window
|
||||
cls.root.update_idletasks()
|
||||
cls.root.destroy()
|
||||
del cls.root
|
||||
|
||||
def test_1window(self):
|
||||
self.assertIn('IDLE Doc', self.window.wm_title())
|
||||
|
||||
def test_4text(self):
|
||||
text = self.window.frame.text
|
||||
self.assertEqual(text.get('1.0', '1.end'), ' IDLE — Python editor and shell ')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(verbosity=2)
|
||||
182
Dependencies/Python/Lib/idlelib/idle_test/test_help_about.py
vendored
Normal file
@@ -0,0 +1,182 @@
|
||||
"""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.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
|
||||
if len(lines) < 2:
|
||||
self.fail(name + ' full text was not found')
|
||||
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, 'News3.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.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)
|
||||
172
Dependencies/Python/Lib/idlelib/idle_test/test_history.py
vendored
Normal 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)
|
||||
276
Dependencies/Python/Lib/idlelib/idle_test/test_hyperparser.py
vendored
Normal 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.prompt_last_line = '>>>'
|
||||
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.prompt_last_line = '>>>'
|
||||
|
||||
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.prompt_last_line = ''
|
||||
|
||||
# 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('a٢'))
|
||||
|
||||
# invalid unicode identifiers
|
||||
self.assertFalse(is_valid_id('2a'))
|
||||
self.assertFalse(is_valid_id('٢a'))
|
||||
self.assertFalse(is_valid_id('a²'))
|
||||
|
||||
# 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)
|
||||
84
Dependencies/Python/Lib/idlelib/idle_test/test_iomenu.py
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
"Test , coverage 17%."
|
||||
|
||||
from idlelib import iomenu
|
||||
import unittest
|
||||
from test.support import requires
|
||||
from tkinter import Tk
|
||||
from idlelib.editor import EditorWindow
|
||||
from idlelib import util
|
||||
from idlelib.idle_test.mock_idle import Func
|
||||
|
||||
# Fail if either tokenize.open and t.detect_encoding does not exist.
|
||||
# These are used in loadfile and encode.
|
||||
# Also used in pyshell.MI.execfile and runscript.tabnanny.
|
||||
from tokenize import open, detect_encoding
|
||||
# Remove when we have proper tests that use both.
|
||||
|
||||
|
||||
class IOBindingTest(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
requires('gui')
|
||||
cls.root = Tk()
|
||||
cls.root.withdraw()
|
||||
cls.editwin = EditorWindow(root=cls.root)
|
||||
cls.io = iomenu.IOBinding(cls.editwin)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.io.close()
|
||||
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):
|
||||
self.assertIs(self.io.editwin, self.editwin)
|
||||
|
||||
def test_fixnewlines_end(self):
|
||||
eq = self.assertEqual
|
||||
io = self.io
|
||||
fix = io.fixnewlines
|
||||
text = io.editwin.text
|
||||
|
||||
# Make the editor temporarily look like Shell.
|
||||
self.editwin.interp = None
|
||||
shelltext = '>>> if 1'
|
||||
self.editwin.get_prompt_text = Func(result=shelltext)
|
||||
eq(fix(), shelltext) # Get... call and '\n' not added.
|
||||
del self.editwin.interp, self.editwin.get_prompt_text
|
||||
|
||||
text.insert(1.0, 'a')
|
||||
eq(fix(), 'a'+io.eol_convention)
|
||||
eq(text.get('1.0', 'end-1c'), 'a\n')
|
||||
eq(fix(), 'a'+io.eol_convention)
|
||||
|
||||
|
||||
def _extension_in_filetypes(extension):
|
||||
return any(
|
||||
f'*{extension}' in filetype_tuple[1]
|
||||
for filetype_tuple in iomenu.IOBinding.filetypes
|
||||
)
|
||||
|
||||
|
||||
class FiletypesTest(unittest.TestCase):
|
||||
def test_python_source_files(self):
|
||||
for extension in util.py_extensions:
|
||||
with self.subTest(extension=extension):
|
||||
self.assertTrue(
|
||||
_extension_in_filetypes(extension)
|
||||
)
|
||||
|
||||
def test_text_files(self):
|
||||
self.assertTrue(_extension_in_filetypes('.txt'))
|
||||
|
||||
def test_all_files(self):
|
||||
self.assertTrue(_extension_in_filetypes(''))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(verbosity=2)
|
||||
113
Dependencies/Python/Lib/idlelib/idle_test/test_macosx.py
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
"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
|
||||
|
||||
|
||||
def setUpModule():
|
||||
global orig_tktype
|
||||
orig_tktype = macosx._tk_type
|
||||
|
||||
|
||||
def tearDownModule():
|
||||
macosx._tk_type = orig_tktype
|
||||
|
||||
|
||||
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)
|
||||
42
Dependencies/Python/Lib/idlelib/idle_test/test_mainmenu.py
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
"Test mainmenu, coverage 100%."
|
||||
# Reported as 88%; mocking turtledemo absence would have no point.
|
||||
|
||||
from idlelib import mainmenu
|
||||
import re
|
||||
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)
|
||||
|
||||
def test_tcl_indexes(self):
|
||||
# Test tcl patterns used to find menuitem to alter.
|
||||
# On failure, change pattern here and in function(s).
|
||||
# Patterns here have '.*' for re instead of '*' for tcl.
|
||||
for menu, pattern in (
|
||||
('debug', '.*tack.*iewer'), # PyShell.debug_menu_postcommand
|
||||
('options', '.*ode.*ontext'), # EW.__init__, CodeContext.toggle...
|
||||
('options', '.*ine.*umbers'), # EW.__init__, EW.toggle...event.
|
||||
):
|
||||
with self.subTest(menu=menu, pattern=pattern):
|
||||
for menutup in mainmenu.menudefs:
|
||||
if menutup[0] == menu:
|
||||
break
|
||||
else:
|
||||
self.assertTrue(0, f"{menu} not in menudefs")
|
||||
self.assertTrue(any(re.search(pattern, menuitem[0])
|
||||
for menuitem in menutup[1]
|
||||
if menuitem is not None), # Separator.
|
||||
f"{pattern} not in {menu}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(verbosity=2)
|
||||
49
Dependencies/Python/Lib/idlelib/idle_test/test_multicall.py
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
"Test multicall, coverage 33%."
|
||||
|
||||
from idlelib import multicall
|
||||
import unittest
|
||||
from test.support import requires
|
||||
from test.support.testcase import ExtraAssertions
|
||||
from tkinter import Tk, Text
|
||||
|
||||
|
||||
class MultiCallTest(unittest.TestCase, ExtraAssertions):
|
||||
|
||||
@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.assertIsSubclass(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)
|
||||
|
||||
def test_yview(self):
|
||||
# Added for tree.wheel_event
|
||||
# (it depends on yview to not be overridden)
|
||||
mc = self.mc
|
||||
self.assertIs(mc.yview, Text.yview)
|
||||
mctext = self.mc(self.root)
|
||||
self.assertIs(mctext.yview.__func__, Text.yview)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(verbosity=2)
|
||||
171
Dependencies/Python/Lib/idlelib/idle_test/test_outwin.py
vendored
Normal file
@@ -0,0 +1,171 @@
|
||||
"Test outwin, coverage 76%."
|
||||
|
||||
from idlelib import outwin
|
||||
import sys
|
||||
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)
|
||||
if sys.platform == 'darwin': # Issue 112938
|
||||
cls.text.update = cls.text.update_idletasks
|
||||
# Without this, test write, writelines, and goto... fail.
|
||||
# The reasons and why macOS-specific are unclear.
|
||||
|
||||
@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
|
||||
|
||||
# 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])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(verbosity=2)
|
||||
112
Dependencies/Python/Lib/idlelib/idle_test/test_parenmatch.py
vendored
Normal 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.prompt_last_line = '>>>' # Currently not used by parenmatch.
|
||||
|
||||
|
||||
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.
|
||||
|
||||
Force execution of conditional expressions and alternate paths.
|
||||
"""
|
||||
text = self.text
|
||||
pm = self.get_parenmatch()
|
||||
|
||||
text.insert('insert', '# Comment.)')
|
||||
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)
|
||||
86
Dependencies/Python/Lib/idlelib/idle_test/test_pathbrowser.py
vendored
Normal 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)
|
||||
118
Dependencies/Python/Lib/idlelib/idle_test/test_percolator.py
vendored
Normal 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)
|
||||
483
Dependencies/Python/Lib/idlelib/idle_test/test_pyparse.py
vendored
Normal file
@@ -0,0 +1,483 @@
|
||||
"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
|
||||
def char_in_string_false(index): return False
|
||||
|
||||
# First line starts with 'def' and ends with ':', then 0 is the pos.
|
||||
setcode('def spam():\n')
|
||||
eq(start(char_in_string_false), 0)
|
||||
|
||||
# First line begins with a keyword in the list and ends
|
||||
# with an open brace, then 0 is the pos. This is how
|
||||
# hyperparser calls this function as the newline is not added
|
||||
# in the editor, but rather on the call to setcode.
|
||||
setcode('class spam( ' + ' \n')
|
||||
eq(start(char_in_string_false), 0)
|
||||
|
||||
# Split def across lines.
|
||||
setcode('"""This is a module docstring"""\n'
|
||||
'class C:\n'
|
||||
' def __init__(self, a,\n'
|
||||
' b=True):\n'
|
||||
' pass\n'
|
||||
)
|
||||
pos0, pos = 33, 42 # Start of 'class...', ' def' lines.
|
||||
|
||||
# Passing no value or non-callable should fail (issue 32989).
|
||||
with self.assertRaises(TypeError):
|
||||
start()
|
||||
with self.assertRaises(TypeError):
|
||||
start(False)
|
||||
|
||||
# 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(char_in_string_false), pos)
|
||||
|
||||
# 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 > pos), pos)
|
||||
# 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 >= pos), pos0)
|
||||
# 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 < pos), 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'
|
||||
) # Does not affect class, def positions.
|
||||
eq(start(char_in_string_false), pos)
|
||||
eq(start(is_char_in_string=lambda index: index > pos), pos)
|
||||
eq(start(is_char_in_string=lambda index: index >= pos), pos0)
|
||||
# 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 < pos), pos)
|
||||
|
||||
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'
|
||||
)
|
||||
pos = 42
|
||||
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(pos)
|
||||
self.assertEqual(p.code, code[pos:])
|
||||
|
||||
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 continuation.
|
||||
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 continuation docstring.\n\n",
|
||||
0, 48, "'", None, ((0, 0), (0, 1), (48, 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), # "", unneeded 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)
|
||||