[Feature][YUKUAI_patch]add 18.02 code

Only Configure: No
Affected branch: master
Affected module: unknow
Is it affected on both ZXIC and MTK: only ZXIC
Self-test: Yes
Doc Update: No

Change-Id: I7f71153004f10fc0ea5adfa083866aaeeb1053ac
diff --git a/rootfs/usr/lib/python3.8/idlelib/CREDITS.txt b/rootfs/usr/lib/python3.8/idlelib/CREDITS.txt
new file mode 100644
index 0000000..3a50eb8
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/CREDITS.txt
@@ -0,0 +1,37 @@
+Guido van Rossum, as well as being the creator of the Python language, is the
+original creator of IDLE.  Other contributors prior to Version 0.8 include
+Mark Hammond, Jeremy Hylton, Tim Peters, and Moshe Zadka.
+
+IDLE's recent development was carried out in the SF IDLEfork project. The
+objective was to develop a version of IDLE which had an execution environment
+which could be initialized prior to each run of user code.
+
+The IDLEfork project was initiated by David Scherer, with some help from Peter
+Schneider-Kamp and Nicholas Riley.  David wrote the first version of the RPC
+code and designed a fast turn-around environment for VPython.  Guido developed
+the RPC code and Remote Debugger currently integrated in IDLE.  Bruce Sherwood
+contributed considerable time testing and suggesting improvements.
+
+Besides David and Guido, the main developers who were active on IDLEfork
+are Stephen M. Gava, who implemented the configuration GUI, the new
+configuration system, and the About dialog, and Kurt B. Kaiser, who completed
+the integration of the RPC and remote debugger, implemented the threaded
+subprocess, and made a number of usability enhancements.
+
+Other contributors include Raymond Hettinger, Tony Lownds (Mac integration),
+Neal Norwitz (code check and clean-up), Ronald Oussoren (Mac integration),
+Noam Raphael (Code Context, Call Tips, many other patches), and Chui Tey (RPC
+integration, debugger integration and persistent breakpoints).
+
+Scott David Daniels, Tal Einat, Hernan Foffani, Christos Georgiou,
+Jim Jewett, Martin v. Löwis, Jason Orendorff, Guilherme Polo, Josh Robb,
+Nigel Rowe, Bruce Sherwood, Jeff Shute, and Weeble have submitted useful
+patches.  Thanks, guys!
+
+For additional details refer to NEWS.txt and Changelog.
+
+Please contact the IDLE maintainer (kbk@shore.net) to have yourself included
+here if you are one of those we missed!
+
+
+
diff --git a/rootfs/usr/lib/python3.8/idlelib/ChangeLog b/rootfs/usr/lib/python3.8/idlelib/ChangeLog
new file mode 100644
index 0000000..d7d7e1e
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/ChangeLog
@@ -0,0 +1,1591 @@
+Please refer to the IDLEfork and IDLE CVS repositories for
+change details subsequent to the 0.8.1 release.
+
+
+IDLEfork ChangeLog
+==================
+
+2001-07-20 11:35  elguavas
+
+	* README.txt, NEWS.txt: bring up to date for 0.8.1 release
+
+2001-07-19 16:40  elguavas
+
+	* IDLEFORK.html: replaced by IDLEFORK-index.html
+
+2001-07-19 16:39  elguavas
+
+	* IDLEFORK-index.html: updated placeholder idlefork homepage
+
+2001-07-19 14:49  elguavas
+
+	* ChangeLog, EditorWindow.py, INSTALLATION, NEWS.txt, README.txt,
+	TODO.txt, idlever.py:
+	minor tidy-ups ready for 0.8.1 alpha tarball release
+
+2001-07-17 15:12  kbk
+
+	* INSTALLATION, setup.py: INSTALLATION: Remove the coexist.patch
+	instructions
+
+	**************** setup.py:
+
+	Remove the idles script, add some words on IDLE Fork to the
+	long_description, and clean up some line spacing.
+
+2001-07-17 15:01  kbk
+
+	* coexist.patch: Put this in the attic, at least for now...
+
+2001-07-17 14:59  kbk
+
+	* PyShell.py, idle, idles: Implement idle command interface as
+	suggested by GvR [idle-dev] 16 July **************** PyShell: Added
+	functionality:
+
+	usage: idle.py [-c command] [-d] [-i] [-r script] [-s] [-t title]
+	[arg] ...
+
+	idle file(s)	(without options) edit the file(s)
+
+	-c cmd	   run the command in a shell -d	 enable the
+	debugger -i	    open an interactive shell -i file(s) open a
+	shell and also an editor window for each file -r script  run a file
+	as a script in a shell -s	  run $IDLESTARTUP or
+	$PYTHONSTARTUP before anything else -t title   set title of shell
+	window
+
+	Remaining arguments are applied to the command (-c) or script (-r).
+
+	****************** idles: Removed the idles script, not needed
+
+	****************** idle:  Removed the IdleConf references, not
+	required anymore
+
+2001-07-16 17:08  kbk
+
+	* INSTALLATION, coexist.patch: Added installation instructions.
+
+	Added a patch which modifies idlefork so that it can co-exist with
+	"official" IDLE in the site-packages directory. This patch is not
+	necessary if only idlefork IDLE is installed. See INSTALLATION for
+	further details.
+
+2001-07-16 15:50  kbk
+
+	* idles: Add a script "idles" which opens a Python Shell window.
+
+	The default behaviour of idlefork idle is to open an editor window
+	instead of a shell. Complex expressions may be run in a fresh
+	environment by selecting "run".  There are times, however, when a
+	shell is desired.  Though one can be started by "idle -t 'foo'",
+	this script is more convenient.  In addition, a shell and an editor
+	window can be started in parallel by "idles -e foo.py".
+
+2001-07-16 15:25  kbk
+
+	* PyShell.py: Call out IDLE Fork in startup message.
+
+2001-07-16 14:00  kbk
+
+	* PyShell.py, setup.py: Add a script "idles" which opens a Python
+	Shell window.
+
+	The default behaviour of idlefork idle is to open an editor window
+	instead of a shell. Complex expressions may be run in a fresh
+	environment by selecting "run".  There are times, however, when a
+	shell is desired.  Though one can be started by "idle -t 'foo'",
+	this script is more convenient.  In addition, a shell and an editor
+	window can be started in parallel by "idles -e foo.py".
+
+2001-07-15 03:06  kbk
+
+	* pyclbr.py, tabnanny.py: tabnanny and pyclbr are now found in /Lib
+
+2001-07-15 02:29  kbk
+
+	* BrowserControl.py: Remove, was retained for 1.5.2 support
+
+2001-07-14 15:48  kbk
+
+	* setup.py: Installing Idle to site-packages via Distutils does not
+	copy the Idle help.txt file.
+
+	Ref SF Python Patch 422471
+
+2001-07-14 15:26  kbk
+
+	* keydefs.py: py-cvs-2001_07_13 (Rev 1.3) merge
+
+	"Make copy, cut and paste events case insensitive.  Reported by
+	Patrick K. O'Brien on idle-dev. (Should other bindings follow
+	suit?)" --GvR
+
+2001-07-14 15:21  kbk
+
+	* idle.py: py-cvs-2001_07_13 (Rev 1.4) merge
+
+	"Move the action of loading the configuration to the IdleConf
+	module rather than the idle.py script.	This has advantages and
+	disadvantages; the biggest advantage being that we can more easily
+	have an alternative main program."  --GvR
+
+2001-07-14 15:18  kbk
+
+	* extend.txt: py-cvs-2001_07_13 (Rev 1.4) merge
+
+	"Quick update to the extension mechanism (extend.py is gone, long
+	live config.txt)" --GvR
+
+2001-07-14 15:15  kbk
+
+	* StackViewer.py: py-cvs-2001_07_13 (Rev 1.16) merge
+
+	"Refactored, with some future plans in mind. This now uses the new
+	gotofileline() method defined in FileList.py"  --GvR
+
+2001-07-14 15:10  kbk
+
+	* PyShell.py: py-cvs-2001_07_13 (Rev 1.34) merge
+
+	"Amazing.  A very subtle change in policy in descr-branch actually
+	found a bug here.  Here's the deal: Class PyShell derives from
+	class OutputWindow.  Method PyShell.close() wants to invoke its
+	parent method, but because PyShell long ago was inherited from
+	class PyShellEditorWindow, it invokes
+	PyShelEditorWindow.close(self).  Now, class PyShellEditorWindow
+	itself derives from class OutputWindow, and inherits the close()
+	method from there without overriding it.  Under the old rules,
+	PyShellEditorWindow.close would return an unbound method restricted
+	to the class that defined the implementation of close(), which was
+	OutputWindow.close.  Under the new rules, the unbound method is
+	restricted to the class whose method was requested, that is
+	PyShellEditorWindow, and this was correctly trapped as an error."
+	--GvR
+
+2001-07-14 14:59  kbk
+
+	* PyParse.py: py-cvs-2001_07_13 (Rel 1.9) merge
+
+	"Taught IDLE's autoident parser that "yield" is a keyword that
+	begins a stmt.	Along w/ the preceding change to keyword.py, making
+	all this work w/ a future-stmt just looks harder and harder."
+	--tim_one
+
+	(From Rel 1.8: "Hack to make this still work with Python 1.5.2.
+	;-( " --fdrake)
+
+2001-07-14 14:51  kbk
+
+	* IdleConf.py: py-cvs-2001_07_13 (Rel 1.7) merge
+
+	"Move the action of loading the configuration to the IdleConf
+	module rather than the idle.py script.	This has advantages and
+	disadvantages; the biggest advantage being that we can more easily
+	have an alternative main program." --GvR
+
+2001-07-14 14:45  kbk
+
+	* FileList.py: py-cvs-2000_07_13 (Rev 1.9) merge
+
+	"Delete goodname() method, which is unused. Add gotofileline(), a
+	convenience method which I intend to use in a variant. Rename
+	test() to _test()."  --GvR
+
+	This was an interesting merge. The join completely missed removing
+	goodname(), which was adjacent, but outside of, a small conflict.
+	I only caught it by comparing the 1.1.3.2/1.1.3.3 diff.  CVS ain't
+	infallible.
+
+2001-07-14 13:58  kbk
+
+	* EditorWindow.py: py-cvs-2000_07_13 (Rev 1.38) merge "Remove
+	legacy support for the BrowserControl module; the webbrowser module
+	has been included since Python 2.0, and that is the preferred
+	interface." --fdrake
+
+2001-07-14 13:32  kbk
+
+	* EditorWindow.py, FileList.py, IdleConf.py, PyParse.py,
+	PyShell.py, StackViewer.py, extend.txt, idle.py, keydefs.py: Import
+	the 2001 July 13 23:59 GMT version of Python CVS IDLE on the
+	existing 1.1.3 vendor branch named py-cvs-vendor-branch. Release
+	tag is py-cvs-2001_07_13.
+
+2001-07-14 12:02  kbk
+
+	* Icons/python.gif: py-cvs-rel2_1 (Rev 1.2) merge Copied py-cvs rev
+	1.2 changed file to idlefork MAIN
+
+2001-07-14 11:58  kbk
+
+	* Icons/minusnode.gif: py-cvs-rel2_1 (Rev 1.2) merge Copied py-cvs
+	1.2 changed file to idlefork MAIN
+
+2001-07-14 11:23  kbk
+
+	* ScrolledList.py: py-cvs-rel2_1 (rev 1.5) merge - whitespace
+	normalization
+
+2001-07-14 11:20  kbk
+
+	* Separator.py: py-cvs-rel2_1 (Rev 1.3) merge - whitespace
+	normalization
+
+2001-07-14 11:16  kbk
+
+	* StackViewer.py: py-cvs-rel2_1 (Rev 1.15) merge - whitespace
+	normalization
+
+2001-07-14 11:14  kbk
+
+	* ToolTip.py: py-cvs-rel2_1 (Rev 1.2) merge - whitespace
+	normalization
+
+2001-07-14 10:13  kbk
+
+	* PyShell.py: cvs-py-rel2_1 (Rev 1.29 - 1.33) merge
+
+	Merged the following py-cvs revs without conflict: 1.29 Reduce
+	copyright text output at startup 1.30 Delay setting sys.args until
+	Tkinter is fully initialized 1.31 Whitespace normalization 1.32
+	Turn syntax warning into error when interactive 1.33 Fix warning
+	initialization bug
+
+	Note that module is extensively modified wrt py-cvs
+
+2001-07-14 06:33  kbk
+
+	* PyParse.py: py-cvs-rel2_1 (Rev 1.6 - 1.8) merge Fix autoindent
+	bug and deflect Unicode from text.get()
+
+2001-07-14 06:00  kbk
+
+	* Percolator.py: py-cvs-rel2_1 (Rev 1.3) "move "from Tkinter import
+	*" to module level" --jhylton
+
+2001-07-14 05:57  kbk
+
+	* PathBrowser.py: py-cvs-rel2_1 (Rev 1.6) merge - whitespace
+	normalization
+
+2001-07-14 05:49  kbk
+
+	* ParenMatch.py: cvs-py-rel2_1 (Rev 1.5) merge - whitespace
+	normalization
+
+2001-07-14 03:57  kbk
+
+	* ObjectBrowser.py: py-cvs-rel2_1 (Rev 1.3) merge "Make the test
+	program work outside IDLE."  -- GvR
+
+2001-07-14 03:52  kbk
+
+	* MultiStatusBar.py: py-cvs-rel2_1 (Rev 1.2) merge - whitespace
+	normalization
+
+2001-07-14 03:44  kbk
+
+	* MultiScrolledLists.py: py-cvs-rel2_1 (Rev 1.2) merge - whitespace
+	normalization
+
+2001-07-14 03:40  kbk
+
+	* IdleHistory.py: py-cvs-rel2_1 (Rev 1.4) merge - whitespace
+	normalization
+
+2001-07-14 03:38  kbk
+
+	* IdleConf.py: py-cvs-rel2_1 (Rev 1.6) merge - whitespace
+	normalization
+
+2001-07-13 14:18  kbk
+
+	* IOBinding.py: py-cvs-rel2_1 (Rev 1.4) merge - move "import *" to
+	module level
+
+2001-07-13 14:12  kbk
+
+	* FormatParagraph.py: py-cvs-rel2_1 (Rev 1.9) merge - whitespace
+	normalization
+
+2001-07-13 14:07  kbk
+
+	* FileList.py: py-cvs-rel2_1 (Rev 1.8) merge - whitespace
+	normalization
+
+2001-07-13 13:35  kbk
+
+	* EditorWindow.py: py-cvs-rel2_1 (Rev 1.33 - 1.37) merge
+
+	VP IDLE version depended on VP's ExecBinding.py and spawn.py to get
+	the path to the Windows Doc directory (relative to python.exe).
+	Removed this conflicting code in favor of py-cvs updates which on
+	Windows use a hard coded path relative to the location of this
+	module. py-cvs updates include support for webbrowser.py.  Module
+	still has BrowserControl.py for 1.5.2 support.
+
+	At this point, the differences wrt py-cvs relate to menu
+	functionality.
+
+2001-07-13 11:30  kbk
+
+	* ConfigParser.py: py-cvs-rel2_1 merge - Remove, lives in /Lib
+
+2001-07-13 10:10  kbk
+
+	* Delegator.py: py-cvs-rel2_1 (Rev 1.3) merge - whitespace
+	normalization
+
+2001-07-13 10:07  kbk
+
+	* Debugger.py: py-cvs-rel2_1 (Rev 1.15) merge - whitespace
+	normalization
+
+2001-07-13 10:04  kbk
+
+	* ColorDelegator.py: py-cvs-rel2_1 (Rev 1.11 and 1.12) merge
+	Colorize "as" after "import" / use DEBUG instead of __debug__
+
+2001-07-13 09:54  kbk
+
+	* ClassBrowser.py: py-cvs-rel2_1 (Rev 1.12) merge - whitespace
+	normalization
+
+2001-07-13 09:41  kbk
+
+	* BrowserControl.py: py-cvs-rel2_1 (Rev 1.1) merge - New File -
+	Force HEAD to trunk with -f Note: browser.py was renamed
+	BrowserControl.py 10 May 2000. It provides a collection of classes
+	and convenience functions to control external browsers "for 1.5.2
+	support". It was removed from py-cvs 18 April 2001.
+
+2001-07-13 09:10  kbk
+
+	* CallTips.py: py-cvs-rel2_1 (Rev 1.8) merge - whitespace
+	normalization
+
+2001-07-13 08:26  kbk
+
+	* CallTipWindow.py: py-cvs-rel2_1 (Rev 1.3) merge - whitespace
+	normalization
+
+2001-07-13 08:13  kbk
+
+	* AutoExpand.py: py-cvs-rel1_2 (Rev 1.4) merge, "Add Alt-slash to
+	Unix keydefs (I somehow need it on RH 6.2).  Get rid of assignment
+	to unused self.text.wordlist."	--GvR
+
+2001-07-12 16:54  elguavas
+
+	* ReplaceDialog.py: py-cvs merge, python 1.5.2 compatibility
+
+2001-07-12 16:46  elguavas
+
+	* ScriptBinding.py: py-cvs merge, better error dialog
+
+2001-07-12 16:38  elguavas
+
+	* TODO.txt: py-cvs merge, additions
+
+2001-07-12 15:35  elguavas
+
+	* WindowList.py: py-cvs merge, correct indentation
+
+2001-07-12 15:24  elguavas
+
+	* config.txt: py-cvs merge, correct typo
+
+2001-07-12 15:21  elguavas
+
+	* help.txt: py-cvs merge, update colour changing info
+
+2001-07-12 14:51  elguavas
+
+	* idle.py: py-cvs merge, idle_dir loading changed
+
+2001-07-12 14:44  elguavas
+
+	* idlever.py: py-cvs merge, version update
+
+2001-07-11 12:53  kbk
+
+	* BrowserControl.py: Initial revision
+
+2001-07-11 12:53  kbk
+
+	* AutoExpand.py, BrowserControl.py, CallTipWindow.py, CallTips.py,
+	ClassBrowser.py, ColorDelegator.py, Debugger.py, Delegator.py,
+	EditorWindow.py, FileList.py, FormatParagraph.py, IOBinding.py,
+	IdleConf.py, IdleHistory.py, MultiScrolledLists.py,
+	MultiStatusBar.py, ObjectBrowser.py, OutputWindow.py,
+	ParenMatch.py, PathBrowser.py, Percolator.py, PyParse.py,
+	PyShell.py, RemoteInterp.py, ReplaceDialog.py, ScriptBinding.py,
+	ScrolledList.py, Separator.py, StackViewer.py, TODO.txt,
+	ToolTip.py, WindowList.py, config.txt, help.txt, idle, idle.bat,
+	idle.py, idlever.py, setup.py, Icons/minusnode.gif,
+	Icons/python.gif: Import the release 2.1 version of Python CVS IDLE
+	on the existing 1.1.3 vendor branch named py-cvs-vendor-branch,
+	with release tag py-cvs-rel2_1.
+
+2001-07-11 12:34  kbk
+
+	* AutoExpand.py, AutoIndent.py, Bindings.py, CallTipWindow.py,
+	CallTips.py, ChangeLog, ClassBrowser.py, ColorDelegator.py,
+	Debugger.py, Delegator.py, EditorWindow.py, FileList.py,
+	FormatParagraph.py, FrameViewer.py, GrepDialog.py, IOBinding.py,
+	IdleConf.py, IdleHistory.py, MultiScrolledLists.py,
+	MultiStatusBar.py, NEWS.txt, ObjectBrowser.py, OldStackViewer.py,
+	OutputWindow.py, ParenMatch.py, PathBrowser.py, Percolator.py,
+	PyParse.py, PyShell.py, README.txt, RemoteInterp.py,
+	ReplaceDialog.py, ScriptBinding.py, ScrolledList.py,
+	SearchBinding.py, SearchDialog.py, SearchDialogBase.py,
+	SearchEngine.py, Separator.py, StackViewer.py, TODO.txt,
+	ToolTip.py, TreeWidget.py, UndoDelegator.py, WidgetRedirector.py,
+	WindowList.py, ZoomHeight.py, __init__.py, config-unix.txt,
+	config-win.txt, config.txt, eventparse.py, extend.txt, help.txt,
+	idle.bat, idle.py, idle.pyw, idlever.py, keydefs.py, pyclbr.py,
+	tabnanny.py, testcode.py, Icons/folder.gif, Icons/minusnode.gif,
+	Icons/openfolder.gif, Icons/plusnode.gif, Icons/python.gif,
+	Icons/tk.gif: Import the 9 March 2000 version of Python CVS IDLE as
+	1.1.3 vendor branch named py-cvs-vendor-branch.
+
+2001-07-04 13:43  kbk
+
+	* Icons/: folder.gif, minusnode.gif, openfolder.gif, plusnode.gif,
+	python.gif, tk.gif: Null commit with -f option to force an uprev
+	and put HEADs firmly on the trunk.
+
+2001-07-04 13:15  kbk
+
+	* AutoExpand.py, AutoIndent.py, Bindings.py, CallTipWindow.py,
+	CallTips.py, ChangeLog, ClassBrowser.py, ColorDelegator.py,
+	ConfigParser.py, Debugger.py, Delegator.py, EditorWindow.py,
+	ExecBinding.py, FileList.py, FormatParagraph.py, FrameViewer.py,
+	GrepDialog.py, IDLEFORK.html, IOBinding.py, IdleConf.py,
+	IdleHistory.py, MultiScrolledLists.py, MultiStatusBar.py, NEWS.txt,
+	ObjectBrowser.py, OldStackViewer.py, OutputWindow.py,
+	ParenMatch.py, PathBrowser.py, Percolator.py, PyParse.py,
+	PyShell.py, README.txt, Remote.py, RemoteInterp.py,
+	ReplaceDialog.py, ScriptBinding.py, ScrolledList.py,
+	SearchBinding.py, SearchDialog.py, SearchDialogBase.py,
+	SearchEngine.py, Separator.py, StackViewer.py, TODO.txt,
+	ToolTip.py, TreeWidget.py, UndoDelegator.py, WidgetRedirector.py,
+	WindowList.py, ZoomHeight.py, __init__.py, config-unix.txt,
+	config-win.txt, config.txt, eventparse.py, extend.txt, help.txt,
+	idle, idle.bat, idle.py, idle.pyw, idlever.py, keydefs.py,
+	loader.py, protocol.py, pyclbr.py, setup.py, spawn.py, tabnanny.py,
+	testcode.py: Null commit with -f option to force an uprev and put
+	HEADs firmly on the trunk.
+
+2001-06-27 10:24  elguavas
+
+	* IDLEFORK.html: updated contact details
+
+2001-06-25 17:23  elguavas
+
+	* idle, RemoteInterp.py, setup.py: Initial revision
+
+2001-06-25 17:23  elguavas
+
+	* idle, RemoteInterp.py, setup.py: import current python cvs idle
+	as a vendor branch
+
+2001-06-24 15:10  elguavas
+
+	* IDLEFORK.html: tiny change to test new syncmail setup
+
+2001-06-24 14:41  elguavas
+
+	* IDLEFORK.html: change to new developer contact, also a test
+	commit for new syncmail setup
+
+2001-06-23 18:15  elguavas
+
+	* IDLEFORK.html: tiny test update for revitalised idle-fork
+
+2000-09-24 17:29  nriley
+
+	* protocol.py: Fixes for Python 1.6 compatibility - socket bind and
+	connect get a tuple instead two arguments.
+
+2000-09-24 17:28  nriley
+
+	* spawn.py: Change for Python 1.6 compatibility - UNIX's 'os'
+	module defines 'spawnv' now, so we check for 'fork' first.
+
+2000-08-15 22:51  nowonder
+
+	* IDLEFORK.html:
+	corrected email address
+
+2000-08-15 22:47  nowonder
+
+	* IDLEFORK.html:
+	added .html file for http://idlefork.sourceforge.net
+
+2000-08-15 11:13  dscherer
+
+	* AutoExpand.py, AutoIndent.py, Bindings.py, CallTipWindow.py,
+	CallTips.py, __init__.py, ChangeLog, ClassBrowser.py,
+	ColorDelegator.py, ConfigParser.py, Debugger.py, Delegator.py,
+	FileList.py, FormatParagraph.py, FrameViewer.py, GrepDialog.py,
+	IOBinding.py, IdleConf.py, IdleHistory.py, MultiScrolledLists.py,
+	MultiStatusBar.py, NEWS.txt, ObjectBrowser.py, OldStackViewer.py,
+	OutputWindow.py, ParenMatch.py, PathBrowser.py, Percolator.py,
+	PyParse.py, PyShell.py, README.txt, ReplaceDialog.py,
+	ScriptBinding.py, ScrolledList.py, SearchBinding.py,
+	SearchDialog.py, SearchDialogBase.py, SearchEngine.py,
+	Separator.py, StackViewer.py, TODO.txt, ToolTip.py, TreeWidget.py,
+	UndoDelegator.py, WidgetRedirector.py, WindowList.py, help.txt,
+	ZoomHeight.py, config-unix.txt, config-win.txt, config.txt,
+	eventparse.py, extend.txt, idle.bat, idle.py, idle.pyw, idlever.py,
+	keydefs.py, loader.py, pyclbr.py, tabnanny.py, testcode.py,
+	EditorWindow.py, ExecBinding.py, Remote.py, protocol.py, spawn.py,
+	Icons/folder.gif, Icons/minusnode.gif, Icons/openfolder.gif,
+	Icons/plusnode.gif, Icons/python.gif, Icons/tk.gif: Initial
+	revision
+
+2000-08-15 11:13  dscherer
+
+	* AutoExpand.py, AutoIndent.py, Bindings.py, CallTipWindow.py,
+	CallTips.py, __init__.py, ChangeLog, ClassBrowser.py,
+	ColorDelegator.py, ConfigParser.py, Debugger.py, Delegator.py,
+	FileList.py, FormatParagraph.py, FrameViewer.py, GrepDialog.py,
+	IOBinding.py, IdleConf.py, IdleHistory.py, MultiScrolledLists.py,
+	MultiStatusBar.py, NEWS.txt, ObjectBrowser.py, OldStackViewer.py,
+	OutputWindow.py, ParenMatch.py, PathBrowser.py, Percolator.py,
+	PyParse.py, PyShell.py, README.txt, ReplaceDialog.py,
+	ScriptBinding.py, ScrolledList.py, SearchBinding.py,
+	SearchDialog.py, SearchDialogBase.py, SearchEngine.py,
+	Separator.py, StackViewer.py, TODO.txt, ToolTip.py, TreeWidget.py,
+	UndoDelegator.py, WidgetRedirector.py, WindowList.py, help.txt,
+	ZoomHeight.py, config-unix.txt, config-win.txt, config.txt,
+	eventparse.py, extend.txt, idle.bat, idle.py, idle.pyw, idlever.py,
+	keydefs.py, loader.py, pyclbr.py, tabnanny.py, testcode.py,
+	EditorWindow.py, ExecBinding.py, Remote.py, protocol.py, spawn.py,
+	Icons/folder.gif, Icons/minusnode.gif, Icons/openfolder.gif,
+	Icons/plusnode.gif, Icons/python.gif, Icons/tk.gif: Modified IDLE
+	from VPython 0.2
+
+
+original IDLE ChangeLog:
+========================
+
+Tue Feb 15 18:08:19 2000  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* NEWS.txt: Notice status bar and stack viewer.
+
+	* EditorWindow.py: Support for Moshe's status bar.
+
+	* MultiStatusBar.py: Status bar code -- by Moshe Zadka.
+
+	* OldStackViewer.py:
+	Adding the old stack viewer implementation back, for the debugger.
+
+	* StackViewer.py: New stack viewer, uses a tree widget.
+	(XXX: the debugger doesn't yet use this.)
+
+	* WindowList.py:
+	Correct a typo and remove an unqualified except that was hiding the error.
+
+	* ClassBrowser.py: Add an XXX comment about the ClassBrowser AIP.
+
+	* ChangeLog: Updated change log.
+
+	* NEWS.txt: News update.  Probably incomplete; what else is new?
+
+	* README.txt:
+	Updated for pending IDLE 0.5 release (still very rough -- just getting
+	it out in a more convenient format than CVS).
+
+	* TODO.txt: Tiny addition.
+
+Thu Sep  9 14:16:02 1999  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* TODO.txt: A few new TODO entries.
+
+Thu Aug 26 23:06:22 1999  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* Bindings.py: Add Python Documentation entry to Help menu.
+
+	* EditorWindow.py:
+	Find the help.txt file relative to __file__ or ".", not in sys.path.
+	(Suggested by Moshe Zadka, but implemented differently.)
+
+	Add <<python-docs>> event which, on Unix, brings up Netscape pointing
+	to http://www.python.doc/current/ (a local copy would be nice but its
+	location can't be predicted).  Windows solution TBD.
+
+Wed Aug 11 14:55:43 1999  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* TreeWidget.py:
+	Moshe noticed an inconsistency in his comment, so I'm rephrasing it to
+	be clearer.
+
+	* TreeWidget.py:
+	Patch inspired by Moshe Zadka to search for the Icons directory in the
+	same directory as __file__, rather than searching for it along sys.path.
+	This works better when idle is a package.
+
+Thu Jul 15 13:11:02 1999  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* TODO.txt: New wishes.
+
+Sat Jul 10 13:17:35 1999  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* IdlePrefs.py:
+	Make the color for stderr red (i.e. the standard warning/danger/stop
+	color) rather than green.  Suggested by Sam Schulenburg.
+
+Fri Jun 25 17:26:34 1999  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* PyShell.py: Close debugger when closing.  This may break a cycle.
+
+	* Debugger.py: Break cycle on close.
+
+	* ClassBrowser.py: Destroy the tree when closing.
+
+	* TreeWidget.py: Add destroy() method to recursively destroy a tree.
+
+	* PyShell.py: Extend _close() to break cycles.
+	Break some other cycles too (and destroy the root when done).
+
+	* EditorWindow.py:
+	Add _close() method that does the actual cleanup (close() asks the
+	user what they want first if there's unsaved stuff, and may cancel).
+	It closes more than before.
+
+	Add unload_extensions() method to unload all extensions; called from
+	_close().  It calls an extension's close() method if it has one.
+
+	* Percolator.py: Add close() method that breaks cycles.
+
+	* WidgetRedirector.py: Add unregister() method.
+	Unregister everything at closing.
+	Don't call close() in __del__, rely on explicit call to close().
+
+	* IOBinding.py, FormatParagraph.py, CallTips.py:
+	Add close() method that breaks a cycle.
+
+Fri Jun 11 15:03:00 1999  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* AutoIndent.py, EditorWindow.py, FormatParagraph.py:
+	Tim Peters smart.patch:
+
+	EditorWindow.py:
+
+	+ Added get_tabwidth & set_tabwidth "virtual text" methods, that get/set the
+	widget's view of what a tab means.
+
+	+ Moved TK_TABWIDTH_DEFAULT here from AutoIndent.
+
+	+ Renamed Mark's get_selection_index to get_selection_indices (sorry, Mark,
+	but the name was plain wrong <wink>).
+
+	FormatParagraph.py:  renamed use of get_selection_index.
+
+	AutoIndent.py:
+
+	+ Moved TK_TABWIDTH_DEFAULT to EditorWindow.
+
+	+ Rewrote set_indentation_params to use new VTW get/set_tabwidth methods.
+
+	+ Changed smart_backspace_event to delete whitespace back to closest
+	preceding virtual tab stop or real character (note that this may require
+	inserting characters if backspacing over a tab!).
+
+	+ Nuked almost references to the selection tag, in favor of using
+	get_selection_indices.  The sole exception is in set_region, for which no
+	"set_selection" abstraction has yet been agreed upon.
+
+	+ Had too much fun using the spiffy new features of the format-paragraph
+	cmd.
+
+Thu Jun 10 17:48:02 1999  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* FormatParagraph.py:
+	Code by Mark Hammond to format paragraphs embedded in comments.
+	Read the comments (which I reformatted using the new feature :-)
+	for some limitations.
+
+	* EditorWindow.py:
+	Added abstraction get_selection_index() (Mark Hammond).  Also
+	reformatted some comment blocks to show off a cool feature I'm about
+	to check in next.
+
+	* ClassBrowser.py:
+	Adapt to the new pyclbr's support of listing top-level functions.  If
+	this functionality is not present (e.g. when used with a vintage
+	Python 1.5.2 installation) top-level functions are not listed.
+
+	(Hmm...  Any distribution of IDLE 0.5 should probably include a copy
+	of the new pyclbr.py!)
+
+	* AutoIndent.py:
+	Fix off-by-one error in Tim's recent change to comment_region(): the
+	list of lines returned by get_region() contains an empty line at the
+	end representing the start of the next line, and this shouldn't be
+	commented out!
+
+	* CallTips.py:
+	Mark Hammond writes: Here is another change that allows it to work for
+	class creation - tries to locate an __init__ function.  Also updated
+	the test code to reflect your new "***" change.
+
+	* CallTipWindow.py:
+	Mark Hammond writes: Tim's suggestion of copying the font for the
+	CallTipWindow from the text control makes sense, and actually makes
+	the control look better IMO.
+
+Wed Jun  9 20:34:57 1999  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* CallTips.py:
+	Append "..." if the appropriate flag (for varargs) in co_flags is set.
+	Ditto "***" for kwargs.
+
+Tue Jun  8 13:06:07 1999  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* ReplaceDialog.py:
+	Hmm...  Tim didn't turn "replace all" into a single undo block.
+	I think I like it better if it os, so here.
+
+	* ReplaceDialog.py: Tim Peters: made replacement atomic for undo/redo.
+
+	* AutoIndent.py: Tim Peters:
+
+	+ Set usetabs=1.  Editing pyclbr.py was driving me nuts <0.6 wink>.
+	usetabs=1 is the Emacs pymode default too, and thanks to indentwidth !=
+	tabwidth magical usetabs disabling, new files are still created with tabs
+	turned off.  The only implication is that if you open a file whose first
+	indent is a single tab, IDLE will now magically use tabs for that file (and
+	set indentwidth to 8).  Note that the whole scheme doesn't work right for
+	PythonWin, though, since Windows users typically set tabwidth to 4; Mark
+	probably has to hide the IDLE algorithm from them (which he already knows).
+
+	+ Changed comment_region_event to stick "##" in front of every line.  The
+	"holes" previously left on blank lines were visually confusing (made it
+	needlessly hard to figure out what to uncomment later).
+
+Mon Jun  7 15:38:40 1999  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* TreeWidget.py, ObjectBrowser.py:
+	Remove unnecessary reference to pyclbr from test() code.
+
+	* PyParse.py: Tim Peters:
+
+	Smarter logic for finding a parse synch point.
+
+	Does a half to a fifth the work in normal cases; don't notice the speedup,
+	but makes  more breathing room for other extensions.
+
+	Speeds terrible cases by at least a factor of 10. "Terrible" == e.g. you put
+	""" at the start of Tkinter.py, undo it, zoom to the bottom, and start
+	typing in code.  Used to take about 8 seconds for ENTER to respond, now some
+	large fraction of a second.  The new code gets indented correctly, despite
+	that it all remains "string colored" until the colorizer catches up (after
+	which, ENTER appears instantaneous again).
+
+Fri Jun  4 19:21:19 1999  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* extend.py: Might as well enable CallTips by default.
+	If there are too many complaints I'll remove it again or fix it.
+
+Thu Jun  3 14:32:16 1999  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* AutoIndent.py, EditorWindow.py, PyParse.py:
+	New offerings by Tim Peters; he writes:
+
+	IDLE is now the first Python editor in the Universe not confused by my
+	doctest.py <wink>.
+
+	As threatened, this defines IDLE's is_char_in_string function as a
+	method of EditorWindow.  You just need to define one similarly in
+	whatever it is you pass as editwin to AutoIndent; looking at the
+	EditorWindow.py part of the patch should make this clear.
+
+	* GrepDialog.py: Enclose pattern in quotes in status message.
+
+	* CallTips.py:
+	Mark Hammond fixed some comments and improved the way the tip text is
+	constructed.
+
+Wed Jun  2 18:18:57 1999  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* CallTips.py:
+	My fix to Mark's code: restore the universal check on <KeyRelease>.
+	Always cancel on <Key-Escape> or <ButtonPress>.
+
+	* CallTips.py:
+	A version that Mark Hammond posted to the newsgroup.  Has some newer
+	stuff for getting the tip.  Had to fix the Key-( and Key-) events
+	for Unix.  Will have to re-apply my patch for catching KeyRelease and
+	ButtonRelease events.
+
+	* CallTipWindow.py, CallTips.py:
+	Call tips by Mark Hammond (plus tiny fix by me.)
+
+	* IdleHistory.py:
+	Changes by Mark Hammond: (1) support optional output_sep argument to
+	the constructor so he can eliminate the sys.ps2 that PythonWin leaves
+	in the source; (2) remove duplicate history items.
+
+	* AutoIndent.py:
+	Changes by Mark Hammond to allow using IDLE extensions in PythonWin as
+	well: make three dialog routines instance variables.
+
+	* EditorWindow.py:
+	Change by Mark Hammond to allow using IDLE extensions in PythonWin as
+	well: make three dialog routines instance variables.
+
+Tue Jun  1 20:06:44 1999  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* AutoIndent.py: Hah!  A fix of my own to Tim's code!
+	Unix bindings for <<toggle-tabs>> and <<change-indentwidth>> were
+	missing, and somehow that meant the events were never generated,
+	even though they were in the menu.  The new Unix bindings are now
+	the same as the Windows bindings (M-t and M-u).
+
+	* AutoIndent.py, PyParse.py, PyShell.py: Tim Peters again:
+
+	The new version (attached) is fast enough all the time in every real module
+	I have <whew!>.  You can make it slow by, e.g., creating an open list with
+	5,000 90-character identifiers (+ trailing comma) each on its own line, then
+	adding an item to the end -- but that still consumes less than a second on
+	my P5-166.  Response time in real code appears instantaneous.
+
+	Fixed some bugs.
+
+	New feature:  when hitting ENTER and the cursor is beyond the line's leading
+	indentation, whitespace is removed on both sides of the cursor; before
+	whitespace was removed only on the left; e.g., assuming the cursor is
+	between the comma and the space:
+
+	def something(arg1, arg2):
+	                   ^ cursor to the left of here, and hit ENTER
+	               arg2):   # new line used to end up here
+	              arg2):    # but now lines up the way you expect
+
+	New hack:  AutoIndent has grown a context_use_ps1 Boolean config option,
+	defaulting to 0 (false) and set to 1 (only) by PyShell.  Reason:  handling
+	the fancy stuff requires looking backward for a parsing synch point; ps1
+	lines are the only sensible thing to look for in a shell window, but are a
+	bad thing to look for in a file window (ps1 lines show up in my module
+	docstrings often).  PythonWin's shell should set this true too.
+
+	Persistent problem:  strings containing def/class can still screw things up
+	completely.  No improvement.  Simplest workaround is on the user's head, and
+	consists of inserting e.g.
+
+	def _(): pass
+
+	(or any other def/class) after the end of the multiline string that's
+	screwing them up.  This is especially irksome because IDLE's syntax coloring
+	is *not* confused, so when this happens the colors don't match the
+	indentation behavior they see.
+
+	* AutoIndent.py: Tim Peters again:
+
+	[Tim, after adding some bracket smarts to AutoIndent.py]
+	> ...
+	> What it can't possibly do without reparsing large gobs of text is
+	> suggest a reasonable indent level after you've *closed* a bracket
+	> left open on some previous line.
+	> ...
+
+	The attached can, and actually fast enough to use -- most of the time.  The
+	code is tricky beyond belief to achieve that, but it works so far; e.g.,
+
+	        return len(string.expandtabs(str[self.stmt_start :
+	                                         ^ indents to caret
+	                                         i],
+	                                     ^ indents to caret
+	                                     self.tabwidth)) + 1
+	    ^ indents to caret
+
+	It's about as smart as pymode now, wrt both bracket and backslash
+	continuation rules.  It does require reparsing large gobs of text, and if it
+	happens to find something that looks like a "def" or "class" or sys.ps1
+	buried in a multiline string, but didn't suck up enough preceding text to
+	see the start of the string, it's completely hosed.  I can't repair that --
+	it's just too slow to reparse from the start of the file all the time.
+
+	AutoIndent has grown a new num_context_lines tuple attribute that controls
+	how far to look back, and-- like other params --this could/should be made
+	user-overridable at startup and per-file on the fly.
+
+	* PyParse.py: New file by Tim Peters:
+
+	One new file in the attached, PyParse.py.  The LineStudier (whatever it was
+	called <wink>) class was removed from AutoIndent; PyParse subsumes its
+	functionality.
+
+	* AutoIndent.py: Tim Peters keeps revising this module (more to come):
+
+	Removed "New tabwidth" menu binding.
+
+	Added "a tab means how many spaces?" dialog to block tabify and untabify.  I
+	think prompting for this is good now:  they're usually at-most-once-per-file
+	commands, and IDLE can't let them change tabwidth from the Tk default
+	anymore, so IDLE can no longer presume to have any idea what a tab means.
+
+	Irony:  for the purpose of keeping comments aligned via tabs, Tk's
+	non-default approach is much nicer than the Emacs/Notepad/Codewright/vi/etc
+	approach.
+
+	* EditorWindow.py:
+	1. Catch NameError on import (could be raised by case mismatch on Windows).
+	2. No longer need to reset pyclbr cache and show watch cursor when calling
+	   ClassBrowser -- the ClassBrowser takes care of pyclbr and the TreeWidget
+	   takes care of the watch cursor.
+	3. Reset the focus to the current window after error message about class
+	   browser on buffer without filename.
+
+	* Icons/minusnode.gif, Icons/plusnode.gif: Missed a few.
+
+	* ClassBrowser.py, PathBrowser.py: Rewritten based on TreeWidget.py
+
+	* ObjectBrowser.py: Object browser, based on TreeWidget.py.
+
+	* TreeWidget.py: Tree widget done right.
+
+	* ToolTip.py: As yet unused code for tool tips.
+
+	* ScriptBinding.py:
+	Ensure sys.argv[0] is the script name on Run Script.
+
+	* ZoomHeight.py: Move zoom height functionality to separate function.
+
+	* Icons/folder.gif, Icons/openfolder.gif, Icons/python.gif, Icons/tk.gif:
+	A few icons used by ../TreeWidget.py and its callers.
+
+	* AutoIndent.py: New version by Tim Peters improves block opening test.
+
+Fri May 21 04:46:17 1999  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* Attic/History.py, PyShell.py: Rename History to IdleHistory.
+	Add isatty() to pseudo files.
+
+	* StackViewer.py: Make initial stack viewer wider
+
+	* TODO.txt: New wishes
+
+	* AutoIndent.py, EditorWindow.py, PyShell.py:
+	Much improved autoindent and handling of tabs,
+	by Tim Peters.
+
+Mon May  3 15:49:52 1999  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* AutoIndent.py, EditorWindow.py, FormatParagraph.py, UndoDelegator.py:
+	Tim Peters writes:
+
+	I'm still unsure, but couldn't stand the virtual event trickery so tried a
+	different sin (adding undo_block_start/stop methods to the Text instance in
+	EditorWindow.py).  Like it or not, it's efficient and works <wink>.  Better
+	idea?
+
+	Give the attached a whirl.  Even if you hate the implementation, I think
+	you'll like the results.  Think I caught all the "block edit" cmds,
+	including Format Paragraph, plus subtler ones involving smart indents and
+	backspacing.
+
+	* WidgetRedirector.py: Tim Peters writes:
+
+	[W]hile trying to dope out how redirection works, stumbled into two
+	possible glitches.  In the first, it doesn't appear to make sense to try to
+	rename a command that's already been destroyed; in the second, the name
+	"previous" doesn't really bring to mind "ignore the previous value" <wink>.
+
+Fri Apr 30 19:39:25 1999  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* __init__.py: Support for using idle as a package.
+
+	* PathBrowser.py:
+	Avoid listing files more than once (e.g. foomodule.so has two hits:
+	once for foo + module.so, once for foomodule + .so).
+
+Mon Apr 26 22:20:38 1999  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* ChangeLog, ColorDelegator.py, PyShell.py: Tim Peters strikes again:
+
+	Ho ho ho -- that's trickier than it sounded!  The colorizer is working with
+	"line.col" strings instead of Text marks, and the absolute coordinates of
+	the point of interest can change across the self.update call (voice of
+	baffled experience, when two quick backspaces no longer fooled it, but a
+	backspace followed by a quick ENTER did <wink>).
+
+	Anyway, the attached appears to do the trick.  CPU usage goes way up when
+	typing quickly into a long triple-quoted string, but the latency is fine for
+	me (a relatively fast typist on a relatively slow machine).  Most of the
+	changes here are left over from reducing the # of vrbl names to help me
+	reason about the logic better; I hope the code is a *little* easier to
+
+Fri Apr 23 14:01:25 1999  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* EditorWindow.py:
+	Provide full arguments to __import__ so it works in packagized IDLE.
+
+Thu Apr 22 23:20:17 1999  Guido van Rossum  <guido@cnri.reston.va.us>
+
+        * help.txt:
+        Bunch of updates necessary due to recent changes; added docs for File
+        menu, command line and color preferences.
+
+        * Bindings.py: Remove obsolete 'script' menu.
+
+	* TODO.txt: Several wishes fulfilled.
+
+	* OutputWindow.py:
+	Moved classes OnDemandOutputWindow and PseudoFile here,
+	from ScriptBinding.py where they are no longer needed.
+
+	* ScriptBinding.py:
+	Mostly rewritten.  Instead of the old Run module and Debug module,
+	there are two new commands:
+
+	Import module (F5) imports or reloads the module and also adds its
+	name to the __main__ namespace.  This gets executed in the PyShell
+	window under control of its debug settings.
+
+	Run script (Control-F5) is similar but executes the contents of the
+	file directly in the __main__ namespace.
+
+	* PyShell.py: Nits: document use of $IDLESTARTUP; display idle version
+
+	* idlever.py: New version to celebrate new command line
+
+	* OutputWindow.py: Added flush(), for completeness.
+
+	* PyShell.py:
+	A lot of changes to make the command line more useful.  You can now do:
+	  idle.py -e file ...    -- to edit files
+	  idle.py script arg ... -- to run a script
+	  idle.py -c cmd arg ... -- to run a command
+	Other options, see also the usage message (also new!) for more details:
+	  -d       -- enable debugger
+	  -s       -- run $IDLESTARTUP or $PYTHONSTARTUP
+	  -t title -- set Python Shell window's title
+	sys.argv is set accordingly, unless -e is used.
+	sys.path is absolutized, and all relevant paths are inserted into it.
+
+	Other changes:
+	- the environment in which commands are executed is now the
+	  __main__ module
+	- explicitly save sys.stdout etc., don't restore from sys.__stdout__
+	- new interpreter methods execsource(), execfile(), stuffsource()
+	- a few small nits
+
+	* TODO.txt:
+	Some more TODO items.  Made up my mind about command line args,
+	Run/Import, __main__.
+
+	* ColorDelegator.py:
+	Super-elegant patch by Tim Peters that speeds up colorization
+	dramatically (up to 15 times he claims).  Works by reading more than
+	one line at a time, up to 100-line chunks (starting with one line and
+	then doubling up to the limit).  On a typical machine (e.g. Tim's
+	P5-166) this doesn't reduce interactive responsiveness in a noticeable
+	way.
+
+Wed Apr 21 15:49:34 1999  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* ColorDelegator.py:
+	Patch by Tim Peters to speed up colorizing of big multiline strings.
+
+Tue Apr 20 17:32:52 1999  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* extend.txt:
+	For an event 'foo-bar', the corresponding method must be called
+	foo_bar_event().  Therefore, fix the references to zoom_height() in
+	the example.
+
+	* IdlePrefs.py: Restored the original IDLE color scheme.
+
+	* PyShell.py, IdlePrefs.py, ColorDelegator.py, EditorWindow.py:
+	Color preferences code by Loren Luke (massaged by me somewhat)
+
+	* SearchEngine.py:
+	Patch by Mark Favas: it fixes the search engine behaviour where an
+	unsuccessful search wraps around and re-searches that part of the file
+	between the start of the search and the end of the file - only really
+	an issue for very large files, but... (also removes a redundant
+	m.span() call).
+
+Mon Apr 19 16:26:02 1999  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* TODO.txt: A few wishes are now fulfilled.
+
+	* AutoIndent.py: Tim Peters implements some of my wishes:
+
+	o Makes the tab key intelligently insert spaces when appropriate
+	(see Help list banter twixt David Ascher and me; idea stolen from
+	every other editor on earth <wink>).
+
+	o newline_and_indent_event trims trailing whitespace on the old
+	line (pymode and Codewright).
+
+	o newline_and_indent_event no longer fooled by trailing whitespace or
+	comment after ":" (pymode, PTUI).
+
+	o newline_and_indent_event now reduces the new line's indentation after
+	return, break, continue, raise and pass stmts (pymode).
+
+	The last two are easy to fool in the presence of strings &
+	continuations, but pymode requires Emacs's high-powered C parsing
+	functions to avoid that in finite time.
+
+======================================================================
+	Python release 1.5.2c1, IDLE version 0.4
+======================================================================
+
+Wed Apr  7 18:41:59 1999  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* README.txt, NEWS.txt: New version.
+
+	* idlever.py: Version bump awaiting impending new release.
+	(Not much has changed :-( )
+
+Mon Mar 29 14:52:28 1999  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* ScriptBinding.py, PyShell.py:
+	At Tim Peters' recommendation, add a dummy flush() method to
+	PseudoFile.
+
+Thu Mar 11 23:21:23 1999  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* PathBrowser.py: Don't crash when sys.path contains an empty string.
+
+	* Attic/Outline.py: This file was never supposed to be part of IDLE.
+
+	* PathBrowser.py:
+	- Don't crash in the case where a superclass is a string instead of a
+	pyclbr.Class object; this can happen when the superclass is
+	unrecognizable (to pyclbr), e.g. when module renaming is used.
+
+	- Show a watch cursor when calling pyclbr (since it may take a while
+	recursively parsing imported modules!).
+
+Wed Mar 10 05:18:02 1999  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* EditorWindow.py, Bindings.py: Add PathBrowser to File module
+
+	* PathBrowser.py: "Path browser" - 4 scrolled lists displaying:
+	    directories on sys.path
+	    modules in selected directory
+	    classes in selected module
+	    methods of selected class
+
+	Sinlge clicking in a directory, module or class item updates the next
+	column with info about the selected item.  Double clicking in a
+	module, class or method item opens the file (and selects the clicked
+	item if it is a class or method).
+
+	I guess eventually I should be using a tree widget for this, but the
+	ones I've seen don't work well enough, so for now I use the old
+	Smalltalk or NeXT style multi-column hierarchical browser.
+
+	* MultiScrolledLists.py:
+	New utility: multiple scrolled lists in parallel
+
+	* ScrolledList.py: - White background.
+	- Display "(None)" (or text of your choosing) when empty.
+	- Don't set the focus.
+
+======================================================================
+	Python release 1.5.2b2, IDLE version 0.3
+======================================================================
+
+Wed Feb 17 22:47:41 1999  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* NEWS.txt: News in 0.3.
+
+	* README.txt, idlever.py: Bump version to 0.3.
+
+	* EditorWindow.py:
+	After all, we don't need to call the callbacks ourselves!
+
+	* WindowList.py:
+	When deleting, call the callbacks *after* deleting the window from our list!
+
+	* EditorWindow.py:
+	Fix up the Windows menu via the new callback mechanism instead of
+	depending on menu post commands (which don't work when the menu is
+	torn off).
+
+	* WindowList.py:
+	Support callbacks to patch up Windows menus everywhere.
+
+	* ChangeLog: Oh, why not.  Checking in the Emacs-generated change log.
+
+Tue Feb 16 22:34:17 1999  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* ScriptBinding.py:
+	Only pop up the stack viewer when requested in the Debug menu.
+
+Mon Feb  8 22:27:49 1999  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* WindowList.py: Don't crash if a window no longer exists.
+
+	* TODO.txt: Restructured a bit.
+
+Mon Feb  1 23:06:17 1999  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* PyShell.py: Add current dir or paths of file args to sys.path.
+
+	* Debugger.py: Add canonic() function -- for brand new bdb.py feature.
+
+	* StackViewer.py: Protect against accessing an empty stack.
+
+Fri Jan 29 20:44:45 1999  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* ZoomHeight.py:
+	Use only the height to decide whether to zoom in or out.
+
+Thu Jan 28 22:24:30 1999  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* EditorWindow.py, FileList.py:
+	Make sure the Tcl variables are shared between windows.
+
+	* PyShell.py, EditorWindow.py, Bindings.py:
+	Move menu/key binding code from Bindings.py to EditorWindow.py,
+	with changed APIs -- it makes much more sense there.
+	Also add a new feature: if the first character of a menu label is
+	a '!', it gets a checkbox.  Checkboxes are bound to Boolean Tcl variables
+	that can be accessed through the new getvar/setvar/getrawvar API;
+	the variable is named after the event to which the menu is bound.
+
+	* Debugger.py: Add Quit button to the debugger window.
+
+	* SearchDialog.py:
+	When find_again() finds exactly the current selection, it's a failure.
+
+	* idle.py, Attic/idle: Rename idle -> idle.py
+
+Mon Jan 18 15:18:57 1999  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* EditorWindow.py, WindowList.py: Only deiconify when iconic.
+
+	* TODO.txt: Misc
+
+Tue Jan 12 22:14:34 1999  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* testcode.py, Attic/test.py:
+	Renamed test.py to testcode.py so one can import Python's
+	test package from inside IDLE.  (Suggested by Jack Jansen.)
+
+	* EditorWindow.py, ColorDelegator.py:
+	Hack to close a window that is colorizing.
+
+	* Separator.py: Vladimir Marangozov's patch:
+	The separator dances too much and seems to jump by arbitrary amounts
+	in arbitrary directions when I try to move it for resizing the frames.
+	This patch makes it more quiet.
+
+Mon Jan 11 14:52:40 1999  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* TODO.txt: Some requests have been fulfilled.
+
+	* EditorWindow.py:
+	Set the cursor to a watch when opening the class browser (which may
+	take quite a while, browsing multiple files).
+
+	Newer, better center() -- but assumes no wrapping.
+
+	* SearchBinding.py:
+	Got rid of debug print statement in goto_line_event().
+
+	* ScriptBinding.py:
+	I think I like it better if it prints the traceback even when it displays
+	the stack viewer.
+
+	* Debugger.py: Bind ESC to close-window.
+
+	* ClassBrowser.py: Use a HSeparator between the classes and the items.
+	Make the list of classes wider by default (40 chars).
+	Bind ESC to close-window.
+
+	* Separator.py:
+	Separator classes (draggable divider between two panes).
+
+Sat Jan  9 22:01:33 1999  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* WindowList.py:
+	Don't traceback when wakeup() is called when the window has been destroyed.
+	This can happen when a torn-of Windows menu references closed windows.
+	And Tim Peters claims that the Windows menu is his favorite to tear off...
+
+	* EditorWindow.py: Allow tearing off of the Windows menu.
+
+	* StackViewer.py: Close on ESC.
+
+	* help.txt: Updated a bunch of things (it was mostly still 0.1!)
+
+	* extend.py: Added ScriptBinding to standard bindings.
+
+	* ScriptBinding.py:
+	This now actually works.  See doc string.  It can run a module (i.e.
+	import or reload) or debug it (same with debugger control).  Output
+	goes to a fresh output window, only created when needed.
+
+======================================================================
+	Python release 1.5.2b1, IDLE version 0.2
+======================================================================
+
+Fri Jan  8 17:26:02 1999  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* README.txt, NEWS.txt: What's new in this release.
+
+	* Bindings.py, PyShell.py:
+	Paul Prescod's patches to allow the stack viewer to pop up when a
+	traceback is printed.
+
+Thu Jan  7 00:12:15 1999  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* FormatParagraph.py:
+	Change paragraph width limit to 70 (like Emacs M-Q).
+
+	* README.txt:
+	Separating TODO from README.  Slight reformulation of features.  No
+	exact release date.
+
+	* TODO.txt: Separating TODO from README.
+
+Mon Jan  4 21:19:09 1999  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* FormatParagraph.py:
+	Hm.  There was a boundary condition error at the end of the file too.
+
+	* SearchBinding.py: Hm.  Add Unix binding for replace, too.
+
+	* keydefs.py: Ran eventparse.py again.
+
+	* FormatParagraph.py: Added Unix Meta-q key binding;
+	fix find_paragraph when at start of file.
+
+	* AutoExpand.py: Added Meta-/ binding for Unix as alt for Alt-/.
+
+	* SearchBinding.py:
+	Add unix binding for grep (otherwise the menu entry doesn't work!)
+
+	* ZoomHeight.py: Adjusted Unix height to work with fvwm96. :=(
+
+	* GrepDialog.py: Need to import sys!
+
+	* help.txt, extend.txt, README.txt: Formatted some paragraphs
+
+	* extend.py, FormatParagraph.py:
+	Add new extension to reformat a (text) paragraph.
+
+	* ZoomHeight.py: Typo in Win specific height setting.
+
+Sun Jan  3 00:47:35 1999  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* AutoIndent.py: Added something like Tim Peters' backspace patch.
+
+	* ZoomHeight.py: Adapted to Unix (i.e., more hardcoded constants).
+
+Sat Jan  2 21:28:54 1999  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* keydefs.py, idlever.py, idle.pyw, idle.bat, help.txt, extend.txt, extend.py, eventparse.py, ZoomHeight.py, WindowList.py, UndoDelegator.py, StackViewer.py, SearchEngine.py, SearchDialogBase.py, SearchDialog.py, ScrolledList.py, SearchBinding.py, ScriptBinding.py, ReplaceDialog.py, Attic/README, README.txt, PyShell.py, Attic/PopupMenu.py, OutputWindow.py, IOBinding.py, Attic/HelpWindow.py, History.py, GrepDialog.py, FileList.py, FrameViewer.py, EditorWindow.py, Debugger.py, Delegator.py, ColorDelegator.py, Bindings.py, ClassBrowser.py, AutoExpand.py, AutoIndent.py:
+	Checking in IDLE 0.2.
+
+	Much has changed -- too much, in fact, to write down.
+	The big news is that there's a standard way to write IDLE extensions;
+	see extend.txt.  Some sample extensions have been provided, and
+	some existing code has been converted to extensions.  Probably the
+	biggest new user feature is a new search dialog with more options,
+	search and replace, and even search in files (grep).
+
+	This is exactly as downloaded from my laptop after returning
+	from the holidays -- it hasn't even been tested on Unix yet.
+
+Fri Dec 18 15:52:54 1998  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* FileList.py, ClassBrowser.py:
+	Fix the class browser to work even when the file is not on sys.path.
+
+Tue Dec  8 20:39:36 1998  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* Attic/turtle.py: Moved to Python 1.5.2/Lib
+
+Fri Nov 27 03:19:20 1998  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* help.txt: Typo
+
+	* EditorWindow.py, FileList.py: Support underlining of menu labels
+
+	* Bindings.py:
+	New approach, separate tables for menus (platform-independent) and key
+	definitions (platform-specific), and generating accelerator strings
+	automatically from the key definitions.
+
+Mon Nov 16 18:37:42 1998  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* Attic/README: Clarify portability and main program.
+
+	* Attic/README: Added intro for 0.1 release and append Grail notes.
+
+Mon Oct 26 18:49:00 1998  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* Attic/turtle.py: root is now a global called _root
+
+Sat Oct 24 16:38:38 1998  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* Attic/turtle.py: Raise the root window on reset().
+	Different action on WM_DELETE_WINDOW is more likely to do the right thing,
+	allowing us to destroy old windows.
+
+	* Attic/turtle.py:
+	Split the goto() function in two: _goto() is the internal one,
+	using Canvas coordinates, and goto() uses turtle coordinates
+	and accepts variable argument lists.
+
+	* Attic/turtle.py: Cope with destruction of the window
+
+	* Attic/turtle.py: Turtle graphics
+
+	* Debugger.py: Use of Breakpoint class should be bdb.Breakpoint.
+
+Mon Oct 19 03:33:40 1998  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* SearchBinding.py:
+	Speed up the search a bit -- don't drag a mark around...
+
+	* PyShell.py:
+	Change our special entries from <console#N> to <pyshell#N>.
+	Patch linecache.checkcache() to keep our special entries alive.
+	Add popup menu to all editor windows to set a breakpoint.
+
+	* Debugger.py:
+	Use and pass through the 'force' flag to set_dict() where appropriate.
+	Default source and globals checkboxes to false.
+	Don't interact in user_return().
+	Add primitive set_breakpoint() method.
+
+	* ColorDelegator.py:
+	Raise priority of 'sel' tag so its foreground (on Windows) will take
+	priority over text colorization (which on Windows is almost the
+	same color as the selection background).
+
+	Define a tag and color for breakpoints ("BREAK").
+
+	* Attic/PopupMenu.py: Disable "Open stack viewer" and "help" commands.
+
+	* StackViewer.py:
+	Add optional 'force' argument (default 0) to load_dict().
+	If set, redo the display even if it's the same dict.
+
+Fri Oct 16 21:10:12 1998  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* StackViewer.py: Do nothing when loading the same dict as before.
+
+	* PyShell.py: Details for debugger interface.
+
+	* Debugger.py:
+	Restructured and more consistent.  Save checkboxes across instantiations.
+
+	* EditorWindow.py, Attic/README, Bindings.py:
+	Get rid of conflicting ^X binding.  Use ^W.
+
+	* Debugger.py, StackViewer.py:
+	Debugger can now show local and global variables.
+
+	* Debugger.py: Oops
+
+	* Debugger.py, PyShell.py: Better debugger support (show stack etc).
+
+	* Attic/PopupMenu.py: Follow renames in StackViewer module
+
+	* StackViewer.py:
+	Rename classes to StackViewer (the widget) and StackBrowser (the toplevel).
+
+	* ScrolledList.py: Add close() method
+
+	* EditorWindow.py: Clarify 'Open Module' dialog text
+
+	* StackViewer.py: Restructured into a browser and a widget.
+
+Thu Oct 15 23:27:08 1998  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* ClassBrowser.py, ScrolledList.py:
+	Generalized the scrolled list which is the base for the class and
+	method browser into a separate class in its own module.
+
+	* Attic/test.py: Cosmetic change
+
+	* Debugger.py: Don't show function name if there is none
+
+Wed Oct 14 03:43:05 1998  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* Debugger.py, PyShell.py: Polish the Debugger GUI a bit.
+	Closing it now also does the right thing.
+
+Tue Oct 13 23:51:13 1998  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* Debugger.py, PyShell.py, Bindings.py:
+	Ad primitive debugger interface (so far it will step and show you the
+	source, but it doesn't yet show the stack).
+
+	* Attic/README: Misc
+
+	* StackViewer.py: Whoops -- referenced self.top before it was set.
+
+	* help.txt: Added history and completion commands.
+
+	* help.txt: Updated
+
+	* FileList.py: Add class browser functionality.
+
+	* StackViewer.py:
+	Add a close() method and bind to WM_DELETE_WINDOW protocol
+
+	* PyShell.py: Clear the linecache before printing a traceback
+
+	* Bindings.py: Added class browser binding.
+
+	* ClassBrowser.py: Much improved, much left to do.
+
+	* PyShell.py: Make the return key do what I mean more often.
+
+	* ClassBrowser.py:
+	Adding the beginnings of a Class browser.  Incomplete, yet.
+
+	* EditorWindow.py, Bindings.py:
+	Add new command, "Open module".  You select or type a module name,
+	and it opens the source.
+
+Mon Oct 12 23:59:27 1998  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* PyShell.py: Subsume functionality from Popup menu in Debug menu.
+	Other stuff so the PyShell window can be resurrected from the Windows menu.
+
+	* FileList.py: Get rid of PopUp menu.
+	Create a simple Windows menu.  (Imperfect when Untitled windows exist.)
+	Add wakeup() method: deiconify, raise, focus.
+
+	* EditorWindow.py: Generalize menu creation.
+
+	* Bindings.py: Add Debug and Help menu items.
+
+	* EditorWindow.py: Added a menu bar to every window.
+
+	* Bindings.py: Add menu configuration to the event configuration.
+
+	* Attic/PopupMenu.py: Pass a root to the help window.
+
+	* SearchBinding.py:
+	Add parent argument to 'go to line number' dialog box.
+
+Sat Oct 10 19:15:32 1998  Guido van Rossum  <guido@cnri.reston.va.us>
+
+	* StackViewer.py:
+	Add a label at the top showing (very basic) help for the stack viewer.
+	Add a label at the bottom showing the exception info.
+
+	* Attic/test.py, Attic/idle: Add Unix main script and test program.
+
+	* idle.pyw, help.txt, WidgetRedirector.py, UndoDelegator.py, StackViewer.py, SearchBinding.py, Attic/README, PyShell.py, Attic/PopupMenu.py, Percolator.py, Outline.py, IOBinding.py, History.py, Attic/HelpWindow.py, FrameViewer.py, FileList.py, EditorWindow.py, Delegator.py, ColorDelegator.py, Bindings.py, AutoIndent.py, AutoExpand.py:
+	Initial checking of Tk-based Python IDE.
+	Features: text editor with syntax coloring and undo;
+	subclassed into interactive Python shell which adds history.
+
diff --git a/rootfs/usr/lib/python3.8/idlelib/HISTORY.txt b/rootfs/usr/lib/python3.8/idlelib/HISTORY.txt
new file mode 100644
index 0000000..731fabd
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/HISTORY.txt
@@ -0,0 +1,296 @@
+IDLE History
+============
+
+This file contains the release messages for previous IDLE releases.
+As you read on you go back to the dark ages of IDLE's history.
+
+
+What's New in IDLEfork 0.8.1?
+=============================
+
+*Release date: 22-Jul-2001*
+
+- New tarball released as a result of the 'revitalisation' of the IDLEfork
+  project.
+
+- This release requires python 2.1 or better. Compatibility with earlier
+  versions of python (especially ancient ones like 1.5x) is no longer a
+  priority in IDLEfork development.
+
+- This release is based on a merging of the earlier IDLE fork work with current
+  cvs IDLE (post IDLE version 0.8), with some minor additional coding by Kurt
+  B. Kaiser and Stephen M. Gava.
+
+- This release is basically functional but also contains some known breakages,
+  particularly with running things from the shell window. Also the debugger is
+  not working, but I believe this was the case with the previous IDLE fork
+  release (0.7.1) as well.
+
+- This release is being made now to mark the point at which IDLEfork is
+  launching into a new stage of development.
+
+- IDLEfork CVS will now be branched to enable further development and
+  exploration of the two "execution in a remote process" patches submitted by
+  David Scherer (David's is currently in IDLEfork) and GvR, while stabilisation
+  and development of less heavyweight improvements (like user customisation)
+  can continue on the trunk.
+
+
+What's New in IDLEfork 0.7.1?
+==============================
+
+*Release date: 15-Aug-2000*
+
+- First project tarball released.
+
+- This was the first release of IDLE fork, which at this stage was a
+  combination of IDLE 0.5 and the VPython idle fork, with additional changes
+  coded by David Scherer, Peter Schneider-Kamp and Nicholas Riley.
+
+
+
+IDLEfork 0.7.1 - 29 May 2000
+-----------------------------
+
+   David Scherer  <dscherer@cmu.edu>
+
+- This is a modification of the CVS version of IDLE 0.5, updated as of
+  2000-03-09.  It is alpha software and might be unstable.  If it breaks, you
+  get to keep both pieces.
+
+- If you have problems or suggestions, you should either contact me or post to
+  the list at http://www.python.org/mailman/listinfo/idle-dev (making it clear
+  that you are using this modified version of IDLE).
+
+- Changes:
+
+  - The ExecBinding module, a replacement for ScriptBinding, executes programs
+    in a separate process, piping standard I/O through an RPC mechanism to an
+    OnDemandOutputWindow in IDLE.  It supports executing unnamed programs
+    (through a temporary file).  It does not yet support debugging.
+
+  - When running programs with ExecBinding, tracebacks will be clipped to
+    exclude system modules.  If, however, a system module calls back into the
+    user program, that part of the traceback will be shown.
+
+  - The OnDemandOutputWindow class has been improved.  In particular, it now
+    supports a readline() function used to implement user input, and a
+    scroll_clear() operation which is used to hide the output of a previous run
+    by scrolling it out of the window.
+
+  - Startup behavior has been changed.  By default IDLE starts up with just a
+    blank editor window, rather than an interactive window.  Opening a file in
+    such a blank window replaces the (nonexistent) contents of that window
+    instead of creating another window.  Because of the need to have a
+    well-known port for the ExecBinding protocol, only one copy of IDLE can be
+    running.  Additional invocations use the RPC mechanism to report their
+    command line arguments to the copy already running.
+
+  - The menus have been reorganized.  In particular, the excessively large
+    'edit' menu has been split up into 'edit', 'format', and 'run'.
+
+  - 'Python Documentation' now works on Windows, if the win32api module is
+    present.
+
+  - A few key bindings have been changed: F1 now loads Python Documentation
+    instead of the IDLE help; shift-TAB is now a synonym for unindent.
+
+- New modules:
+
+  ExecBinding.py         Executes program through loader
+  loader.py              Bootstraps user program
+  protocol.py            RPC protocol
+  Remote.py              User-process interpreter
+  spawn.py               OS-specific code to start programs
+
+- Files modified:
+
+  autoindent.py          ( bindings tweaked )
+  bindings.py            ( menus reorganized )
+  config.txt             ( execbinding enabled )
+  editorwindow.py        ( new menus, fixed 'Python Documentation' )
+  filelist.py            ( hook for "open in same window" )
+  formatparagraph.py     ( bindings tweaked )
+  idle.bat               ( removed absolute pathname )
+  idle.pyw               ( weird bug due to import with same name? )
+  iobinding.py           ( open in same window, EOL convention )
+  keydefs.py             ( bindings tweaked )
+  outputwindow.py        ( readline, scroll_clear, etc )
+  pyshell.py             ( changed startup behavior )
+  readme.txt             ( <Recursion on file with id=1234567> )
+
+
+
+IDLE 0.5 - February 2000 - Release Notes
+----------------------------------------
+
+This is an early release of IDLE, my own attempt at a Tkinter-based
+IDE for Python.
+
+(For a more detailed change log, see the file ChangeLog.)
+
+FEATURES
+
+IDLE has the following features:
+
+- coded in 100% pure Python, using the Tkinter GUI toolkit (i.e. Tcl/Tk)
+
+- cross-platform: works on Windows and Unix (on the Mac, there are
+currently problems with Tcl/Tk)
+
+- multi-window text editor with multiple undo, Python colorizing
+and many other features, e.g. smart indent and call tips
+
+- Python shell window (a.k.a. interactive interpreter)
+
+- debugger (not complete, but you can set breakpoints, view  and step)
+
+USAGE
+
+The main program is in the file "idle.py"; on Unix, you should be able
+to run it by typing "./idle.py" to your shell.  On Windows, you can
+run it by double-clicking it; you can use idle.pyw to avoid popping up
+a DOS console.  If you want to pass command line arguments on Windows,
+use the batch file idle.bat.
+
+Command line arguments: files passed on the command line are executed,
+not opened for editing, unless you give the -e command line option.
+Try "./idle.py -h" to see other command line options.
+
+IDLE requires Python 1.5.2, so it is currently only usable with a
+Python 1.5.2 distribution.  (An older version of IDLE is distributed
+with Python 1.5.2; you can drop this version on top of it.)
+
+COPYRIGHT
+
+IDLE is covered by the standard Python copyright notice
+(http://www.python.org/doc/Copyright.html).
+
+
+New in IDLE 0.5 (2/15/2000)
+---------------------------
+
+Tons of stuff, much of it contributed by Tim Peters and Mark Hammond:
+
+- Status bar, displaying current line/column (Moshe Zadka).
+
+- Better stack viewer, using tree widget.  (XXX Only used by Stack
+Viewer menu, not by the debugger.)
+
+- Format paragraph now recognizes Python block comments and reformats
+them correctly (MH)
+
+- New version of pyclbr.py parses top-level functions and understands
+much more of Python's syntax; this is reflected in the class and path
+browsers (TP)
+
+- Much better auto-indent; knows how to indent the insides of
+multi-line statements (TP)
+
+- Call tip window pops up when you type the name of a known function
+followed by an open parenthesis.  Hit ESC or click elsewhere in the
+window to close the tip window (MH)
+
+- Comment out region now inserts ## to make it stand out more (TP)
+
+- New path and class browsers based on a tree widget that looks
+familiar to Windows users
+
+- Reworked script running commands to be more intuitive: I/O now
+always goes to the *Python Shell* window, and raw_input() works
+correctly.  You use F5 to import/reload a module: this adds the module
+name to the __main__ namespace.  You use Control-F5 to run a script:
+this runs the script *in* the __main__ namespace.  The latter also
+sets sys.argv[] to the script name
+
+
+New in IDLE 0.4 (4/7/99)
+------------------------
+
+Most important change: a new menu entry "File -> Path browser", shows
+a 4-column hierarchical browser which lets you browse sys.path,
+directories, modules, and classes.  Yes, it's a superset of the Class
+browser menu entry.  There's also a new internal module,
+MultiScrolledLists.py, which provides the framework for this dialog.
+
+
+New in IDLE 0.3 (2/17/99)
+-------------------------
+
+Most important changes:
+
+- Enabled support for running a module, with or without the debugger.
+Output goes to a new window.  Pressing F5 in a module is effectively a
+reload of that module; Control-F5 loads it under the debugger.
+
+- Re-enable tearing off the Windows menu, and make a torn-off Windows
+menu update itself whenever a window is opened or closed.
+
+- Menu items can now be have a checkbox (when the menu label starts
+with "!"); use this for the Debugger and "Auto-open stack viewer"
+(was: JIT stack viewer) menu items.
+
+- Added a Quit button to the Debugger API.
+
+- The current directory is explicitly inserted into sys.path.
+
+- Fix the debugger (when using Python 1.5.2b2) to use canonical
+filenames for breakpoints, so these actually work.  (There's still a
+lot of work to be done to the management of breakpoints in the
+debugger though.)
+
+- Closing a window that is still colorizing now actually works.
+
+- Allow dragging of the separator between the two list boxes in the
+class browser.
+
+- Bind ESC to "close window" of the debugger, stack viewer and class
+browser.  It removes the selection highlighting in regular text
+windows.  (These are standard Windows conventions.)
+
+
+New in IDLE 0.2 (1/8/99)
+------------------------
+
+Lots of changes; here are the highlights:
+
+General:
+
+- You can now write and configure your own IDLE extension modules; see
+extend.txt.
+
+
+File menu:
+
+The command to open the Python shell window is now in the File menu.
+
+
+Edit menu:
+
+New Find dialog with more options; replace dialog; find in files dialog.
+
+Commands to tabify or untabify a region.
+
+Command to format a paragraph.
+
+
+Debug menu:
+
+JIT (Just-In-Time) stack viewer toggle -- if set, the stack viewer
+automaticall pops up when you get a traceback.
+
+Windows menu:
+
+Zoom height -- make the window full height.
+
+
+Help menu:
+
+The help text now show up in a regular window so you can search and
+even edit it if you like.
+
+
+
+IDLE 0.1 was distributed with the Python 1.5.2b1 release on 12/22/98.
+
+======================================================================
diff --git a/rootfs/usr/lib/python3.8/idlelib/Icons/README.txt b/rootfs/usr/lib/python3.8/idlelib/Icons/README.txt
new file mode 100644
index 0000000..d91c4d5
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/Icons/README.txt
@@ -0,0 +1,13 @@
+The IDLE icons are from https://bugs.python.org/issue1490384
+
+Created by Andrew Clover.
+
+The original sources are available from Andrew's website:
+https://www.doxdesk.com/software/py/pyicons.html
+
+Various different formats and sizes are available at this GitHub Pull Request:
+https://github.com/python/cpython/pull/17473
+
+The idle.ico file was created with ImageMagick:
+
+    $ convert idle_16.png idle_32.png idle_48.png idle_256.png idle.ico
diff --git a/rootfs/usr/lib/python3.8/idlelib/Icons/folder.gif b/rootfs/usr/lib/python3.8/idlelib/Icons/folder.gif
new file mode 100644
index 0000000..effe8dc
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/Icons/folder.gif
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/Icons/idle.ico b/rootfs/usr/lib/python3.8/idlelib/Icons/idle.ico
new file mode 100644
index 0000000..2aa9a83
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/Icons/idle.ico
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/Icons/idle_16.gif b/rootfs/usr/lib/python3.8/idlelib/Icons/idle_16.gif
new file mode 100644
index 0000000..9f001b1
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/Icons/idle_16.gif
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/Icons/idle_16.png b/rootfs/usr/lib/python3.8/idlelib/Icons/idle_16.png
new file mode 100644
index 0000000..6abde0a
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/Icons/idle_16.png
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/Icons/idle_256.png b/rootfs/usr/lib/python3.8/idlelib/Icons/idle_256.png
new file mode 100644
index 0000000..99ffa6f
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/Icons/idle_256.png
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/Icons/idle_32.gif b/rootfs/usr/lib/python3.8/idlelib/Icons/idle_32.gif
new file mode 100644
index 0000000..af5b2d5
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/Icons/idle_32.gif
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/Icons/idle_32.png b/rootfs/usr/lib/python3.8/idlelib/Icons/idle_32.png
new file mode 100644
index 0000000..41b70db
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/Icons/idle_32.png
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/Icons/idle_48.gif b/rootfs/usr/lib/python3.8/idlelib/Icons/idle_48.gif
new file mode 100644
index 0000000..fc5304f
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/Icons/idle_48.gif
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/Icons/idle_48.png b/rootfs/usr/lib/python3.8/idlelib/Icons/idle_48.png
new file mode 100644
index 0000000..e5fa928
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/Icons/idle_48.png
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/Icons/minusnode.gif b/rootfs/usr/lib/python3.8/idlelib/Icons/minusnode.gif
new file mode 100644
index 0000000..c72e46f
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/Icons/minusnode.gif
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/Icons/openfolder.gif b/rootfs/usr/lib/python3.8/idlelib/Icons/openfolder.gif
new file mode 100644
index 0000000..24aea1b
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/Icons/openfolder.gif
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/Icons/plusnode.gif b/rootfs/usr/lib/python3.8/idlelib/Icons/plusnode.gif
new file mode 100644
index 0000000..13ace90
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/Icons/plusnode.gif
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/Icons/python.gif b/rootfs/usr/lib/python3.8/idlelib/Icons/python.gif
new file mode 100644
index 0000000..b189c2c
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/Icons/python.gif
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/Icons/tk.gif b/rootfs/usr/lib/python3.8/idlelib/Icons/tk.gif
new file mode 100644
index 0000000..a603f5e
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/Icons/tk.gif
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/NEWS.txt b/rootfs/usr/lib/python3.8/idlelib/NEWS.txt
new file mode 100644
index 0000000..1764bb4
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/NEWS.txt
@@ -0,0 +1,1256 @@
+What's New in IDLE 3.8.9
+Released on 2021-05-03?
+=========================
+
+
+bpo-43283: Document why printing to IDLE's Shell is often slower than
+printing to a system terminal and that it can be made faster by
+pre-formatting a single string before printing.
+
+What's New in IDLE 3.8.8
+Released on 2021-02-19
+======================================
+
+bpo-23544: Disable Debug=>Stack Viewer when user code is running or
+Debugger is active, to prevent hang or crash.  Patch by Zackery Spytz.
+
+bpo-43008: Make IDLE invoke :func:`sys.excepthook` in normal,
+2-process mode.  Patch by Ken Hilton.
+
+bpo-33065: Fix problem debugging user classes with __repr__ method.
+
+bpo-32631: Finish zzdummy example extension module: make menu entries
+work; add docstrings and tests with 100% coverage.
+
+bpo-42508: Keep IDLE running on macOS.  Remove obsolete workaround
+that prevented running files with shortcuts when using new universal2
+installers built on macOS 11.
+
+What's New in IDLE 3.8.7
+Released on 2020-12-27
+======================================
+
+bpo-42426: Fix reporting offset of the RE error in searchengine.
+
+bpo-42416: Get docstrings for IDLE calltips more often
+by using inspect.getdoc.
+
+bpo-33987: Mostly finish using ttk widgets, mainly for editor,
+settings, and searches.  Some patches by Mark Roseman.
+
+bpo-41775: Make 'IDLE Shell' the shell title.
+
+bpo-35764: Rewrite the Calltips doc section.
+
+bpo-40181: In calltips, stop reminding that '/' marks the end of
+positional-only arguments.
+
+
+What's New in IDLE 3.8.6
+Released on 2020-09-??
+======================================
+
+bpo-41468: Improve IDLE run crash error message (which users should
+never see).
+
+bpo-41373: Save files loaded with no line ending, as when blank, or
+different line endings, by setting its line ending to the system
+default. Fix regression in 3.8.4 and 3.9.0b4.
+
+
+What's New in IDLE 3.8.5
+Released on 2020-07-20
+======================================
+
+bpo-41300: Save files with non-ascii chars.  Fix regression in
+3.9.0b4 and 3.8.4.
+
+
+What's New in IDLE 3.8.4
+Released on 2020-06-30
+======================================
+
+bpo-37765: Add keywords to module name completion list.  Rewrite
+Completions section of IDLE doc.
+
+bpo-41152: The encoding of ``stdin``, ``stdout`` and ``stderr`` in IDLE
+is now always UTF-8.
+
+bpo-41144: Make Open Module open a special module such as os.path.
+
+bpo-40723: Make test_idle pass when run after import.
+Patch by Florian Dahlitz.
+
+
+What's New in IDLE 3.8.3
+Released on 2020-05-13
+======================================
+
+bpo-38689: IDLE will no longer freeze when inspect.signature fails
+when fetching a calltip.
+
+bpo-27115: For 'Go to Line', use a Query entry box subclass with
+IDLE standard behavior and improved error checking.
+
+bpo-39885: When a context menu is invoked by right-clicking outside
+of a selection, clear the selection and move the cursor.  Cut and
+Copy require that the click be within the selection.
+
+bpo-39852: Edit "Go to line" now clears any selection, preventing
+accidental deletion.  It also updates Ln and Col on the status bar.
+
+bpo-39781: Selecting code context lines no longer causes a jump.
+
+bpo-39663: Add tests for pyparse find_good_parse_start().
+
+
+What's New in IDLE 3.8.2
+Released on 2020-02-17
+======================================
+
+bpo-39600: Remove duplicate font names from configuration list.
+
+bpo-38792: Close a shell calltip if a :exc:`KeyboardInterrupt`
+or shell restart occurs.  Patch by Zackery Spytz.
+
+bpo-30780: Add remaining configdialog tests for buttons and
+highlights and keys tabs.
+
+bpo-39388: Settings dialog Cancel button cancels pending changes.
+
+bpo-39050: Settings dialog Help button again displays help text.
+
+bpo-32989: Add tests for editor newline_and_indent_event method.
+Remove unneeded arguments and dead code from pyparse
+find_good_parse_start method.
+
+
+What's New in IDLE 3.8.1
+Released on 2019-12-18
+======================================
+
+bpo-38943: Fix autocomplete windows not always appearing on some
+systems.  Patch by Johnny Najera.
+
+bpo-38944: Escape key now closes IDLE completion windows.  Patch by
+Johnny Najera.
+
+bpo-38862: 'Strip Trailing Whitespace' on the Format menu removes extra
+newlines at the end of non-shell files.
+
+bpo-38636: Fix IDLE Format menu tab toggle and file indent width. These
+functions (default shortcuts Alt-T and Alt-U) were mistakenly disabled
+in 3.7.5 and 3.8.0.
+
+bpo-4630: Add an option to toggle IDLE's cursor blink for shell,
+editor, and output windows.  See Settings, General, Window Preferences,
+Cursor Blink.  Patch by Zackery Spytz.
+
+bpo-26353: Stop adding newline when saving an IDLE shell window.
+
+bpo-38598: Do not try to compile IDLE shell or output windows.
+
+
+What's New in IDLE 3.8.0 (since 3.7.0)
+Released on 2019-10-14
+======================================
+
+bpo-36698: IDLE no longer fails when writing non-encodable characters
+to stderr.  It now escapes them with a backslash, like the regular
+Python interpreter.  Add an errors field to the standard streams.
+
+bpo-13153: Improve tkinter's handing of non-BMP (astral) unicode
+characters, such as 'rocket \U0001f680'.  Whether a proper glyph or
+replacement char is displayed depends on the OS and font.  For IDLE,
+astral chars in code interfere with editing.
+
+bpo-35379: When exiting IDLE, catch any AttributeError.  One happens
+when EditorWindow.close is called twice.  Printing a traceback, when
+IDLE is run from a terminal, is useless and annoying.
+
+bpo-38183: To avoid test issues, test_idle ignores the user config
+directory.  It no longer tries to create or access .idlerc or any files
+within.  Users must run IDLE to discover problems with saving settings.
+
+bpo-38077: IDLE no longer adds 'argv' to the user namespace when
+initializing it.  This bug only affected 3.7.4 and 3.8.0b2 to 3.8.0b4.
+
+bpo-38401: Shell restart lines now fill the window width, always start
+with '=', and avoid wrapping unnecessarily. The line will still wrap
+if the included file name is long relative to the width.
+
+bpo-37092: Add mousewheel scrolling for IDLE module, path, and stack
+browsers.  Patch by George Zhang.
+
+bpo-35771: To avoid occasional spurious test_idle failures on slower
+machines, increase the ``hover_delay`` in test_tooltip.
+
+bpo-37824: Properly handle user input warnings in IDLE shell.
+Cease turning SyntaxWarnings into SyntaxErrors.
+
+bpo-37929: IDLE Settings dialog now closes properly when there is no
+shell window.
+
+bpo-37849: Fix completions list appearing too high or low when shown
+above the current line.
+
+bpo-36419: Refactor autocompete and improve testing.
+
+bpo-37748: Reorder the Run menu.  Put the most common choice,
+Run Module, at the top.
+
+bpo-37692: Improve highlight config sample with example shell
+interaction and better labels for shell elements.
+
+bpo-37628: Settings dialog no longer expands with font size.
+The font and highlight sample boxes gain scrollbars instead.
+
+bpo-17535: Add optional line numbers for IDLE editor windows.
+
+bpo-37627: Initialize the Customize Run dialog with the command line
+arguments most recently entered before.  The user can optionally edit
+before submitting them.
+
+bpo-33610: Code context always shows the correct context when toggled on.
+
+bpo-36390: Gather Format menu functions into format.py.  Combine
+paragraph.py, rstrip.py, and format methods from editor.py.
+
+bpo-37530: Optimize code context to reduce unneeded background activity.
+Font and highlight changes now occur along with text changes instead
+of after a random delay.
+
+bpo-27452: Cleanup config.py by inlining RemoveFile and simplifying
+the handling of __file__ in CreateConfigHandlers/
+
+bpo-26806: To compensate for stack frames added by IDLE and avoid
+possible problems with low recursion limits, add 30 to limits in the
+user code execution process.  Subtract 30 when reporting recursion
+limits to make this addition mostly transparent.
+
+bpo-37325: Fix tab focus traversal order for help source and custom
+run dialogs.
+
+bpo-37321: Both subprocess connection error messages now refer to
+the 'Startup failure' section of the IDLE doc.
+
+bpo-37177: Properly attach search dialogs to their main window so
+that they behave like other dialogs and do not get hidden behind
+their main window.
+
+bpo-37039: Adjust "Zoom Height" to individual screens by momentarily
+maximizing the window on first use with a particular screen.  Changing
+screen settings may invalidate the saved height.  While a window is
+maximized, "Zoom Height" has no effect.
+
+bpo-35763: Make calltip reminder about '/' meaning positional-only less
+obtrusive by only adding it when there is room on the first line.
+
+bpo-5680: Add 'Run Customized' to the Run menu to run a module with
+customized settings. Any command line arguments entered are added
+to sys.argv. One can suppress the normal Shell main module restart.
+
+bpo-35610: Replace now redundant editor.context_use_ps1 with
+.prompt_last_line.  This finishes change started in bpo-31858.
+
+bpo-32411: Stop sorting dict created with desired line order.
+
+bpo-37038: Make idlelib.run runnable; add test clause.
+
+bpo-36958: Print any argument other than None or int passed to
+SystemExit or sys.exit().
+
+bpo-36807: When saving a file, call file.flush() and os.fsync()
+so bits are flushed to e.g. a USB drive.
+
+bpo-36429: Fix starting IDLE with pyshell.
+Add idlelib.pyshell alias at top; remove pyshell alias at bottom.
+Remove obsolete __name__=='__main__' command.
+
+bpo-30348: Increase test coverage of idlelib.autocomplete by 30%.
+Patch by Louie Lu.
+
+bpo-23205: Add tests and refactor grep's findfiles.
+
+bpo-36405: Use dict unpacking in idlelib.
+
+bpo-36396: Remove fgBg param of idlelib.config.GetHighlight().
+This param was only used twice and changed the return type.
+
+bpo-23216: IDLE: Add docstrings to search modules.
+
+bpo-36176: Fix IDLE autocomplete & calltip popup colors.
+Prevent conflicts with Linux dark themes
+(and slightly darken calltip background).
+
+bpo-36152: Remove colorizer.ColorDelegator.close_when_done and the
+corresponding argument of .close().  In IDLE, both have always been
+None or False since 2007.
+
+bpo-36096: Make colorizer state variables instance-only.
+
+bpo-32129: Avoid blurry IDLE application icon on macOS with Tk 8.6.
+Patch by Kevin Walzer.
+
+bpo-24310: Document settings dialog font tab sample.
+
+bpo-35689: Add docstrings and tests for colorizer.
+
+bpo-35833: Revise IDLE doc for control codes sent to Shell.
+Add a code example block.
+
+bpo-35770: IDLE macosx deletes Options => Configure IDLE.
+It previously deleted Window => Zoom Height by mistake.
+(Zoom Height is now on the Options menu).  On Mac, the settings
+dialog is accessed via Preferences on the IDLE menu.
+
+bpo-35769: Change new file name from 'Untitled' to 'untitled'.
+
+bpo-35660: Fix imports in window module.
+
+bpo-35641: Properly format calltip for function without docstring.
+
+bpo-33987: Use ttk Frame for ttk widgets.
+
+bpo-34055: Fix erroneous 'smart' indents and newlines in IDLE Shell.
+
+bpo-28097: Add Previous/Next History entries to Shell menu.
+
+bpo-35591: Find Selection now works when selection not found.
+
+bpo-35598: Update config_key: use PEP 8 names and ttk widgets,
+make some objects global, and add tests.
+
+bpo-35196: Speed up squeezer line counting.
+
+bpo-35208: Squeezer now counts wrapped lines before newlines.
+
+bpo-35555: Gray out Code Context menu entry when it's not applicable.
+
+bpo-22703: Improve the Code Context and Zoom Height menu labels.
+The Code Context menu label now toggles between Show/Hide Code Context.
+The Zoom Height menu now toggles between Zoom/Restore Height.
+Zoom Height has moved from the Window menu to the Options menu.
+
+bpo-35521: Document the editor code context feature.
+Add some internal references within the IDLE doc.
+
+bpo-34864: When starting IDLE on MacOS, warn if the system setting
+"Prefer tabs when opening documents" is "Always".  As previous
+documented for this issue, running IDLE with this setting causes
+problems.  If the setting is changed while IDLE is running,
+there will be no warning until IDLE is restarted.
+
+bpo-35213: Where appropriate, use 'macOS' in idlelib.
+
+bpo-34864: Document two IDLE on MacOS issues.  The System Preferences
+Dock "prefer tabs always" setting disables some IDLE features.
+Menus are a bit different than as described for Windows and Linux.
+
+bpo-35202: Remove unused imports in idlelib.
+
+bpo-33000: Document that IDLE's shell has no line limit.
+A program that runs indefinitely can overfill memory.
+
+bpo-23220: Explain how IDLE's Shell displays output.
+Add new subsection "User output in Shell".
+
+bpo-35099: Improve the doc about IDLE running user code.
+"IDLE -- console differences" is renamed "Running user code".
+It mostly covers the implications of using custom sys.stdxxx objects.
+
+bpo-35097: Add IDLE doc subsection explaining editor windows.
+Topics include opening, title and status bars, .py* extension, and running.
+
+Issue 35093: Document the IDLE document viewer in the IDLE doc.
+Add a paragraph in "Help and preferences", "Help sources" subsection.
+
+bpo-1529353: Explain Shell text squeezing in the IDLE doc.
+
+bpo-35088: Update idlelib.help.copy_string docstring.
+We now use git and backporting instead of hg and forward merging.
+
+bpo-35087: Update idlelib help files for the current doc build.
+The main change is the elimination of chapter-section numbers.
+
+bpo-1529353: Output over N lines (50 by default) is squeezed down to a button.
+N can be changed in the PyShell section of the General page of the
+Settings dialog.  Fewer, but possibly extra long, lines can be squeezed by
+right clicking on the output.  Squeezed output can be expanded in place
+by double-clicking the button or into the clipboard or a separate window
+by right-clicking the button.
+
+bpo-34548: Use configured color theme for read-only text views.
+
+bpo-33839: Refactor ToolTip and CallTip classes; add documentation
+and tests.
+
+bpo-34047: Fix mouse wheel scrolling direction on macOS.
+
+bpo-34275: Make calltips always visible on Mac.
+Patch by Kevin Walzer.
+
+bpo-34120: Fix freezing after closing some dialogs on Mac.
+This is one of multiple regressions from using newer tcl/tk.
+
+bpo-33975: Avoid small type when running htests.
+Since part of the purpose of human-viewed tests is to determine that
+widgets look right, it is important that they look the same for
+testing as when running IDLE.
+
+bpo-33905: Add test for idlelib.stackview.StackBrowser.
+
+bpo-33924: Change mainmenu.menudefs key 'windows' to 'window'.
+Every other menudef key is the lowercase version of the
+corresponding main menu entry (in this case, 'Window').
+
+bpo-33906: Rename idlelib.windows as window
+Match Window on the main menu and remove last plural module name.
+Change imports, test, and attribute references to match new name.
+
+bpo-33917: Fix and document idlelib/idle_test/template.py.
+The revised file compiles, runs, and tests OK.  idle_test/README.txt
+explains how to use it to create new IDLE test files.
+
+bpo-33904: In rstrip module, rename class RstripExtension as Rstrip.
+
+bpo-33907: For consistency and clarity, rename calltip objects.
+Module calltips and its class CallTips are now calltip and Calltip.
+In module calltip_w, class CallTip is now CalltipWindow.
+
+bpo-33855: Minimally test all IDLE modules.
+Standardize the test file format.  Add missing test files that import
+the tested module and perform at least one test.  Check and record the
+coverage of each test.
+
+bpo-33856: Add 'help' to Shell's initial welcome message.
+
+
+What's New in IDLE 3.7.0 (since 3.6.0)
+Released on 2018-06-27
+======================================
+
+bpo-33656: On Windows, add API call saying that tk scales for DPI.
+On Windows 8.1+ or 10, with DPI compatibility properties of the Python
+binary unchanged, and a monitor resolution greater than 96 DPI, this
+should make text and lines sharper and some colors brighter.
+On other systems, it should have no effect.  If you have a custom theme,
+you may want to adjust a color or two.  If perchance it make text worse
+on your monitor, you can disable the ctypes.OleDLL call near the top of
+pyshell.py and report the problem on python-list or idle-dev@python.org.
+
+bpo-33768: Clicking on a context line moves that line to the top
+of the editor window.
+
+bpo-33763: Replace the code context label widget with a text widget.
+
+bpo-33664: Scroll IDLE editor text by lines.
+(Previously, the mouse wheel and scrollbar slider moved text by a fixed
+number of pixels, resulting in partial lines at the top of the editor
+box.)  This change also applies to the shell and grep output windows,
+but currently not to read-only text views.
+
+bpo-33679: Enable theme-specific color configuration for Code Context.
+(Previously, there was one code context foreground and background font
+color setting, default or custom, on the extensions tab, that applied
+to all themes.)  For built-in themes, the foreground is the same as
+normal text and the background is a contrasting gray.  Context colors for
+custom themes are set on the Hightlights tab along with other colors.
+When one starts IDLE from a console and loads a custom theme without
+definitions for 'context', one will see a warning message on the
+console.
+
+bpo-33642: Display up to maxlines non-blank lines for Code Context.
+If there is no current context, show a single blank line.  (Previously,
+the Code Contex had numlines lines, usually with some blank.)  The use
+of a new option, 'maxlines' (default 15), avoids possible interference
+with user settings of the old option, 'numlines' (default 3).
+
+bpo-33628: Cleanup codecontext.py and its test.
+
+bpo-32831: Add docstrings and tests for codecontext.py.
+Coverage is 100%.  Patch by Cheryl Sabella.
+
+bpo-33564: Code context now recognizes async as a block opener.
+
+bpo-21474: Update word/identifier definition from ascii to unicode.
+In text and entry boxes, this affects selection by double-click,
+movement left/right by control-left/right, and deletion left/right
+by control-BACKSPACE/DEL.
+
+bpo-33204: Consistently color invalid string prefixes.
+A 'u' string prefix cannot be paired with either 'r' or 'f'.
+IDLE now consistently colors as much of the prefix, starting at the
+right, as is valid.  Revise and extend colorizer test.
+
+bpo-32984: Set __file__ while running a startup file.
+Like Python, IDLE optionally runs 1 startup file in the Shell window
+before presenting the first interactive input prompt.  For IDLE,
+option -s runs a file named in environmental variable IDLESTARTUP or
+PYTHONSTARTUP; -r file runs file.  Python sets __file__ to the startup
+file name before running the file and unsets it before the first
+prompt.  IDLE now does the same when run normally, without the -n
+option.
+
+bpo-32940: Replace StringTranslatePseudoMapping with faster code.
+
+bpo-32916: Change 'str' to 'code' in idlelib.pyparse and users.
+
+bpo-32905: Remove unused code in pyparse module.
+
+bpo-32874: IDLE - add pyparse tests with 97% coverage.
+
+bpo-32837: IDLE - require encoding argument for textview.view_file.
+Using the system and place-dependent default encoding for open()
+is a bad idea for IDLE's system and location-independent files.
+
+bpo-32826: Add "encoding=utf-8" to open() in IDLE's test_help_about.
+GUI test test_file_buttons() only looks at initial ascii-only lines,
+but failed on systems where open() defaults to 'ascii' because
+readline() internally reads and decodes far enough ahead to encounter
+a non-ascii character in CREDITS.txt.
+
+bpo-32765: Update configdialog General tab create page docstring.
+Add new widgets to the widget list.
+
+bpo-32207: Improve tk event exception tracebacks in IDLE.
+When tk event handling is driven by IDLE's run loop, a confusing
+and distracting queue.EMPTY traceback context is no longer added
+to tk event exception tracebacks.  The traceback is now the same
+as when event handling is driven by user code.  Patch based on
+a suggestion by Serhiy Storchaka.
+
+bpo-32164: Delete unused file idlelib/tabbedpages.py.
+Use of TabbedPageSet in configdialog was replaced by ttk.Notebook.
+
+bpo-32100: Fix old and new bugs in pathbrowser; improve tests.
+Patch mostly by Cheryl Sabella.
+
+bpo-31860: The font sample in the settings dialog is now editable.
+Edits persist while IDLE remains open.
+Patch by Serhiy Storchake and Terry Jan Reedy.
+
+bpo-31858: Restrict shell prompt manipulation to the shell.
+Editor and output windows only see an empty last prompt line.  This
+simplifies the code and fixes a minor bug when newline is inserted.
+Sys.ps1, if present, is read on Shell start-up, but is not set or changed.
+Patch by Terry Jan Reedy.
+
+bpo-28603: Fix a TypeError that caused a shell restart when printing
+a traceback that includes an exception that is unhashable.
+Patch by Zane Bitter.
+
+bpo-13802: Use non-Latin characters in the Font settings sample.
+Even if one selects a font that defines a limited subset of the unicode
+Basic Multilingual Plane, tcl/tk will use other fonts that define a
+character.  The expanded example give users of non-Latin characters
+a better idea of what they might see in the shell and editors.
+
+To make room for the expanded sample, frames on the Font tab are
+re-arranged.  The Font/Tabs help explains a bit about the additions.
+Patch by Terry Jan Reedy
+
+bpo-31460: Simplify the API of IDLE's Module Browser.
+Passing a widget instead of an flist with a root widget opens the
+option of creating a browser frame that is only part of a window.
+Passing a full file name instead of pieces assumed to come from a
+.py file opens the possibility of browsing python files that do not
+end in .py.
+
+bpo-31649: Make _htest and _utest parameters keyword-only.
+These are used to adjust code for human and unit tests.
+
+bpo-31459: Rename module browser from Class Browser to Module Browser.
+The original module-level class and method browser became a module
+browser, with the addition of module-level functions, years ago.
+Nested classes and functions were added yesterday.  For back-
+compatibility, the virtual event <<open-class-browser>>, which
+appears on the Keys tab of the Settings dialog, is not changed.
+Patch by Cheryl Sabella.
+
+bpo-1612262: Module browser now shows nested classes and functions.
+Original patches for code and tests by Guilherme Polo and
+Cheryl Sabella, respectively.  Revisions by Terry Jan Reedy.
+
+bpo-31500: Tk's default fonts now are scaled on HiDPI displays.
+This affects all dialogs.  Patch by Serhiy Storchaka.
+
+bpo-31493: Fix code context update and font update timers.
+Canceling timers prevents a warning message when test_idle completes.
+
+bpo-31488: Update non-key options in former extension classes.
+When applying configdialog changes, call .reload for each feature class.
+Change ParenMatch so updated options affect existing instances attached
+to existing editor windows.
+
+bpo-31477: Improve rstrip entry in IDLE doc.
+Strip Trailing Whitespace strips more than blank spaces.
+Multiline string literals are not skipped.
+
+bpo-31480: fix tests to pass with zzdummy extension disabled. (#3590)
+To see the example in action, enable it on options extensions tab.
+
+bpo-31421: Document how IDLE runs tkinter programs.
+IDLE calls tcl/tk update in the background in order to make live
+interaction and experimentation with tkinter applications much easier.
+
+bpo-31414: Fix tk entry box tests by deleting first.
+Adding to an int entry is not the same as deleting and inserting
+because int('') will fail.  Patch by Terry Jan Reedy.
+
+bpo-27099: Convert IDLE's built-in 'extensions' to regular features.
+  About 10 IDLE features were implemented as supposedly optional
+extensions.  Their different behavior could be confusing or worse for
+users and not good for maintenance.  Hence the conversion.
+  The main difference for users is that user configurable key bindings
+for builtin features are now handled uniformly.  Now, editing a binding
+in a keyset only affects its value in the keyset.  All bindings are
+defined together in the system-specific default keysets in config-
+extensions.def.  All custom keysets are saved as a whole in config-
+extension.cfg.  All take effect as soon as one clicks Apply or Ok.
+   The affected events are '<<force-open-completions>>',
+'<<expand-word>>', '<<force-open-calltip>>', '<<flash-paren>>',
+'<<format-paragraph>>', '<<run-module>>', '<<check-module>>', and
+'<<zoom-height>>'.  Any (global) customizations made before 3.6.3 will
+not affect their keyset-specific customization after 3.6.3. and vice
+versa.
+  Initial patch by Charles Wohlganger, revised by Terry Jan Reedy.
+
+bpo-31051:  Rearrange condigdialog General tab.
+Sort non-Help options into Window (Shell+Editor) and Editor (only).
+Leave room for the addition of new options.
+Patch by Terry Jan Reedy.
+
+bpo-30617: Add docstrings and tests for outwin subclass of editor.
+Move some data and functions from the class to module level.
+Patch by Cheryl Sabella.
+
+bpo-31287: Do not modify tkinter.messagebox in test_configdialog.
+Instead, mask it with an instance mock that can be deleted.
+Patch by Terry Jan Reedy.
+
+bpo-30781: Use ttk widgets in ConfigDialog pages.
+These should especially look better on MacOSX.
+Patches by Terry Jan Reedy and Cheryl Sabella.
+
+bpo-31206: Factor HighPage(Frame) class from ConfigDialog.
+Patch by Cheryl Sabella.
+
+bp0-31001: Add tests for configdialog highlight tab.
+Patch by Cheryl Sabella.
+
+bpo-31205: Factor KeysPage(Frame) class from ConfigDialog.
+The slightly modified tests continue to pass.
+Patch by Cheryl Sabella.
+
+bpo-31002: Add tests for configdialog keys tab.
+Patch by Cheryl Sabella.
+
+bpo-19903: Change calltipes to use inspect.signature.
+Idlelib.calltips.get_argspec now uses inspect.signature instead of
+inspect.getfullargspec, like help() does.  This improves the signature
+in the call tip in a few different cases, including builtins converted
+to provide a signature.  A message is added if the object is not
+callable, has an invalid signature, or if it has positional-only
+parameters.  Patch by Louie Lu.
+
+bop-31083: Add an outline of a TabPage class in configdialog.
+Add template as comment. Update existing classes to match outline.
+Initial patch by Cheryl Sabella.
+
+bpo-31050: Factor GenPage(Frame) class from ConfigDialog.
+The slightly modified tests for the General tab continue to pass.
+Patch by Cheryl Sabella.
+
+bpo-31004: Factor FontPage(Frame) class from ConfigDialog.
+The slightly modified tests continue to pass. The General test
+broken by the switch to ttk.Notebook is fixed.
+Patch mostly by Cheryl Sabella.
+
+bpo-30781: IDLE - Use ttk Notebook in ConfigDialog.
+This improves navigation by tabbing.
+Patch by Terry Jan Reedy.
+
+bpo-31060: IDLE - Finish rearranging methods of ConfigDialog.
+Grouping methods pertaining to each tab and the buttons will aid
+writing tests and improving the tabs and will enable splitting the
+groups into classes.
+Patch by Terry Jan Reedy.
+
+bpo-30853: IDLE -- Factor a VarTrace class out of ConfigDialog.
+Instance tracers manages pairs consisting of a tk variable and a
+callback function.  When tracing is turned on, setting the variable
+calls the function.  Test coverage for the new class is 100%.
+Patch by Terry Jan Reedy.
+
+bpo-31003: IDLE: Add more tests for General tab.
+Patch by Terry Jan Reedy.
+
+bpo-30993: IDLE - Improve configdialog font page and tests.
+*In configdialog: Document causal pathways in create_font_tab
+docstring.  Simplify some attribute names. Move set_samples calls to
+var_changed_font (idea from Cheryl Sabella).  Move related functions to
+positions after the create widgets function.
+* In test_configdialog: Fix test_font_set so not order dependent.  Fix
+renamed test_indent_scale so it tests the widget.  Adjust tests for
+movement of set_samples call.  Add tests for load functions.  Put all
+font tests in one class and tab indent tests in another.  Except for
+two lines, these tests completely cover the related functions.
+Patch by Terry Jan Reedy.
+
+bpo-30981: IDLE -- Add more configdialog font page tests.
+
+bpo-28523: IDLE: replace 'colour' with 'color' in configdialog.
+
+bpo-30917: Add tests for idlelib.config.IdleConf.
+Increase coverage from 46% to 96%.
+Patch by Louie Lu.
+
+bpo-30913: Document ConfigDialog tk Vars, methods, and widgets in docstrings
+This will facilitate improving the dialog and splitting up the class.
+Original patch by Cheryl Sabella.
+
+bpo-30899: Add tests for ConfigParser subclasses in config.
+Coverage is 100% for those classes and ConfigChanges.
+Patch by Louie Lu.
+
+bpo-30881: Add docstrings to browser.py.
+Patch by Cheryl Sabella.
+
+bpo-30851: Remove unused tk variables in configdialog.
+One is a duplicate, one is set but cannot be altered by users.
+Patch by Cheryl Sabella.
+
+bpo-30870: Select font option with Up and Down keys, as well as with mouse.
+Added test increases configdialog coverage to 60%
+Patches mostly by Louie Lu.
+
+bpo-8231: Call config.IdleConf.GetUserCfgDir only once per process.
+
+bpo-30779: Factor ConfigChanges class from configdialog, put in config; test.
+* In config, put dump test code in a function; run it and unittest in
+  'if __name__ == '__main__'.
+* Add class config.ConfigChanges based on changes_class_v4.py on bpo issue.
+* Add class test_config.ChangesTest, partly using configdialog_tests_v1.py.
+* Revise configdialog to use ConfigChanges; see tracker msg297804.
+* Revise test_configdialog to match configdialog changes.
+* Remove configdialog functions unused or moved to ConfigChanges.
+Cheryl Sabella contributed parts of the patch.
+
+bpo-30777: Configdialog - add docstrings and improve comments.
+Patch by Cheryl Sabella.
+
+bpo-30495: Improve textview with docstrings, PEP8 names, and more tests.
+Split TextViewer class into ViewWindow, ViewFrame, and TextFrame classes
+so that instances of the latter two can be placed with other widgets
+within a multiframe window.
+Patches by Cheryl Sabella and Terry Jan Reedy.
+
+bpo-30723: Make several improvements to parenmatch.
+* Add 'parens' style to highlight both opener and closer.
+* Make 'default' style, which is not default, a synonym for 'opener'.
+* Make time-delay work the same with all styles.
+* Add help for config dialog extensions tab, including parenmatch.
+* Add new tests.
+Original patch by Charles Wohlganger.  Revisions by Terry Jan Reedy
+
+bpo-30674: Grep -- Add docstrings.  Patch by Cheryl Sabella.
+
+bpo-21519: IDLE's basic custom key entry dialog now detects
+duplicates properly. Original patch by Saimadhav Heblikar.
+
+bpo-29910: IDLE no longer deletes a character after commenting out a
+region by a key shortcut.  Add "return 'break'" for this and other
+potential conflicts between IDLE and default key bindings.
+Patch by Serhiy Storchaka.
+
+bpo-30728: Modernize idlelib.configdialog:
+* replace import * with specific imports;
+* lowercase method and attribute lines.
+Patch by Cheryl Sabella.
+
+bpo-6739: Verify user-entered key sequences by trying to bind them
+with to a tk widget.  Add tests for all 3 validation functions.
+Original patch by G Polo.  Tests added by Cheryl Sabella.
+Code revised and more tests added by Terry Jan Reedy
+
+bpo-24813: Add icon to help_about and make other changes.
+
+bpo-15786: Fix several problems with IDLE's autocompletion box.
+The following should now work: clicking on selection box items;
+using the scrollbar; selecting an item by hitting Return.
+Hangs on MacOSX should no longer happen. Patch by Louie Lu.
+
+bpo-25514: Add doc subsubsection about IDLE failure to start.
+Popup no-connection message directs users to this section.
+
+bpo-30642: Fix reference leaks in IDLE tests.
+Patches by Louie Lu and Terry Jan Reedy.
+
+bpo-30495: Add docstrings for textview.py and use PEP8 names.
+Patches by Cheryl Sabella and Terry Jan Reedy.
+
+bpo-30290: Help-about: use pep8 names and add tests.
+Increase coverage to 100%.
+Patches by Louie Lu, Cheryl Sabella, and Terry Jan Reedy.
+
+bpo-30303: Add _utest option to textview; add new tests.
+Increase coverage to 100%.
+Patches by Louie Lu and Terry Jan Reedy.
+
+Issue #29071: IDLE colors f-string prefixes but not invalid ur prefixes.
+
+Issue #28572: Add 10% to coverage of IDLE's test_configdialog.
+Update and augment description of the configuration system.
+
+
+What's New in IDLE 3.6.0 (since 3.5.0)
+Released on 2016-12-23
+======================================
+
+- Issue #15308: Add 'interrupt execution' (^C) to Shell menu.
+  Patch by Roger Serwy, updated by Bayard Randel.
+
+- Issue #27922: Stop IDLE tests from 'flashing' gui widgets on the screen.
+
+- Issue #27891: Consistently group and sort imports within idlelib modules.
+
+- Issue #17642: add larger font sizes for classroom projection.
+
+- Add version to title of IDLE help window.
+
+- Issue #25564: In section on IDLE -- console differences, mention that
+  using exec means that __builtins__ is defined for each statement.
+
+- Issue #27821: Fix 3.6.0a3 regression that prevented custom key sets
+  from being selected when no custom theme was defined.
+
+- Issue #27714: text_textview and test_autocomplete now pass when re-run
+  in the same process.  This occurs when test_idle fails when run with the
+  -w option but without -jn.  Fix warning from test_config.
+
+- Issue #27621: Put query response validation error messages in the query
+  box itself instead of in a separate messagebox.  Redo tests to match.
+  Add Mac OSX refinements.  Original patch by Mark Roseman.
+
+- Issue #27620: Escape key now closes Query box as cancelled.
+
+- Issue #27609: IDLE: tab after initial whitespace should tab, not
+  autocomplete. This fixes problem with writing docstrings at least
+  twice indented.
+
+- Issue #27609: Explicitly return None when there are also non-None
+  returns. In a few cases, reverse a condition and eliminate a return.
+
+- Issue #25507: IDLE no longer runs buggy code because of its tkinter imports.
+  Users must include the same imports required to run directly in Python.
+
+- Issue #27173: Add 'IDLE Modern Unix' to the built-in key sets.
+  Make the default key set depend on the platform.
+  Add tests for the changes to the config module.
+
+- Issue #27452: add line counter and crc to IDLE configHandler test dump.
+
+- Issue #27477: IDLE search dialogs now use ttk widgets.
+
+- Issue #27173: Add 'IDLE Modern Unix' to the built-in key sets.
+  Make the default key set depend on the platform.
+  Add tests for the changes to the config module.
+
+- Issue #27452: make command line "idle-test> python test_help.py" work.
+  __file__ is relative when python is started in the file's directory.
+
+- Issue #27452: add line counter and crc to IDLE configHandler test dump.
+
+- Issue #27380: IDLE: add query.py with base Query dialog and ttk widgets.
+  Module had subclasses SectionName, ModuleName, and HelpSource, which are
+  used to get information from users by configdialog and file =>Load Module.
+  Each subclass has itw own validity checks.  Using ModuleName allows users
+  to edit bad module names instead of starting over.
+  Add tests and delete the two files combined into the new one.
+
+- Issue #27372: Test_idle no longer changes the locale.
+
+- Issue #27365: Allow non-ascii chars in IDLE NEWS.txt, for contributor names.
+
+- Issue #27245: IDLE: Cleanly delete custom themes and key bindings.
+  Previously, when IDLE was started from a console or by import, a cascade
+  of warnings was emitted.  Patch by Serhiy Storchaka.
+
+- Issue #24137: Run IDLE, test_idle, and htest with tkinter default root disabled.
+  Fix code and tests that fail with this restriction.
+  Fix htests to not create a second and redundant root and mainloop.
+
+- Issue #27310: Fix IDLE.app failure to launch on OS X due to vestigial import.
+
+- Issue #5124: Paste with text selected now replaces the selection on X11.
+  This matches how paste works on Windows, Mac, most modern Linux apps,
+  and ttk widgets.  Original patch by Serhiy Storchaka.
+
+- Issue #24750: Switch all scrollbars in IDLE to ttk versions.
+  Where needed, minimal tests are added to cover changes.
+
+- Issue #24759: IDLE requires tk 8.5 and availability ttk widgets.
+  Delete now unneeded tk version tests and code for older versions.
+  Add test for IDLE syntax colorizer.
+
+- Issue #27239: idlelib.macosx.isXyzTk functions initialize as needed.
+
+- Issue #27262: move Aqua unbinding code, which enable context menus, to macosx.
+
+- Issue #24759: Make clear in idlelib.idle_test.__init__ that the directory
+  is a private implementation of test.test_idle and tool for maintainers.
+
+- Issue #27196: Stop 'ThemeChanged' warnings when running IDLE tests.
+  These persisted after other warnings were suppressed in #20567.
+  Apply Serhiy Storchaka's update_idletasks solution to four test files.
+  Record this additional advice in idle_test/README.txt
+
+- Issue #20567: Revise idle_test/README.txt with advice about avoiding
+  tk warning messages from tests.  Apply advice to several IDLE tests.
+
+- Issue # 24225: Update idlelib/README.txt with new file names
+  and event handlers.
+
+- Issue #27156: Remove obsolete code not used by IDLE.  Replacements:
+  1. help.txt, replaced by help.html, is out-of-date and should not be used.
+  Its dedicated viewer has be replaced by the html viewer in help.py.
+  2. 'import idlever; I = idlever.IDLE_VERSION' is the same as
+  'import sys; I = version[:version.index(' ')]'
+  3. After 'ob = stackviewer.VariablesTreeItem(*args)',
+  'ob.keys()' == 'list(ob.object.keys).
+  4. In macosc, runningAsOSXAPP == isAquaTk; idCarbonAquaTk == isCarbonTk
+
+- Issue #27117: Make colorizer htest and turtledemo work with dark themes.
+  Move code for configuring text widget colors to a new function.
+
+- Issue #24225: Rename many idlelib/*.py and idle_test/test_*.py files.
+  Edit files to replace old names with new names when the old name
+  referred to the module rather than the class it contained.
+  See the issue and IDLE section in What's New in 3.6 for more.
+
+- Issue #26673: When tk reports font size as 0, change to size 10.
+  Such fonts on Linux prevented the configuration dialog from opening.
+
+- Issue #21939: Add test for IDLE's percolator.
+  Original patch by Saimadhav Heblikar.
+
+- Issue #21676: Add test for IDLE's replace dialog.
+  Original patch by Saimadhav Heblikar.
+
+- Issue #18410: Add test for IDLE's search dialog.
+  Original patch by Westley Martínez.
+
+- Issue #21703: Add test for undo delegator.  Patch mostly by
+  Saimadhav Heblikar .
+
+- Issue #27044: Add ConfigDialog.remove_var_callbacks to stop memory leaks.
+
+- Issue #23977: Add more asserts to test_delegator.
+
+- Issue #20640: Add tests for idlelib.configHelpSourceEdit.
+  Patch by Saimadhav Heblikar.
+
+- In the 'IDLE-console differences' section of the IDLE doc, clarify
+  how running with IDLE affects sys.modules and the standard streams.
+
+- Issue #25507: fix incorrect change in IOBinding that prevented printing.
+  Augment IOBinding htest to include all major IOBinding functions.
+
+- Issue #25905: Revert unwanted conversion of ' to ’ RIGHT SINGLE QUOTATION
+  MARK in README.txt and open this and NEWS.txt with 'ascii'.
+  Re-encode CREDITS.txt to utf-8 and open it with 'utf-8'.
+
+- Issue 15348: Stop the debugger engine (normally in a user process)
+  before closing the debugger window (running in the IDLE process).
+  This prevents the RuntimeErrors that were being caught and ignored.
+
+- Issue #24455: Prevent IDLE from hanging when a) closing the shell while the
+  debugger is active (15347); b) closing the debugger with the [X] button
+  (15348); and c) activating the debugger when already active (24455).
+  The patch by Mark Roseman does this by making two changes.
+  1. Suspend and resume the gui.interaction method with the tcl vwait
+  mechanism intended for this purpose (instead of root.mainloop & .quit).
+  2. In gui.run, allow any existing interaction to terminate first.
+
+- Change 'The program' to 'Your program' in an IDLE 'kill program?' message
+  to make it clearer that the program referred to is the currently running
+  user program, not IDLE itself.
+
+- Issue #24750: Improve the appearance of the IDLE editor window status bar.
+  Patch by Mark Roseman.
+
+- Issue #25313: Change the handling of new built-in text color themes to better
+  address the compatibility problem introduced by the addition of IDLE Dark.
+  Consistently use the revised idleConf.CurrentTheme everywhere in idlelib.
+
+- Issue #24782: Extension configuration is now a tab in the IDLE Preferences
+  dialog rather than a separate dialog.   The former tabs are now a sorted
+  list.  Patch by Mark Roseman.
+
+- Issue #22726: Re-activate the config dialog help button with some content
+  about the other buttons and the new IDLE Dark theme.
+
+- Issue #24820: IDLE now has an 'IDLE Dark' built-in text color theme.
+  It is more or less IDLE Classic inverted, with a cobalt blue background.
+  Strings, comments, keywords, ... are still green, red, orange, ... .
+  To use it with IDLEs released before November 2015, hit the
+  'Save as New Custom Theme' button and enter a new name,
+  such as 'Custom Dark'.  The custom theme will work with any IDLE
+  release, and can be modified.
+
+- Issue #25224: README.txt is now an idlelib index for IDLE developers and
+  curious users.  The previous user content is now in the IDLE doc chapter.
+  'IDLE' now means 'Integrated Development and Learning Environment'.
+
+- Issue #24820: Users can now set breakpoint colors in
+  Settings -> Custom Highlighting.  Original patch by Mark Roseman.
+
+- Issue #24972: Inactive selection background now matches active selection
+  background, as configured by users, on all systems.  Found items are now
+  always highlighted on Windows.  Initial patch by Mark Roseman.
+
+- Issue #24570: Idle: make calltip and completion boxes appear on Macs
+  affected by a tk regression.  Initial patch by Mark Roseman.
+
+- Issue #24988: Idle ScrolledList context menus (used in debugger)
+  now work on Mac Aqua.  Patch by Mark Roseman.
+
+- Issue #24801: Make right-click for context menu work on Mac Aqua.
+  Patch by Mark Roseman.
+
+- Issue #25173: Associate tkinter messageboxes with a specific widget.
+  For Mac OSX, make them a 'sheet'.  Patch by Mark Roseman.
+
+- Issue #25198: Enhance the initial html viewer now used for Idle Help.
+  * Properly indent fixed-pitch text (patch by Mark Roseman).
+  * Give code snippet a very Sphinx-like light blueish-gray background.
+  * Re-use initial width and height set by users for shell and editor.
+  * When the Table of Contents (TOC) menu is used, put the section header
+  at the top of the screen.
+
+- Issue #25225: Condense and rewrite Idle doc section on text colors.
+
+- Issue #21995: Explain some differences between IDLE and console Python.
+
+- Issue #22820: Explain need for *print* when running file from Idle editor.
+
+- Issue #25224: Doc: augment Idle feature list and no-subprocess section.
+
+- Issue #25219: Update doc for Idle command line options.
+  Some were missing and notes were not correct.
+
+- Issue #24861: Most of idlelib is private and subject to change.
+  Use idleib.idle.* to start Idle. See idlelib.__init__.__doc__.
+
+- Issue #25199: Idle: add synchronization comments for future maintainers.
+
+- Issue #16893: Replace help.txt with help.html for Idle doc display.
+  The new idlelib/help.html is rstripped Doc/build/html/library/idle.html.
+  It looks better than help.txt and will better document Idle as released.
+  The tkinter html viewer that works for this file was written by Mark Roseman.
+  The now unused EditorWindow.HelpDialog class and helt.txt file are deprecated.
+
+- Issue #24199: Deprecate unused idlelib.idlever with possible removal in 3.6.
+
+- Issue #24790: Remove extraneous code (which also create 2 & 3 conflicts).
+
+
+What's New in IDLE 3.5.0?
+=========================
+*Release date: 2015-09-13*
+
+- Issue #23672: Allow Idle to edit and run files with astral chars in name.
+  Patch by Mohd Sanad Zaki Rizvi.
+
+- Issue 24745: Idle editor default font. Switch from Courier to
+  platform-sensitive TkFixedFont.  This should not affect current customized
+  font selections.  If there is a problem, edit $HOME/.idlerc/config-main.cfg
+  and remove 'fontxxx' entries from [Editor Window].  Patch by Mark Roseman.
+
+- Issue #21192: Idle editor. When a file is run, put its name in the restart bar.
+  Do not print false prompts. Original patch by Adnan Umer.
+
+- Issue #13884: Idle menus. Remove tearoff lines. Patch by Roger Serwy.
+
+- Issue #23184: remove unused names and imports in idlelib.
+  Initial patch by Al Sweigart.
+
+- Issue #20577: Configuration of the max line length for the FormatParagraph
+  extension has been moved from the General tab of the Idle preferences dialog
+  to the FormatParagraph tab of the Config Extensions dialog.
+  Patch by Tal Einat.
+
+- Issue #16893: Update Idle doc chapter to match current Idle and add new
+  information.
+
+- Issue #3068: Add Idle extension configuration dialog to Options menu.
+  Changes are written to HOME/.idlerc/config-extensions.cfg.
+  Original patch by Tal Einat.
+
+- Issue #16233: A module browser (File : Class Browser, Alt+C) requires an
+  editor window with a filename.  When Class Browser is requested otherwise,
+  from a shell, output window, or 'Untitled' editor, Idle no longer displays
+  an error box.  It now pops up an  Open Module box (Alt+M). If a valid name
+  is entered and a module is opened, a corresponding browser is also opened.
+
+- Issue #4832: Save As to type Python files automatically adds .py to the
+  name you enter (even if your system does not display it).  Some systems
+  automatically add .txt when type is Text files.
+
+- Issue #21986: Code objects are not normally pickled by the pickle module.
+  To match this, they are no longer pickled when running under Idle.
+
+- Issue #23180: Rename IDLE "Windows" menu item to "Window".
+  Patch by Al Sweigart.
+
+- Issue #17390: Adjust Editor window title; remove 'Python',
+  move version to end.
+
+- Issue #14105: Idle debugger breakpoints no longer disappear
+  when inserting or deleting lines.
+
+- Issue #17172: Turtledemo can now be run from Idle.
+  Currently, the entry is on the Help menu, but it may move to Run.
+  Patch by Ramchandra Apt and Lita Cho.
+
+- Issue #21765: Add support for non-ascii identifiers to HyperParser.
+
+- Issue #21940: Add unittest for WidgetRedirector. Initial patch by Saimadhav
+  Heblikar.
+
+- Issue #18592: Add unittest for SearchDialogBase. Patch by Phil Webster.
+
+- Issue #21694: Add unittest for ParenMatch. Patch by Saimadhav Heblikar.
+
+- Issue #21686: add unittest for HyperParser. Original patch by Saimadhav
+  Heblikar.
+
+- Issue #12387: Add missing upper(lower)case versions of default Windows key
+  bindings for Idle so Caps Lock does not disable them. Patch by Roger Serwy.
+
+- Issue #21695: Closing a Find-in-files output window while the search is
+  still in progress no longer closes Idle.
+
+- Issue #18910: Add unittest for textView. Patch by Phil Webster.
+
+- Issue #18292: Add unittest for AutoExpand. Patch by Saihadhav Heblikar.
+
+- Issue #18409: Add unittest for AutoComplete. Patch by Phil Webster.
+
+- Issue #21477: htest.py - Improve framework, complete set of tests.
+  Patches by Saimadhav Heblikar
+
+- Issue #18104: Add idlelib/idle_test/htest.py with a few sample tests to begin
+  consolidating and improving human-validated tests of Idle. Change other files
+  as needed to work with htest.  Running the module as __main__ runs all tests.
+
+- Issue #21139: Change default paragraph width to 72, the PEP 8 recommendation.
+
+- Issue #21284: Paragraph reformat test passes after user changes reformat width.
+
+- Issue #17654: Ensure IDLE menus are customized properly on OS X for
+  non-framework builds and for all variants of Tk.
+
+
+What's New in IDLE 3.4.0?
+=========================
+*Release date: 2014-03-16*
+
+- Issue #17390: Display Python version on Idle title bar.
+  Initial patch by Edmond Burnett.
+
+- Issue #5066: Update IDLE docs. Patch by Todd Rovito.
+
+- Issue #17625: Close the replace dialog after it is used.
+
+- Issue #16226: Fix IDLE Path Browser crash.
+  (Patch by Roger Serwy)
+
+- Issue #15853: Prevent IDLE crash on OS X when opening Preferences menu
+  with certain versions of Tk 8.5.  Initial patch by Kevin Walzer.
+
+
+What's New in IDLE 3.3.0?
+=========================
+*Release date: 2012-09-29*
+
+- Issue #17625: Close the replace dialog after it is used.
+
+- Issue #7163: Propagate return value of sys.stdout.write.
+
+- Issue #15318: Prevent writing to sys.stdin.
+
+- Issue #4832: Modify IDLE to save files with .py extension by
+  default on Windows and OS X (Tk 8.5) as it already does with X11 Tk.
+
+- Issue #13532, #15319: Check that arguments to sys.stdout.write are strings.
+
+- Issue # 12510: Attempt to get certain tool tips no longer crashes IDLE.
+  Erroneous tool tips have been corrected. Default added for callables.
+
+- Issue #10365: File open dialog now works instead of crashing even when
+  parent window is closed while dialog is open.
+
+- Issue 14876: use user-selected font for highlight configuration.
+
+- Issue #14937: Perform auto-completion of filenames in strings even for
+  non-ASCII filenames. Likewise for identifiers.
+
+- Issue #8515: Set __file__ when run file in IDLE.
+  Initial patch by Bruce Frederiksen.
+
+- IDLE can be launched as `python -m idlelib`
+
+- Issue #14409: IDLE now properly executes commands in the Shell window
+  when it cannot read the normal config files on startup and
+  has to use the built-in default key bindings.
+  There was previously a bug in one of the defaults.
+
+- Issue #3573: IDLE hangs when passing invalid command line args
+  (directory(ies) instead of file(s)).
+
+- Issue #14018: Update checks for unstable system Tcl/Tk versions on OS X
+  to include versions shipped with OS X 10.7 and 10.8 in addition to 10.6.
+
+
+What's New in IDLE 3.2.1?
+=========================
+*Release date: 15-May-11*
+
+- Issue #6378: Further adjust idle.bat to start associated Python
+
+- Issue #11896: Save on Close failed despite selecting "Yes" in dialog.
+
+- Issue #1028: Ctrl-space binding to show completions was causing IDLE to exit.
+  Tk < 8.5 was sending invalid Unicode null; replaced with valid null.
+
+- Issue #4676: <Home> toggle failing on Tk 8.5, causing IDLE exits and strange selection
+  behavior. Improve selection extension behaviour.
+
+- Issue #3851: <Home> toggle non-functional when NumLock set on Windows.
+
+
+What's New in IDLE 3.1b1?
+=========================
+*Release date: 06-May-09*
+
+- Issue #5707: Use of 'filter' in keybindingDialog.py was causing custom key assignment to
+  fail.  Patch by Amaury Forgeot d'Arc.
+
+- Issue #4815: Offer conversion to UTF-8 if source files have
+  no encoding declaration and are not encoded in UTF-8.
+
+- Issue #4008: Fix problems with non-ASCII source files.
+
+- Issue #4323: Always encode source as UTF-8 without asking
+  the user (unless a different encoding is declared); remove
+  user configuration of source encoding; all according to
+  PEP 3120.
+
+- Issue #2665: On Windows, an IDLE installation upgraded from an old version
+  would not start if a custom theme was defined.
+
+------------------------------------------------------------------------
+Refer to NEWS2x.txt and HISTORY.txt for information on earlier releases.
+------------------------------------------------------------------------
diff --git a/rootfs/usr/lib/python3.8/idlelib/NEWS2x.txt b/rootfs/usr/lib/python3.8/idlelib/NEWS2x.txt
new file mode 100644
index 0000000..6751ca5
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/NEWS2x.txt
@@ -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.
+--------------------------------------------------------------------
diff --git a/rootfs/usr/lib/python3.8/idlelib/README.txt b/rootfs/usr/lib/python3.8/idlelib/README.txt
new file mode 100644
index 0000000..bc3d978
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/README.txt
@@ -0,0 +1,251 @@
+README.txt: an index to idlelib files and the IDLE menu.
+
+IDLE is Python's Integrated Development and Learning
+Environment.  The user documentation is part of the Library Reference and
+is available in IDLE by selecting Help => IDLE Help.  This README documents
+idlelib for IDLE developers and curious users.
+
+IDLELIB FILES lists files alphabetically by category,
+with a short description of each.
+
+IDLE MENU show the menu tree, annotated with the module
+or module object that implements the corresponding function.
+
+This file is descriptive, not prescriptive, and may have errors
+and omissions and lag behind changes in idlelib.
+
+
+IDLELIB FILES
+Implementation files not in IDLE MENU are marked (nim).
+Deprecated files and objects are listed separately as the end.
+
+Startup
+-------
+__init__.py  # import, does nothing
+__main__.py  # -m, starts IDLE
+idle.bat
+idle.py
+idle.pyw
+
+Implementation
+--------------
+autocomplete.py   # Complete attribute names or filenames.
+autocomplete_w.py # Display completions.
+autoexpand.py     # Expand word with previous word in file.
+browser.py        # Create module browser window.
+calltip_w.py      # Display calltip.
+calltips.py       # Create calltip text.
+codecontext.py    # Show compound statement headers otherwise not visible.
+colorizer.py      # Colorize text (nim)
+config.py         # Load, fetch, and save configuration (nim).
+configdialog.py   # Display user configuration dialogs.
+config_help.py    # Specify help source in configdialog.
+config_key.py     # Change keybindings.
+dynoption.py      # Define mutable OptionMenu widget (nim).
+debugobj.py       # Define class used in stackviewer.
+debugobj_r.py     # Communicate objects between processes with rpc (nim).
+debugger.py       # Debug code run from shell or editor; show window.
+debugger_r.py     # Debug code run in remote process.
+delegator.py      # Define base class for delegators (nim).
+editor.py         # Define most of editor and utility functions.
+filelist.py       # Open files and manage list of open windows (nim).
+grep.py           # Find all occurrences of pattern in multiple files.
+help.py           # Display IDLE's html doc.
+help_about.py     # Display About IDLE dialog.
+history.py        # Get previous or next user input in shell (nim)
+hyperparser.py    # Parse code around a given index.
+iomenu.py         # Open, read, and write files
+macosx.py         # Help IDLE run on Macs (nim).
+mainmenu.py       # Define most of IDLE menu.
+multicall.py      # Wrap tk widget to allow multiple calls per event (nim).
+outwin.py         # Create window for grep output.
+paragraph.py      # Re-wrap multiline strings and comments.
+parenmatch.py     # Match fenceposts: (), [], and {}.
+pathbrowser.py    # Create path browser window.
+percolator.py     # Manage delegator stack (nim).
+pyparse.py        # Give information on code indentation
+pyshell.py        # Start IDLE, manage shell, complete editor window
+query.py          # Query user for information
+redirector.py     # Intercept widget subcommands (for percolator) (nim).
+replace.py        # Search and replace pattern in text.
+rpc.py            # Communicate between idle and user processes (nim).
+rstrip.py         # Strip trailing whitespace.
+run.py            # Manage user code execution subprocess.
+runscript.py      # Check and run user code.
+scrolledlist.py   # Define scrolledlist widget for IDLE (nim).
+search.py         # Search for pattern in text.
+searchbase.py     # Define base for search, replace, and grep dialogs.
+searchengine.py   # Define engine for all 3 search dialogs.
+stackviewer.py    # View stack after exception.
+statusbar.py      # Define status bar for windows (nim).
+tabbedpages.py    # Define tabbed pages widget (nim).
+textview.py       # Define read-only text widget (nim).
+tree.py           # Define tree widget, used in browsers (nim).
+undo.py           # Manage undo stack.
+windows.py        # Manage window list and define listed top level.
+zoomheight.py     # Zoom window to full height of screen.
+
+Configuration
+-------------
+config-extensions.def # Defaults for extensions
+config-highlight.def  # Defaults for colorizing
+config-keys.def       # Defaults for key bindings
+config-main.def       # 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
+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
+
+Unused and Deprecated files and objects (nim)
+---------------------------------------------
+tooltip.py # unused
+
+
+
+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)
+
+Shell  # pyshell
+  View Last Restart    # pyshell.PyShell.view_restart_mark
+  Restart Shell        # pyshell.PyShell.restart_shell
+  Interrupt Execution  # pyshell.PyShell.cancel_callback
+
+Debug (Shell only)
+  Go to File/Line
+  debugger         # debugger, debugger_r, PyShell.toggle_debugger
+  Stack Viewer     # stackviewer, PyShell.open_stack_viewer
+  Auto-open Stack Viewer  # stackviewer
+
+Format (Editor only)
+  Indent Region    # eEW.indent_region_event
+  Dedent Region    # eEW.dedent_region_event
+  Comment Out Reg. # eEW.comment_region_event
+  Uncomment Region # eEW.uncomment_region_event
+  Tabify Region    # eEW.tabify_region_event
+  Untabify Region  # eEW.untabify_region_event
+  Toggle Tabs      # eEW.toggle_tabs_event
+  New Indent Width # eEW.change_indentwidth_event
+  Format Paragraph # paragraph extension
+  ---
+  Strip tailing whitespace  # rstrip extension
+
+Run (Editor only)
+  Python Shell     # pyshell
+  ---
+  Check Module     # runscript
+  Run Module       # runscript
+
+Options
+  Configure IDLE   # eEW.config_dialog, configdialog
+    (tabs in the dialog)
+    Font tab       # config-main.def
+    Highlight tab  # query, config-highlight.def
+    Keys tab       # query, config_key, config_keys.def
+    General tab    # config_help, config-main.def
+    Extensions tab # config-extensions.def, corresponding .py
+  ---
+  Code Context (ed)# codecontext extension
+
+Window
+  Zoomheight       # zoomheight extension
+  ---
+  <open windows>   # windows
+
+Help
+  About IDLE       # eEW.about_dialog, help_about.AboutDialog
+  ---
+  IDLE Help        # eEW.help_dialog, helpshow_idlehelp
+  Python Doc       # eEW.python_docs
+  Turtle Demo      # eEW.open_turtle_demo
+  ---
+  <other help sources>
+
+<Context Menu> (right click)
+  Defined in editor, PyShelpyshellut
+    Cut
+    Copy
+    Paste
+    ---
+    Go to file/line (shell and output only)
+    Set Breakpoint (editor only)
+    Clear Breakpoint (editor only)
+  Defined in debugger
+    Go to source line
+    Show stack frame
+
+<No menu>
+Center Insert      # eEW.center_insert_event
+
+
+CODE STYLE -- Generally PEP 8.
+
+import
+------
+Put import at the top, unless there is a good reason otherwise.
+PEP 8 says to group stdlib, 3rd-party dependencies, and package imports.
+For idlelib, the groups are general stdlib, tkinter, and idlelib.
+Sort modules within each group, except that tkinter.ttk follows tkinter.
+Sort 'from idlelib import mod1' and 'from idlelib.mod2 import object'
+together by module, ignoring within module objects.
+Put 'import __main__' after other idlelib imports.
+
+Imports only needed for testing are put not at the top but in an
+htest function def or "if __name__ == '__main__'" clause.
+
+Within module imports like "from idlelib.mod import class" may cause
+circular imports to deadlock.  Even without this, circular imports may
+require at least one of the imports to be delayed until a function call.
diff --git a/rootfs/usr/lib/python3.8/idlelib/TODO.txt b/rootfs/usr/lib/python3.8/idlelib/TODO.txt
new file mode 100644
index 0000000..e2f1ac0
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/TODO.txt
@@ -0,0 +1,210 @@
+Original IDLE todo, much of it now outdated:
+============================================
+TO DO:
+
+- improve debugger:
+    - manage breakpoints globally, allow bp deletion, tbreak, cbreak etc.
+    - real object browser
+    - help on how to use it (a simple help button will do wonders)
+    - performance?  (updates of large sets of locals are slow)
+    - better integration of "debug module"
+    - debugger should be global resource (attached to flist, not to shell)
+    - fix the stupid bug where you need to step twice
+    - display class name in stack viewer entries for methods
+    - suppress tracing through IDLE internals (e.g. print) DONE
+    - add a button to suppress through a specific module or class or method
+    - more object inspection to stack viewer, e.g. to view all array items
+- insert the initial current directory into sys.path DONE
+- default directory attribute for each window instead of only for windows
+  that have an associated filename
+- command expansion from keywords, module contents, other buffers, etc.
+- "Recent documents" menu item DONE
+- Filter region command
+- Optional horizontal scroll bar
+- more Emacsisms:
+    - ^K should cut to buffer
+    - M-[, M-] to move by paragraphs
+    - incremental search?
+- search should indicate wrap-around in some way
+- restructure state sensitive code to avoid testing flags all the time
+- persistent user state (e.g. window and cursor positions, bindings)
+- make backups when saving
+- check file mtimes at various points
+- Pluggable interface with RCS/CVS/Perforce/Clearcase
+- better help?
+- don't open second class browser on same module (nor second path browser)
+- unify class and path browsers
+- Need to define a standard way whereby one can determine one is running
+  inside IDLE (needed for Tk mainloop, also handy for $PYTHONSTARTUP)
+- Add more utility methods for use by extensions (a la get_selection)
+- Way to run command in totally separate interpreter (fork+os.system?) DONE
+- Way to find definition of fully-qualified name:
+  In other words, select "UserDict.UserDict", hit some magic key and
+  it loads up UserDict.py and finds the first def or class for UserDict.
+- need a way to force colorization on/off
+- need a way to force auto-indent on/off
+
+Details:
+
+- ^O (on Unix -- open-line) should honor autoindent
+- after paste, show end of pasted text
+- on Windows, should turn short filename to long filename (not only in argv!)
+  (shouldn't this be done -- or undone -- by ntpath.normpath?)
+- new autoindent after colon even indents when the colon is in a comment!
+- sometimes forward slashes in pathname remain
+- sometimes star in window name remains in Windows menu
+- With unix bindings, ESC by itself is ignored
+- Sometimes for no apparent reason a selection from the cursor to the
+  end of the command buffer appears, which is hard to get rid of
+  because it stays when you are typing!
+- The Line/Col in the status bar can be wrong initially in PyShell DONE
+
+Structural problems:
+
+- too much knowledge in FileList about EditorWindow (for example)
+- should add some primitives for accessing the selection etc.
+  to repeat cumbersome code over and over
+
+======================================================================
+
+Jeff Bauer suggests:
+
+- Open Module doesn't appear to handle hierarchical packages.
+- Class browser should also allow hierarchical packages.
+- Open and Open Module could benefit from a history, DONE
+  either command line style, or Microsoft recent-file
+  style.
+- Add a Smalltalk-style inspector  (i.e. Tkinspect)
+
+The last suggestion is already a reality, but not yet
+integrated into IDLE.  I use a module called inspector.py,
+that used to be available from python.org(?)  It no longer
+appears to be in the contributed section, and the source
+has no author attribution.
+
+In any case, the code is useful for visually navigating
+an object's attributes, including its container hierarchy.
+
+    >>> from inspector import Tkinspect
+    >>> Tkinspect(None, myObject)
+
+Tkinspect could probably be extended and refined to
+integrate better into IDLE.
+
+======================================================================
+
+Comparison to PTUI
+------------------
+
++ PTUI's help is better (HTML!)
+
++ PTUI can attach a shell to any module
+
++ PTUI has some more I/O commands:
+  open multiple
+  append
+  examine (what's that?)
+
+======================================================================
+
+Notes after trying to run Grail
+-------------------------------
+
+- Grail does stuff to sys.path based on sys.argv[0]; you must set
+sys.argv[0] to something decent first (it is normally set to the path of
+the idle script).
+
+- Grail must be exec'ed in __main__ because that's imported by some
+other parts of Grail.
+
+- Grail uses a module called History and so does idle :-(
+
+======================================================================
+
+Robin Friedrich's items:
+
+Things I'd like to see:
+    - I'd like support for shift-click extending the selection. There's a
+      bug now that it doesn't work the first time you try it.
+    - Printing is needed. How hard can that be on Windows? FIRST CUT DONE
+    - The python-mode trick of autoindenting a line with <tab> is neat and
+      very handy.
+    - (someday) a spellchecker for docstrings and comments.
+    - a pagedown/up command key which moves to next class/def statement (top
+      level)
+    - split window capability
+    - DnD text relocation/copying
+
+Things I don't want to see.
+    - line numbers...  will probably slow things down way too much.
+    - Please use another icon for the tree browser leaf. The small snake
+      isn't cutting it.
+
+----------------------------------------------------------------------
+
+- Customizable views (multi-window or multi-pane).  (Markus Gritsch)
+
+- Being able to double click (maybe double right click) on a callable
+object in the editor which shows the source of the object, if
+possible.  (Gerrit Holl)
+
+- Hooks into the guts, like in Emacs.  (Mike Romberg)
+
+- Sharing the editor with a remote tutor.  (Martijn Faassen)
+
+- Multiple views on the same file.  (Tony J Ibbs)
+
+- Store breakpoints in a global (per-project) database (GvR); Dirk
+Heise adds: save some space-trimmed context and search around when
+reopening a file that might have been edited by someone else.
+
+- Capture menu events in extensions without changing the IDLE source.
+(Matthias Barmeier)
+
+- Use overlapping panels (a "notebook" in MFC terms I think) for info
+that doesn't need to be accessible simultaneously (e.g. HTML source
+and output).  Use multi-pane windows for info that does need to be
+shown together (e.g. class browser and source).  (Albert Brandl)
+
+- A project should invisibly track all symbols, for instant search,
+replace and cross-ref.  Projects should be allowed to span multiple
+directories, hosts, etc.  Project management files are placed in a
+directory you specify.  A global mapping between project names and
+project directories should exist [not so sure --GvR].  (Tim Peters)
+
+- Merge attr-tips and auto-expand.  (Mark Hammond, Tim Peters)
+
+- Python Shell should behave more like a "shell window" as users know
+it -- i.e. you can only edit the current command, and the cursor can't
+escape from the command area.  (Albert Brandl)
+
+- Set X11 class to "idle/Idle", set icon and title to something
+beginning with "idle" -- for window manangers.  (Randall Hopper)
+
+- Config files editable through a preferences dialog.  (me) DONE
+
+- Config files still editable outside the preferences dialog.
+(Randall Hopper) DONE
+
+- When you're editing a command in PyShell, and there are only blank
+lines below the cursor, hitting Return should ignore or delete those
+blank lines rather than deciding you're not on the last line.  (me)
+
+- Run command (F5 c.s.) should be more like Pythonwin's Run -- a
+dialog with options to give command line arguments, run the debugger,
+etc.  (me)
+
+- Shouldn't be able to delete part of the prompt (or any text before
+it) in the PyShell.  (Martijn Faassen)   DONE
+
+- Emacs style auto-fill (also smart about comments and strings).
+(Jeremy Hylton)
+
+- Output of Run Script should go to a separate output window, not to
+the shell window.  Output of separate runs should all go to the same
+window but clearly delimited.  (David Scherer) REJECT FIRST, LATTER DONE
+
+- GUI form designer to kick VB's butt.  (Robert Geiger) THAT'S NOT IDLE
+
+- Printing!  Possibly via generation of PDF files which the user must
+then send to the printer separately.  (Dinu Gherman)  FIRST CUT
diff --git a/rootfs/usr/lib/python3.8/idlelib/__init__.py b/rootfs/usr/lib/python3.8/idlelib/__init__.py
new file mode 100644
index 0000000..791ddea
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__init__.py
@@ -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.
diff --git a/rootfs/usr/lib/python3.8/idlelib/__main__.py b/rootfs/usr/lib/python3.8/idlelib/__main__.py
new file mode 100644
index 0000000..6349ec7
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__main__.py
@@ -0,0 +1,8 @@
+"""
+IDLE main entry point
+
+Run IDLE as python -m idlelib
+"""
+import idlelib.pyshell
+idlelib.pyshell.main()
+# This file does not work for 2.7; See issue 24212.
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/__init__.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/__init__.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..8053315
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/__init__.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/__init__.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/__init__.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..b9a0923
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/__init__.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/__init__.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/__init__.cpython-38.pyc
new file mode 100644
index 0000000..8053315
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/__init__.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/__main__.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/__main__.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..28246df
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/__main__.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/__main__.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/__main__.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..b0c5a34
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/__main__.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/__main__.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/__main__.cpython-38.pyc
new file mode 100644
index 0000000..28246df
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/__main__.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/autocomplete.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/autocomplete.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..acaf7b0
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/autocomplete.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/autocomplete.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/autocomplete.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..b364301
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/autocomplete.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/autocomplete.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/autocomplete.cpython-38.pyc
new file mode 100644
index 0000000..acaf7b0
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/autocomplete.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/autocomplete_w.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/autocomplete_w.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..deff1c5
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/autocomplete_w.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/autocomplete_w.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/autocomplete_w.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..40f13f5
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/autocomplete_w.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/autocomplete_w.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/autocomplete_w.cpython-38.pyc
new file mode 100644
index 0000000..c0866b1
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/autocomplete_w.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/autoexpand.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/autoexpand.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..6174933
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/autoexpand.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/autoexpand.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/autoexpand.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..a1a73b3
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/autoexpand.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/autoexpand.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/autoexpand.cpython-38.pyc
new file mode 100644
index 0000000..6174933
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/autoexpand.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/browser.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/browser.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..e7f5241
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/browser.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/browser.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/browser.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..52c2864
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/browser.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/browser.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/browser.cpython-38.pyc
new file mode 100644
index 0000000..e7f5241
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/browser.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/calltip.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/calltip.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..5d8418f
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/calltip.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/calltip.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/calltip.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..f6f5128
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/calltip.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/calltip.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/calltip.cpython-38.pyc
new file mode 100644
index 0000000..5d8418f
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/calltip.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/calltip_w.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/calltip_w.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..9602160
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/calltip_w.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/calltip_w.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/calltip_w.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..8dd93b1
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/calltip_w.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/calltip_w.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/calltip_w.cpython-38.pyc
new file mode 100644
index 0000000..9602160
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/calltip_w.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/codecontext.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/codecontext.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..3d6b85f
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/codecontext.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/codecontext.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/codecontext.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..23165c6
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/codecontext.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/codecontext.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/codecontext.cpython-38.pyc
new file mode 100644
index 0000000..2b7b68e
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/codecontext.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/colorizer.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/colorizer.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..aa35d7f
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/colorizer.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/colorizer.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/colorizer.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..0ecd125
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/colorizer.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/colorizer.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/colorizer.cpython-38.pyc
new file mode 100644
index 0000000..aa35d7f
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/colorizer.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/config.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/config.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..60ff702
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/config.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/config.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/config.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..e8bde70
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/config.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/config.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/config.cpython-38.pyc
new file mode 100644
index 0000000..60ff702
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/config.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/config_key.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/config_key.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..597aed2
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/config_key.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/config_key.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/config_key.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..c78969d
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/config_key.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/config_key.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/config_key.cpython-38.pyc
new file mode 100644
index 0000000..597aed2
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/config_key.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/configdialog.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/configdialog.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..7ff16bf
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/configdialog.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/configdialog.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/configdialog.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..5912bbb
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/configdialog.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/configdialog.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/configdialog.cpython-38.pyc
new file mode 100644
index 0000000..7ff16bf
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/configdialog.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/debugger.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/debugger.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..419dddf
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/debugger.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/debugger.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/debugger.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..76fa967
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/debugger.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/debugger.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/debugger.cpython-38.pyc
new file mode 100644
index 0000000..419dddf
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/debugger.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/debugger_r.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/debugger_r.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..355ad14
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/debugger_r.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/debugger_r.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/debugger_r.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..be83193
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/debugger_r.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/debugger_r.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/debugger_r.cpython-38.pyc
new file mode 100644
index 0000000..30fd85e
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/debugger_r.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/debugobj.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/debugobj.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..e756361
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/debugobj.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/debugobj.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/debugobj.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..e756361
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/debugobj.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/debugobj.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/debugobj.cpython-38.pyc
new file mode 100644
index 0000000..e756361
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/debugobj.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/debugobj_r.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/debugobj_r.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..313f860
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/debugobj_r.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/debugobj_r.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/debugobj_r.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..313f860
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/debugobj_r.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/debugobj_r.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/debugobj_r.cpython-38.pyc
new file mode 100644
index 0000000..313f860
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/debugobj_r.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/delegator.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/delegator.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..20b2832
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/delegator.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/delegator.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/delegator.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..2a04034
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/delegator.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/delegator.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/delegator.cpython-38.pyc
new file mode 100644
index 0000000..20b2832
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/delegator.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/dynoption.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/dynoption.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..284ef73
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/dynoption.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/dynoption.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/dynoption.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..d8f3d29
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/dynoption.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/dynoption.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/dynoption.cpython-38.pyc
new file mode 100644
index 0000000..284ef73
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/dynoption.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/editor.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/editor.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..07f1cac
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/editor.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/editor.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/editor.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..1f58c3f
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/editor.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/editor.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/editor.cpython-38.pyc
new file mode 100644
index 0000000..73bb822
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/editor.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/filelist.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/filelist.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..7439b5d
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/filelist.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/filelist.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/filelist.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..b075b93
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/filelist.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/filelist.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/filelist.cpython-38.pyc
new file mode 100644
index 0000000..309dc74
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/filelist.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/format.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/format.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..777f89b
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/format.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/format.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/format.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..397b173
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/format.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/format.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/format.cpython-38.pyc
new file mode 100644
index 0000000..777f89b
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/format.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/grep.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/grep.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..8327478
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/grep.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/grep.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/grep.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..7360592
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/grep.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/grep.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/grep.cpython-38.pyc
new file mode 100644
index 0000000..8327478
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/grep.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/help.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/help.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..a2f37b9
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/help.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/help.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/help.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..2c619f9
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/help.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/help.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/help.cpython-38.pyc
new file mode 100644
index 0000000..b65f0b2
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/help.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/help_about.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/help_about.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..6879c3d
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/help_about.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/help_about.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/help_about.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..61c7d91
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/help_about.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/help_about.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/help_about.cpython-38.pyc
new file mode 100644
index 0000000..6879c3d
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/help_about.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/history.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/history.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..6734acf
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/history.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/history.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/history.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..f3ea9af
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/history.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/history.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/history.cpython-38.pyc
new file mode 100644
index 0000000..6734acf
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/history.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/hyperparser.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/hyperparser.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..3060779
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/hyperparser.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/hyperparser.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/hyperparser.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..2a1d2c8
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/hyperparser.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/hyperparser.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/hyperparser.cpython-38.pyc
new file mode 100644
index 0000000..3060779
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/hyperparser.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/idle.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/idle.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..fdf9a02
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/idle.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/idle.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/idle.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..fdf9a02
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/idle.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/idle.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/idle.cpython-38.pyc
new file mode 100644
index 0000000..fdf9a02
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/idle.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/iomenu.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/iomenu.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..f6bd1ce
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/iomenu.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/iomenu.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/iomenu.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..6f0df72
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/iomenu.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/iomenu.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/iomenu.cpython-38.pyc
new file mode 100644
index 0000000..f6bd1ce
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/iomenu.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/macosx.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/macosx.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..11a9062
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/macosx.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/macosx.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/macosx.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..df56616
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/macosx.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/macosx.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/macosx.cpython-38.pyc
new file mode 100644
index 0000000..11a9062
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/macosx.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/mainmenu.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/mainmenu.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..73e6d1d
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/mainmenu.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/mainmenu.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/mainmenu.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..8f1ae85
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/mainmenu.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/mainmenu.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/mainmenu.cpython-38.pyc
new file mode 100644
index 0000000..73e6d1d
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/mainmenu.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/multicall.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/multicall.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..dacb0c5
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/multicall.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/multicall.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/multicall.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..d47e906
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/multicall.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/multicall.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/multicall.cpython-38.pyc
new file mode 100644
index 0000000..9460389
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/multicall.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/outwin.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/outwin.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..b6fb90f
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/outwin.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/outwin.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/outwin.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..cb4f1fa
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/outwin.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/outwin.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/outwin.cpython-38.pyc
new file mode 100644
index 0000000..cece3d2
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/outwin.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/parenmatch.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/parenmatch.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..28656da
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/parenmatch.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/parenmatch.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/parenmatch.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..42df285
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/parenmatch.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/parenmatch.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/parenmatch.cpython-38.pyc
new file mode 100644
index 0000000..28656da
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/parenmatch.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/pathbrowser.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/pathbrowser.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..33cc878
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/pathbrowser.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/pathbrowser.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/pathbrowser.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..5c0e859
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/pathbrowser.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/pathbrowser.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/pathbrowser.cpython-38.pyc
new file mode 100644
index 0000000..33cc878
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/pathbrowser.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/percolator.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/percolator.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..387d18c
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/percolator.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/percolator.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/percolator.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..387d18c
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/percolator.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/percolator.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/percolator.cpython-38.pyc
new file mode 100644
index 0000000..ec4ce8e
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/percolator.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/pyparse.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/pyparse.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..69489a7
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/pyparse.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/pyparse.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/pyparse.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..ed53664
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/pyparse.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/pyparse.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/pyparse.cpython-38.pyc
new file mode 100644
index 0000000..f38fb65
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/pyparse.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/pyshell.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/pyshell.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..9164e04
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/pyshell.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/pyshell.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/pyshell.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..7f7748c
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/pyshell.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/pyshell.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/pyshell.cpython-38.pyc
new file mode 100644
index 0000000..a761849
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/pyshell.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/query.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/query.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..7feefd2
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/query.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/query.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/query.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..eb06dc7
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/query.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/query.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/query.cpython-38.pyc
new file mode 100644
index 0000000..7feefd2
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/query.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/redirector.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/redirector.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..bcb41ce
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/redirector.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/redirector.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/redirector.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..77f3713
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/redirector.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/redirector.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/redirector.cpython-38.pyc
new file mode 100644
index 0000000..bcb41ce
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/redirector.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/replace.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/replace.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..88a64ad
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/replace.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/replace.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/replace.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..02a99a1
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/replace.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/replace.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/replace.cpython-38.pyc
new file mode 100644
index 0000000..88a64ad
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/replace.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/rpc.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/rpc.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..6670369
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/rpc.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/rpc.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/rpc.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..0acd3d6
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/rpc.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/rpc.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/rpc.cpython-38.pyc
new file mode 100644
index 0000000..711abe9
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/rpc.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/run.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/run.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..5f3a482
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/run.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/run.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/run.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..df3e25b
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/run.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/run.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/run.cpython-38.pyc
new file mode 100644
index 0000000..8ca349a
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/run.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/runscript.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/runscript.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..35e4e0e
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/runscript.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/runscript.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/runscript.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..32cdf20
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/runscript.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/runscript.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/runscript.cpython-38.pyc
new file mode 100644
index 0000000..35e4e0e
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/runscript.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/scrolledlist.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/scrolledlist.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..6101452
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/scrolledlist.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/scrolledlist.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/scrolledlist.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..6101452
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/scrolledlist.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/scrolledlist.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/scrolledlist.cpython-38.pyc
new file mode 100644
index 0000000..6101452
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/scrolledlist.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/search.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/search.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..5f63f59
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/search.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/search.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/search.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..ad8db17
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/search.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/search.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/search.cpython-38.pyc
new file mode 100644
index 0000000..5f63f59
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/search.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/searchbase.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/searchbase.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..80954e1
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/searchbase.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/searchbase.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/searchbase.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..22760e5
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/searchbase.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/searchbase.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/searchbase.cpython-38.pyc
new file mode 100644
index 0000000..80954e1
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/searchbase.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/searchengine.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/searchengine.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..66bce2a
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/searchengine.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/searchengine.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/searchengine.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..8088cf0
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/searchengine.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/searchengine.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/searchengine.cpython-38.pyc
new file mode 100644
index 0000000..66bce2a
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/searchengine.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/sidebar.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/sidebar.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..7c5fc74
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/sidebar.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/sidebar.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/sidebar.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..fc89193
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/sidebar.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/sidebar.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/sidebar.cpython-38.pyc
new file mode 100644
index 0000000..7c5fc74
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/sidebar.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/squeezer.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/squeezer.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..1612aa4
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/squeezer.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/squeezer.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/squeezer.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..c524134
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/squeezer.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/squeezer.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/squeezer.cpython-38.pyc
new file mode 100644
index 0000000..d4f1669
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/squeezer.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/stackviewer.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/stackviewer.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..70c833f
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/stackviewer.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/stackviewer.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/stackviewer.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..70c833f
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/stackviewer.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/stackviewer.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/stackviewer.cpython-38.pyc
new file mode 100644
index 0000000..70c833f
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/stackviewer.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/statusbar.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/statusbar.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..405de53
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/statusbar.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/statusbar.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/statusbar.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..405de53
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/statusbar.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/statusbar.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/statusbar.cpython-38.pyc
new file mode 100644
index 0000000..405de53
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/statusbar.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/textview.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/textview.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..7586cce
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/textview.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/textview.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/textview.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..0d224c2
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/textview.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/textview.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/textview.cpython-38.pyc
new file mode 100644
index 0000000..7586cce
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/textview.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/tooltip.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/tooltip.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..1620160
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/tooltip.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/tooltip.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/tooltip.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..819f22b
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/tooltip.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/tooltip.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/tooltip.cpython-38.pyc
new file mode 100644
index 0000000..1620160
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/tooltip.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/tree.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/tree.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..75d0e94
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/tree.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/tree.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/tree.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..0d47daa
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/tree.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/tree.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/tree.cpython-38.pyc
new file mode 100644
index 0000000..75d0e94
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/tree.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/undo.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/undo.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..7e91207
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/undo.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/undo.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/undo.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..7e91207
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/undo.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/undo.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/undo.cpython-38.pyc
new file mode 100644
index 0000000..7e91207
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/undo.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/window.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/window.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..4e323ee
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/window.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/window.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/window.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..4e323ee
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/window.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/window.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/window.cpython-38.pyc
new file mode 100644
index 0000000..4e323ee
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/window.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/zoomheight.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/zoomheight.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..721b93e
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/zoomheight.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/zoomheight.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/zoomheight.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..e7559d6
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/zoomheight.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/zoomheight.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/zoomheight.cpython-38.pyc
new file mode 100644
index 0000000..721b93e
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/zoomheight.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/zzdummy.cpython-38.opt-1.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/zzdummy.cpython-38.opt-1.pyc
new file mode 100644
index 0000000..e3372b8
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/zzdummy.cpython-38.opt-1.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/zzdummy.cpython-38.opt-2.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/zzdummy.cpython-38.opt-2.pyc
new file mode 100644
index 0000000..64b0399
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/zzdummy.cpython-38.opt-2.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/__pycache__/zzdummy.cpython-38.pyc b/rootfs/usr/lib/python3.8/idlelib/__pycache__/zzdummy.cpython-38.pyc
new file mode 100644
index 0000000..e3372b8
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/__pycache__/zzdummy.cpython-38.pyc
Binary files differ
diff --git a/rootfs/usr/lib/python3.8/idlelib/autocomplete.py b/rootfs/usr/lib/python3.8/idlelib/autocomplete.py
new file mode 100644
index 0000000..e1e9e17
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/autocomplete.py
@@ -0,0 +1,223 @@
+"""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
+
+# 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):
+        self.editwin = editwin
+        if editwin is not None:   # not in subprocess or no-gui test
+            self.text = editwin.text
+        self.autocompletewindow = None
+        # id of delayed call, and the index of the text insert when
+        # the delayed call was issued. If _delayed_completion_id is
+        # None, there is no delayed call.
+        self._delayed_completion_id = None
+        self._delayed_completion_index = None
+
+    @classmethod
+    def reload(cls):
+        cls.popupwait = idleConf.GetOption(
+            "extensions", "AutoComplete", "popupwait", type="int", default=0)
+
+    def _make_autocomplete_window(self):  # Makes mocking easier.
+        return autocomplete_w.AutoCompleteWindow(self.text)
+
+    def _remove_autocomplete_window(self, event=None):
+        if self.autocompletewindow:
+            self.autocompletewindow.hide_window()
+            self.autocompletewindow = None
+
+    def force_open_completions_event(self, event):
+        "(^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)
+                    kwds = (s for s in keyword.kwlist
+                            if s not in {'True', 'False', 'None'})
+                    bigl.extend(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)
diff --git a/rootfs/usr/lib/python3.8/idlelib/autocomplete_w.py b/rootfs/usr/lib/python3.8/idlelib/autocomplete_w.py
new file mode 100644
index 0000000..fe7a6be
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/autocomplete_w.py
@@ -0,0 +1,483 @@
+"""
+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):
+        # The widget (Text) on which we place the AutoCompleteWindow
+        self.widget = widget
+        # The widgets we create
+        self.autocompletewindow = self.listbox = self.scrollbar = None
+        # The default foreground and background of a selection. Saved because
+        # they are changed to the regular colors of list items when the
+        # completion start is not a prefix of the selected completion
+        self.origselforeground = self.origselbackground = None
+        # The list of completions
+        self.completions = None
+        # A list with more completions, or None
+        self.morecompletions = None
+        # The completion mode, either autocomplete.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.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
+
+        # Put widgets in place
+        self.autocompletewindow = acw = Toplevel(self.widget)
+        # Put it in a position so that it is not seen.
+        acw.wm_geometry("+10000+10000")
+        # Make it float
+        acw.wm_overrideredirect(1)
+        try:
+            # This command is only needed and available on Tk >= 8.4.0 for OSX
+            # Without it, call tips intrude on the typing process by grabbing
+            # the focus.
+            acw.tk.call("::tk::unsupported::MacWindowStyle", "style", acw._w,
+                        "help", "noActivates")
+        except TclError:
+            pass
+        self.scrollbar = scrollbar = Scrollbar(acw, orient=VERTICAL)
+        self.listbox = listbox = Listbox(acw, yscrollcommand=scrollbar.set,
+                                         exportselection=False)
+        for item in self.completions:
+            listbox.insert(END, item)
+        self.origselforeground = listbox.cget("selectforeground")
+        self.origselbackground = listbox.cget("selectbackground")
+        scrollbar.config(command=listbox.yview)
+        scrollbar.pack(side=RIGHT, fill=Y)
+        listbox.pack(side=LEFT, fill=BOTH, expand=True)
+        acw.lift()  # work around bug in Tk 8.5.18+ (issue #24570)
+
+        # Initialize the listbox selection
+        self.listbox.select_set(self._binary_search(self.start))
+        self._selection_changed()
+
+        # bind events
+        self.hideaid = acw.bind(HIDE_VIRTUAL_EVENT_NAME, self.hide_event)
+        self.hidewid = self.widget.bind(HIDE_VIRTUAL_EVENT_NAME, self.hide_event)
+        acw.event_add(HIDE_VIRTUAL_EVENT_NAME, HIDE_FOCUS_OUT_SEQUENCE)
+        for seq in HIDE_SEQUENCES:
+            self.widget.event_add(HIDE_VIRTUAL_EVENT_NAME, seq)
+
+        self.keypressid = self.widget.bind(KEYPRESS_VIRTUAL_EVENT_NAME,
+                                           self.keypress_event)
+        for seq in KEYPRESS_SEQUENCES:
+            self.widget.event_add(KEYPRESS_VIRTUAL_EVENT_NAME, seq)
+        self.keyreleaseid = self.widget.bind(KEYRELEASE_VIRTUAL_EVENT_NAME,
+                                             self.keyrelease_event)
+        self.widget.event_add(KEYRELEASE_VIRTUAL_EVENT_NAME,KEYRELEASE_SEQUENCE)
+        self.listupdateid = listbox.bind(LISTUPDATE_SEQUENCE,
+                                         self.listselect_event)
+        self.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
+        # Position the completion list window
+        text = self.widget
+        text.see(self.startindex)
+        x, y, cx, cy = text.bbox(self.startindex)
+        acw = self.autocompletewindow
+        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.update_idletasks()
+
+        if platform.system().startswith('Windows'):
+            # See issue 15786. When on Windows platform, Tk will misbehave
+            # to call winconfig_event multiple times, we need to prevent this,
+            # otherwise mouse button double click will not be able to used.
+            acw.unbind(WINCONFIG_SEQUENCE, self.winconfigid)
+            self.winconfigid = None
+
+        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
diff --git a/rootfs/usr/lib/python3.8/idlelib/autoexpand.py b/rootfs/usr/lib/python3.8/idlelib/autoexpand.py
new file mode 100644
index 0000000..92f5c84
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/autoexpand.py
@@ -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)
diff --git a/rootfs/usr/lib/python3.8/idlelib/browser.py b/rootfs/usr/lib/python3.8/idlelib/browser.py
new file mode 100644
index 0000000..3c3a53a
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/browser.py
@@ -0,0 +1,249 @@
+"""Module browser.
+
+XXX TO DO:
+
+- reparse when source changed (maybe just a button would be OK?)
+    (or recheck on window popup)
+- add popup menu with more options (e.g. doc strings, base classes, imports)
+- add base classes to class browser tree
+- finish removing limitation to x.py files (ModuleBrowserTreeItem)
+"""
+
+import os
+import pyclbr
+import sys
+
+from idlelib.config import idleConf
+from idlelib import pyshell
+from idlelib.tree import TreeNode, TreeItem, ScrolledCanvas
+from idlelib.window import ListedToplevel
+
+
+file_open = None  # Method...Item and Class...Item use this.
+# Normally pyshell.flist.open, but there is no pyshell.flist for htest.
+
+
+def transform_children(child_dict, modname=None):
+    """Transform a child dictionary to an ordered sequence of objects.
+
+    The dictionary maps names to pyclbr information objects.
+    Filter out imported objects.
+    Augment class names with bases.
+    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 type(sup) is type(''):
+                        sname = sup
+                    else:
+                        sname = sup.name
+                        if sup.module != obj.module:
+                            sname = f'{sup.module}.{sname}'
+                    supers.append(sname)
+                obj.name += '({})'.format(', '.join(supers))
+            obs.append(obj)
+    return 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 .py extension.  Used in
+                creating ModuleBrowserTreeItem as the rootnode for
+                the tree and subsequently in the children.
+        """
+        self.master = master
+        self.path = path
+        self._htest = _htest
+        self._utest = _utest
+        self.init()
+
+    def close(self, event=None):
+        "Dismiss the window and the tree nodes."
+        self.top.destroy()
+        self.node.destroy()
+
+    def init(self):
+        "Create browser tkinter widgets, including the tree."
+        global file_open
+        root = self.master
+        flist = (pyshell.flist if not (self._htest or self._utest)
+                 else pyshell.PyShellFileList(root))
+        file_open = flist.open
+        pyclbr._modules.clear()
+
+        # create top
+        self.top = top = ListedToplevel(root)
+        top.protocol("WM_DELETE_WINDOW", self.close)
+        top.bind("<Escape>", self.close)
+        if self._htest: # place dialog below parent if running htest
+            top.geometry("+%d+%d" %
+                (root.winfo_rootx(), root.winfo_rooty() + 200))
+        self.settitle()
+        top.focus_set()
+
+        # create scrolled canvas
+        theme = idleConf.CurrentTheme()
+        background = idleConf.GetHighlight(theme, 'normal')['background']
+        sc = ScrolledCanvas(top, bg=background, highlightthickness=0,
+                            takefocus=1)
+        sc.frame.pack(expand=1, fill="both")
+        item = self.rootnode()
+        self.node = node = TreeNode(sc.canvas, None, item)
+        if not self._utest:
+            node.update()
+            node.expand()
+
+    def settitle(self):
+        "Set the window title."
+        self.top.wm_title("Module Browser - " + os.path.basename(self.path))
+        self.top.wm_iconname("Module Browser")
+
+    def rootnode(self):
+        "Return a ModuleBrowserTreeItem as the root of the tree."
+        return ModuleBrowserTreeItem(self.path)
+
+
+class ModuleBrowserTreeItem(TreeItem):
+    """Browser tree for Python module.
+
+    Uses TreeItem as the basis for the structure of the tree.
+    Used by both browsers.
+    """
+
+    def __init__(self, file):
+        """Create a TreeItem for the file.
+
+        Args:
+            file: Full path and module name.
+        """
+        self.file = file
+
+    def GetText(self):
+        "Return the module name as the text string to display."
+        return os.path.basename(self.file)
+
+    def GetIconName(self):
+        "Return the name of the icon to display."
+        return "python"
+
+    def GetSubList(self):
+        "Return ChildBrowserTreeItems for children."
+        return [ChildBrowserTreeItem(obj) for obj in self.listchildren()]
+
+    def OnDoubleClick(self):
+        "Open a module in an editor window when double clicked."
+        if os.path.normcase(self.file[-3:]) != ".py":
+            return
+        if not os.path.exists(self.file):
+            return
+        file_open(self.file)
+
+    def IsExpandable(self):
+        "Return True if Python (.py) file."
+        return os.path.normcase(self.file[-3:]) == ".py"
+
+    def listchildren(self):
+        "Return sequenced classes and functions in the module."
+        dir, base = os.path.split(self.file)
+        name, ext = os.path.splitext(base)
+        if os.path.normcase(ext) != ".py":
+            return []
+        try:
+            tree = pyclbr.readmodule_ex(name, [dir] + sys.path)
+        except ImportError:
+            return []
+        return transform_children(tree, name)
+
+
+class ChildBrowserTreeItem(TreeItem):
+    """Browser tree for child nodes within the module.
+
+    Uses TreeItem as the basis for the structure of the tree.
+    """
+
+    def __init__(self, obj):
+        "Create a TreeItem for a pyclbr class/function object."
+        self.obj = obj
+        self.name = obj.name
+        self.isfunction = isinstance(obj, pyclbr.Function)
+
+    def GetText(self):
+        "Return the name of the function/class to display."
+        name = self.name
+        if self.isfunction:
+            return "def " + name + "(...)"
+        else:
+            return "class " + name
+
+    def GetIconName(self):
+        "Return the name of the icon to display."
+        if self.isfunction:
+            return "python"
+        else:
+            return "folder"
+
+    def IsExpandable(self):
+        "Return True if self.obj has nested objects."
+        return self.obj.children != {}
+
+    def GetSubList(self):
+        "Return ChildBrowserTreeItems for children."
+        return [ChildBrowserTreeItem(obj)
+                for obj in transform_children(self.obj.children)]
+
+    def OnDoubleClick(self):
+        "Open module with file_open and position to lineno."
+        try:
+            edit = file_open(self.obj.file)
+            edit.gotoline(self.obj.lineno)
+        except (OSError, AttributeError):
+            pass
+
+
+def _module_browser(parent): # htest #
+    if len(sys.argv) > 1:  # If pass file on command line.
+        file = sys.argv[1]
+    else:
+        file = __file__
+        # Add nested objects for htest.
+        class Nested_in_func(TreeNode):
+            def nested_in_class(): pass
+        def closure():
+            class Nested_in_closure: pass
+    ModuleBrowser(parent, file, _htest=True)
+
+if __name__ == "__main__":
+    if len(sys.argv) == 1:  # If pass file on command line, unittest fails.
+        from unittest import main
+        main('idlelib.idle_test.test_browser', verbosity=2, exit=False)
+    from idlelib.idle_test.htest import run
+    run(_module_browser)
diff --git a/rootfs/usr/lib/python3.8/idlelib/calltip.py b/rootfs/usr/lib/python3.8/idlelib/calltip.py
new file mode 100644
index 0000000..40bc5a0
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/calltip.py
@@ -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)
diff --git a/rootfs/usr/lib/python3.8/idlelib/calltip_w.py b/rootfs/usr/lib/python3.8/idlelib/calltip_w.py
new file mode 100644
index 0000000..1e0404a
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/calltip_w.py
@@ -0,0 +1,201 @@
+"""A call-tip window class for Tkinter/IDLE.
+
+After tooltip.py, which uses ideas gleaned from PySol.
+Used by calltip.py.
+"""
+from tkinter import Label, LEFT, SOLID, TclError
+
+from idlelib.tooltip import TooltipBase
+
+HIDE_EVENT = "<<calltipwindow-hide>>"
+HIDE_SEQUENCES = ("<Key-Escape>", "<FocusOut>")
+CHECKHIDE_EVENT = "<<calltipwindow-checkhide>>"
+CHECKHIDE_SEQUENCES = ("<KeyRelease>", "<ButtonRelease>")
+CHECKHIDE_TIME = 100  # milliseconds
+
+MARK_RIGHT = "calltipwindowregion_right"
+
+
+class CalltipWindow(TooltipBase):
+    """A call-tip widget for tkinter text widgets."""
+
+    def __init__(self, text_widget):
+        """Create a call-tip; shown by showtip().
+
+        text_widget: a Text widget with code for which call-tips are desired
+        """
+        # Note: The Text widget will be accessible as self.anchor_widget
+        super(CalltipWindow, self).__init__(text_widget)
+
+        self.label = self.text = None
+        self.parenline = self.parencol = self.lastline = None
+        self.hideid = self.checkhideid = None
+        self.checkhide_after_id = None
+
+    def get_position(self):
+        """Choose the position of the call-tip."""
+        curline = int(self.anchor_widget.index("insert").split('.')[0])
+        if curline == self.parenline:
+            anchor_index = (self.parenline, self.parencol)
+        else:
+            anchor_index = (curline, 0)
+        box = self.anchor_widget.bbox("%d.%d" % anchor_index)
+        if not box:
+            box = list(self.anchor_widget.bbox("insert"))
+            # align to left of window
+            box[0] = 0
+            box[2] = 0
+        return box[0] + 2, box[1] + box[3]
+
+    def position_window(self):
+        "Reposition the window if needed."
+        curline = int(self.anchor_widget.index("insert").split('.')[0])
+        if curline == self.lastline:
+            return
+        self.lastline = curline
+        self.anchor_widget.see("insert")
+        super(CalltipWindow, self).position_window()
+
+    def showtip(self, text, parenleft, parenright):
+        """Show the call-tip, bind events which will close it and reposition it.
+
+        text: the text to display in the call-tip
+        parenleft: index of the opening parenthesis in the text widget
+        parenright: index of the closing parenthesis in the text widget,
+                    or the end of the line if there is no closing parenthesis
+        """
+        # Only called in calltip.Calltip, where lines are truncated
+        self.text = text
+        if self.tipwindow or not self.text:
+            return
+
+        self.anchor_widget.mark_set(MARK_RIGHT, parenright)
+        self.parenline, self.parencol = map(
+            int, self.anchor_widget.index(parenleft).split("."))
+
+        super(CalltipWindow, self).showtip()
+
+        self._bind_events()
+
+    def showcontents(self):
+        """Create the call-tip widget."""
+        self.label = Label(self.tipwindow, text=self.text, justify=LEFT,
+                           background="#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(CalltipWindow, self).hidetip()
+
+    def _bind_events(self):
+        """Bind event handlers."""
+        self.checkhideid = self.anchor_widget.bind(CHECKHIDE_EVENT,
+                                                   self.checkhide_event)
+        for seq in CHECKHIDE_SEQUENCES:
+            self.anchor_widget.event_add(CHECKHIDE_EVENT, seq)
+        self.anchor_widget.after(CHECKHIDE_TIME, self.checkhide_event)
+        self.hideid = self.anchor_widget.bind(HIDE_EVENT,
+                                              self.hide_event)
+        for seq in HIDE_SEQUENCES:
+            self.anchor_widget.event_add(HIDE_EVENT, seq)
+
+    def _unbind_events(self):
+        """Unbind event handlers."""
+        for seq in CHECKHIDE_SEQUENCES:
+            self.anchor_widget.event_delete(CHECKHIDE_EVENT, seq)
+        self.anchor_widget.unbind(CHECKHIDE_EVENT, self.checkhideid)
+        self.checkhideid = None
+        for seq in HIDE_SEQUENCES:
+            self.anchor_widget.event_delete(HIDE_EVENT, seq)
+        self.anchor_widget.unbind(HIDE_EVENT, self.hideid)
+        self.hideid = None
+
+
+def _calltip_window(parent):  # htest #
+    from tkinter import Toplevel, Text, LEFT, BOTH
+
+    top = Toplevel(parent)
+    top.title("Test call-tips")
+    x, y = map(int, parent.geometry().split('+')[1:])
+    top.geometry("250x100+%d+%d" % (x + 175, y + 150))
+    text = Text(top)
+    text.pack(side=LEFT, fill=BOTH, expand=1)
+    text.insert("insert", "string.split")
+    top.update()
+
+    calltip = CalltipWindow(text)
+    def calltip_show(event):
+        calltip.showtip("(s='Hello world')", "insert", "end")
+    def calltip_hide(event):
+        calltip.hidetip()
+    text.event_add("<<calltip-show>>", "(")
+    text.event_add("<<calltip-hide>>", ")")
+    text.bind("<<calltip-show>>", calltip_show)
+    text.bind("<<calltip-hide>>", calltip_hide)
+
+    text.focus_set()
+
+if __name__ == '__main__':
+    from unittest import main
+    main('idlelib.idle_test.test_calltip_w', verbosity=2, exit=False)
+
+    from idlelib.idle_test.htest import run
+    run(_calltip_window)
diff --git a/rootfs/usr/lib/python3.8/idlelib/codecontext.py b/rootfs/usr/lib/python3.8/idlelib/codecontext.py
new file mode 100644
index 0000000..f2f44f5
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/codecontext.py
@@ -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.
diff --git a/rootfs/usr/lib/python3.8/idlelib/colorizer.py b/rootfs/usr/lib/python3.8/idlelib/colorizer.py
new file mode 100644
index 0000000..0aae177
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/colorizer.py
@@ -0,0 +1,340 @@
+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"
+    builtinlist = [str(name) for name in dir(builtins)
+                   if not name.startswith('_') and
+                   name not in keyword.kwlist]
+    builtin = r"([^.'\"\\#]\b|^)" + any("BUILTIN", builtinlist) + r"\b"
+    comment = any("COMMENT", [r"#[^\n]*"])
+    stringprefix = r"(?i:r|u|f|fr|rf|b|br|rb)?"
+    sqstring = stringprefix + r"'[^'\\\n]*(\\.[^'\\\n]*)*'?"
+    dqstring = stringprefix + r'"[^"\\\n]*(\\.[^"\\\n]*)*"?'
+    sq3string = stringprefix + r"'''[^'\\]*((\\.|'(?!''))[^'\\]*)*(''')?"
+    dq3string = stringprefix + r'"""[^"\\]*((\\.|"(?!""))[^"\\]*)*(""")?'
+    string = any("STRING", [sq3string, dq3string, sqstring, dqstring])
+    return (kw + "|" + builtin + "|" + comment + "|" + string +
+            "|" + any("SYNC", [r"\n"]))
+
+
+prog = re.compile(make_pat(), re.S)
+idprog = re.compile(r"\s+(\w+)", re.S)
+
+
+def color_config(text):
+    """Set color options of Text widget.
+
+    If ColorDelegator is used, this should be called first.
+    """
+    # Called from htest, TextFrame, Editor, and Turtledemo.
+    # Not automatic because ColorDelegator does not know 'text'.
+    theme = idleConf.CurrentTheme()
+    normal_colors = idleConf.GetHighlight(theme, 'normal')
+    cursor_color = idleConf.GetHighlight(theme, 'cursor')['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 True:
+            item = self.tag_nextrange("TODO", next)
+            if not item:
+                break
+            head, tail = item
+            self.tag_remove("SYNC", head, tail)
+            item = self.tag_prevrange("SYNC", head)
+            head = item[1] if item 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 = chars + line
+                m = self.prog.search(chars)
+                while m:
+                    for key, value in m.groupdict().items():
+                        if value:
+                            a, b = m.span(key)
+                            self.tag_add(key,
+                                         head + "+%dc" % a,
+                                         head + "+%dc" % b)
+                            if value in ("def", "class"):
+                                m1 = self.idprog.match(chars, b)
+                                if m1:
+                                    a, b = m1.span(1)
+                                    self.tag_add("DEFINITION",
+                                                 head + "+%dc" % a,
+                                                 head + "+%dc" % b)
+                    m = self.prog.search(chars, m.end())
+                if "SYNC" in self.tag_names(next + "-1c"):
+                    head = next
+                    chars = ""
+                else:
+                    ok = False
+                if not ok:
+                    # We're in an inconsistent state, and the call to
+                    # update may tell us to stop.  It may also change
+                    # the correct value for "next" (since this is a
+                    # line.col string, not a true mark).  So leave a
+                    # crumb telling the next invocation to resume here
+                    # in case update tells us to leave.
+                    self.tag_add("TODO", next)
+                self.update()
+                if self.stop_colorizing:
+                    if DEBUG: print("colorizing stopped")
+                    return
+
+    def removecolors(self):
+        "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.percolator import Percolator
+
+    top = Toplevel(parent)
+    top.title("Test ColorDelegator")
+    x, y = map(int, parent.geometry().split('+')[1:])
+    top.geometry("700x250+%d+%d" % (x + 20, y + 175))
+    source = (
+        "if True: int ('1') # keyword, builtin, string, comment\n"
+        "elif False: print(0)\n"
+        "else: float(None)\n"
+        "if iF + If + IF: 'keyword matching must respect case'\n"
+        "if'': x or''  # valid keyword-string no-space combinations\n"
+        "async def f(): await g()\n"
+        "# All valid prefixes for unicode and byte strings should be colored.\n"
+        "'x', '''x''', \"x\", \"\"\"x\"\"\"\n"
+        "r'x', u'x', R'x', U'x', f'x', F'x'\n"
+        "fr'x', Fr'x', fR'x', FR'x', rf'x', rF'x', Rf'x', RF'x'\n"
+        "b'x',B'x', br'x',Br'x',bR'x',BR'x', rb'x', rB'x',Rb'x',RB'x'\n"
+        "# Invalid combinations of legal characters should be half colored.\n"
+        "ur'x', ru'x', uf'x', fu'x', UR'x', ufr'x', rfu'x', xf'x', fx'x'\n"
+        )
+    text = Text(top, background="white")
+    text.pack(expand=1, fill="both")
+    text.insert("insert", source)
+    text.focus_set()
+
+    color_config(text)
+    p = Percolator(text)
+    d = ColorDelegator()
+    p.insertfilter(d)
+
+
+if __name__ == "__main__":
+    from unittest import main
+    main('idlelib.idle_test.test_colorizer', verbosity=2, exit=False)
+
+    from idlelib.idle_test.htest import run
+    run(_color_delegator)
diff --git a/rootfs/usr/lib/python3.8/idlelib/config-extensions.def b/rootfs/usr/lib/python3.8/idlelib/config-extensions.def
new file mode 100644
index 0000000..7e23fb0
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/config-extensions.def
@@ -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>
diff --git a/rootfs/usr/lib/python3.8/idlelib/config-highlight.def b/rootfs/usr/lib/python3.8/idlelib/config-highlight.def
new file mode 100644
index 0000000..a7b0433
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/config-highlight.def
@@ -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
diff --git a/rootfs/usr/lib/python3.8/idlelib/config-keys.def b/rootfs/usr/lib/python3.8/idlelib/config-keys.def
new file mode 100644
index 0000000..f71269b
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/config-keys.def
@@ -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>
diff --git a/rootfs/usr/lib/python3.8/idlelib/config-main.def b/rootfs/usr/lib/python3.8/idlelib/config-main.def
new file mode 100644
index 0000000..28ae941
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/config-main.def
@@ -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]
diff --git a/rootfs/usr/lib/python3.8/idlelib/config.py b/rootfs/usr/lib/python3.8/idlelib/config.py
new file mode 100644
index 0000000..04444a3
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/config.py
@@ -0,0 +1,911 @@
+"""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 useable defaults
+when a problem occurs in returning a requested configuration value back to
+idle. This is to allow IDLE to continue to function in spite of errors in
+the retrieval of config information. When a default is returned instead of
+a requested config value, a message is printed to stderr to aid in
+configuration problem notification and resolution.
+"""
+# TODOs added Oct 2014, tjr
+
+from configparser import ConfigParser
+import os
+import sys
+
+from tkinter.font import Font
+import idlelib
+
+class InvalidConfigType(Exception): pass
+class InvalidConfigSet(Exception): pass
+class 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
+        # self.blink_off_time = <first editor text>['insertofftime']
+        # See https:/bugs.python.org/issue4630, msg356516.
+
+        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 keyBindins a file or class attribute used for test above
+# and copied in function below.
+
+    former_extension_events = {  #  Those with user-configurable keys.
+        '<<force-open-completions>>', '<<expand-word>>',
+        '<<force-open-calltip>>', '<<flash-paren>>', '<<format-paragraph>>',
+         '<<run-module>>', '<<check-module>>', '<<zoom-height>>',
+         '<<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.
+        """
+        keyBindings={
+            '<<copy>>': ['<Control-c>', '<Control-C>'],
+            '<<cut>>': ['<Control-x>', '<Control-X>'],
+            '<<paste>>': ['<Control-v>', '<Control-V>'],
+            '<<beginning-of-line>>': ['<Control-a>', '<Home>'],
+            '<<center-insert>>': ['<Control-l>'],
+            '<<close-all-windows>>': ['<Control-q>'],
+            '<<close-window>>': ['<Alt-F4>'],
+            '<<do-nothing>>': ['<Control-x>'],
+            '<<end-of-file>>': ['<Control-d>'],
+            '<<python-docs>>': ['<F1>'],
+            '<<python-context-help>>': ['<Shift-F1>'],
+            '<<history-next>>': ['<Alt-n>'],
+            '<<history-previous>>': ['<Alt-p>'],
+            '<<interrupt-execution>>': ['<Control-c>'],
+            '<<view-restart>>': ['<F6>'],
+            '<<restart-shell>>': ['<Control-F6>'],
+            '<<open-class-browser>>': ['<Alt-c>'],
+            '<<open-module>>': ['<Alt-m>'],
+            '<<open-new-window>>': ['<Control-n>'],
+            '<<open-window-from-file>>': ['<Control-o>'],
+            '<<plain-newline-and-indent>>': ['<Control-j>'],
+            '<<print-window>>': ['<Control-p>'],
+            '<<redo>>': ['<Control-y>'],
+            '<<remove-selection>>': ['<Escape>'],
+            '<<save-copy-of-window-as-file>>': ['<Alt-Shift-S>'],
+            '<<save-window-as-file>>': ['<Alt-s>'],
+            '<<save-window>>': ['<Control-s>'],
+            '<<select-all>>': ['<Alt-a>'],
+            '<<toggle-auto-coloring>>': ['<Control-slash>'],
+            '<<undo>>': ['<Control-z>'],
+            '<<find-again>>': ['<Control-g>', '<F3>'],
+            '<<find-in-files>>': ['<Alt-F3>'],
+            '<<find-selection>>': ['<Control-F3>'],
+            '<<find>>': ['<Control-f>'],
+            '<<replace>>': ['<Control-h>'],
+            '<<goto-line>>': ['<Alt-g>'],
+            '<<smart-backspace>>': ['<Key-BackSpace>'],
+            '<<newline-and-indent>>': ['<Key-Return>', '<Key-KP_Enter>'],
+            '<<smart-indent>>': ['<Key-Tab>'],
+            '<<indent-region>>': ['<Control-Key-bracketright>'],
+            '<<dedent-region>>': ['<Control-Key-bracketleft>'],
+            '<<comment-region>>': ['<Alt-Key-3>'],
+            '<<uncomment-region>>': ['<Alt-Key-4>'],
+            '<<tabify-region>>': ['<Alt-Key-5>'],
+            '<<untabify-region>>': ['<Alt-Key-6>'],
+            '<<toggle-tabs>>': ['<Alt-Key-t>'],
+            '<<change-indentwidth>>': ['<Alt-Key-u>'],
+            '<<del-word-left>>': ['<Control-Key-BackSpace>'],
+            '<<del-word-right>>': ['<Control-Key-Delete>'],
+            '<<force-open-completions>>': ['<Control-Key-space>'],
+            '<<expand-word>>': ['<Alt-Key-slash>'],
+            '<<force-open-calltip>>': ['<Control-Key-backslash>'],
+            '<<flash-paren>>': ['<Control-Key-0>'],
+            '<<format-paragraph>>': ['<Alt-Key-q>'],
+            '<<run-module>>': ['<Key-F5>'],
+            '<<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):
+        global line, crc
+        txt = str(obj)
+        line += 1
+        crc = crc32(txt.encode(encoding='utf-8'), crc)
+        print(txt)
+        #print('***', line, crc, '***')  # Uncomment for diagnosis.
+
+    def dumpCfg(cfg):
+        print('\n', cfg, '\n')  # Cfg has variable '0xnnnnnnnn' address.
+        for key in sorted(cfg.keys()):
+            sections = cfg[key].sections()
+            sprint(key)
+            sprint(sections)
+            for section in sections:
+                options = cfg[key].options(section)
+                sprint(section)
+                sprint(options)
+                for option in options:
+                    sprint(option + ' = ' + cfg[key].Get(section, option))
+
+    dumpCfg(idleConf.defaultCfg)
+    dumpCfg(idleConf.userCfg)
+    print('\nlines = ', line, ', crc = ', crc, sep='')
+
+if __name__ == '__main__':
+    from unittest import main
+    main('idlelib.idle_test.test_config', verbosity=2, exit=False)
+
+    # Run revised _dump() as htest?
diff --git a/rootfs/usr/lib/python3.8/idlelib/config_key.py b/rootfs/usr/lib/python3.8/idlelib/config_key.py
new file mode 100644
index 0000000..9ca3a15
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/config_key.py
@@ -0,0 +1,327 @@
+"""
+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 GetKeysDialog(Toplevel):
+
+    # Dialog title for invalid key sequence
+    keyerror_title = 'Key Sequence Error'
+
+    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
+        """
+        Toplevel.__init__(self, parent)
+        self.withdraw()  # Hide while setting geometry.
+        self.configure(borderwidth=5)
+        self.resizable(height=False, width=False)
+        self.title(title)
+        self.transient(parent)
+        _setup_dialog(self)
+        self.grab_set()
+        self.protocol("WM_DELETE_WINDOW", self.cancel)
+        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()
+        self.update_idletasks()
+        self.geometry(
+                "+%d+%d" % (
+                    parent.winfo_rootx() +
+                    (parent.winfo_width()/2 - self.winfo_reqwidth()/2),
+                    parent.winfo_rooty() +
+                    ((parent.winfo_height()/2 - self.winfo_reqheight()/2)
+                    if not _htest else 150)
+                ) )  # Center dialog over parent (or below htest box).
+        if not _utest:
+            self.deiconify()  # Geometry set, unhide.
+            self.wait_window()
+
+    def showerror(self, *args, **kwargs):
+        # Make testing easier.  Replace in #30751.
+        messagebox.showerror(*args, **kwargs)
+
+    def create_widgets(self):
+        self.frame = frame = Frame(self, borderwidth=2, relief='sunken')
+        frame.pack(side='top', expand=True, fill='both')
+
+        frame_buttons = Frame(self)
+        frame_buttons.pack(side='bottom', fill='x')
+
+        self.button_ok = Button(frame_buttons, text='OK',
+                                width=8, command=self.ok)
+        self.button_ok.grid(row=0, column=0, padx=5, pady=5)
+        self.button_cancel = Button(frame_buttons, text='Cancel',
+                                   width=8, command=self.cancel)
+        self.button_cancel.grid(row=0, column=1, padx=5, pady=5)
+
+        # Basic entry key sequence.
+        self.frame_keyseq_basic = Frame(frame, 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(frame)
+        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(frame, 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(frame)
+        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(frame, 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, event=None):
+        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
+        self.grab_release()
+        self.destroy()
+
+    def cancel(self, event=None):
+        self.result = ''
+        self.grab_release()
+        self.destroy()
+
+    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
+
+
+if __name__ == '__main__':
+    from unittest import main
+    main('idlelib.idle_test.test_config_key', verbosity=2, exit=False)
+
+    from idlelib.idle_test.htest import run
+    run(GetKeysDialog)
diff --git a/rootfs/usr/lib/python3.8/idlelib/configdialog.py b/rootfs/usr/lib/python3.8/idlelib/configdialog.py
new file mode 100644
index 0000000..c52a04b
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/configdialog.py
@@ -0,0 +1,2390 @@
+"""IDLE Configuration Dialog: support user customization of IDLE by GUI
+
+Customize font faces, sizes, and colorization attributes.  Set indentation
+defaults.  Customize keybindings.  Colorization and keybindings can be
+saved as user defined sets.  Select startup options including shell/editor
+and default window size.  Define additional help sources.
+
+Note that tab width in IDLE is currently fixed at eight due to Tk issues.
+Refer to comments in EditorWindow autoindent code for details.
+
+"""
+import re
+
+from tkinter import (Toplevel, Listbox, Scale, Canvas,
+                     StringVar, BooleanVar, IntVar, TRUE, FALSE,
+                     TOP, BOTTOM, RIGHT, LEFT, SOLID, GROOVE,
+                     NONE, BOTH, X, Y, W, E, EW, NS, NSEW, NW,
+                     HORIZONTAL, VERTICAL, ANCHOR, ACTIVE, END)
+from tkinter.ttk import (Frame, LabelFrame, Button, Checkbutton, Entry, Label,
+                         OptionMenu, Notebook, Radiobutton, Scrollbar, Style)
+from tkinter import colorchooser
+import tkinter.font as tkfont
+from tkinter import messagebox
+
+from idlelib.config import idleConf, ConfigChanges
+from idlelib.config_key import GetKeysDialog
+from idlelib.dynoption import DynOptionMenu
+from idlelib import macosx
+from idlelib.query import SectionName, HelpSource
+from idlelib.textview import view_text
+from idlelib.autocomplete import AutoComplete
+from idlelib.codecontext import CodeContext
+from idlelib.parenmatch import ParenMatch
+from idlelib.format import FormatParagraph
+from idlelib.squeezer import Squeezer
+from idlelib.textview import ScrollableTextFrame
+
+changes = ConfigChanges()
+# Reload changed options in the following classes.
+reloadables = (AutoComplete, CodeContext, ParenMatch, FormatParagraph,
+               Squeezer)
+
+
+class ConfigDialog(Toplevel):
+    """Config dialog for IDLE.
+    """
+
+    def __init__(self, parent, title='', *, _htest=False, _utest=False):
+        """Show the tabbed dialog for user configuration.
+
+        Args:
+            parent - parent of this dialog
+            title - string which is the title of this popup dialog
+            _htest - bool, change box location when running htest
+            _utest - bool, don't wait_window when running unittest
+
+        Note: Focus set on font page fontlist.
+
+        Methods:
+            create_widgets
+            cancel: Bound to DELETE_WINDOW protocol.
+        """
+        Toplevel.__init__(self, parent)
+        self.parent = parent
+        if _htest:
+            parent.instance_dict = {}
+        if not _utest:
+            self.withdraw()
+
+        self.title(title or 'IDLE Preferences')
+        x = parent.winfo_rootx() + 20
+        y = parent.winfo_rooty() + (30 if not _htest else 150)
+        self.geometry(f'+{x}+{y}')
+        # Each theme element key is its display name.
+        # The first value of the tuple is the sample area tag name.
+        # The second value is the display name list sort index.
+        self.create_widgets()
+        self.resizable(height=FALSE, width=FALSE)
+        self.transient(parent)
+        self.protocol("WM_DELETE_WINDOW", self.cancel)
+        self.fontpage.fontlist.focus_set()
+        # XXX Decide whether to keep or delete these key bindings.
+        # Key bindings for this dialog.
+        # self.bind('<Escape>', self.Cancel) #dismiss dialog, no save
+        # self.bind('<Alt-a>', self.Apply) #apply changes, save
+        # self.bind('<F1>', self.Help) #context help
+        # Attach callbacks after loading config to avoid calling them.
+        tracers.attach()
+
+        if not _utest:
+            self.grab_set()
+            self.wm_deiconify()
+            self.wait_window()
+
+    def create_widgets(self):
+        """Create and place widgets for tabbed dialog.
+
+        Widgets Bound to self:
+            frame: encloses all other widgets
+            note: Notebook
+            highpage: HighPage
+            fontpage: FontPage
+            keyspage: KeysPage
+            genpage: GenPage
+            extpage: self.create_page_extensions
+
+        Methods:
+            create_action_buttons
+            load_configs: Load pages except for extensions.
+            activate_config_changes: Tell editors to reload.
+        """
+        self.frame = frame = Frame(self, padding="5px")
+        self.frame.grid(sticky="nwes")
+        self.note = note = Notebook(frame)
+        self.highpage = HighPage(note)
+        self.fontpage = FontPage(note, self.highpage)
+        self.keyspage = KeysPage(note)
+        self.genpage = GenPage(note)
+        self.extpage = self.create_page_extensions()
+        note.add(self.fontpage, text='Fonts/Tabs')
+        note.add(self.highpage, text='Highlights')
+        note.add(self.keyspage, text=' Keys ')
+        note.add(self.genpage, text=' General ')
+        note.add(self.extpage, text='Extensions')
+        note.enable_traversal()
+        note.pack(side=TOP, expand=TRUE, fill=BOTH)
+        self.create_action_buttons().pack(side=BOTTOM)
+
+    def create_action_buttons(self):
+        """Return frame of action buttons for dialog.
+
+        Methods:
+            ok
+            apply
+            cancel
+            help
+
+        Widget Structure:
+            outer: Frame
+                buttons: Frame
+                    (no assignment): Button (ok)
+                    (no assignment): Button (apply)
+                    (no assignment): Button (cancel)
+                    (no assignment): Button (help)
+                (no assignment): Frame
+        """
+        if macosx.isAquaTk():
+            # Changing the default padding on OSX results in unreadable
+            # text in the buttons.
+            padding_args = {}
+        else:
+            padding_args = {'padding': (6, 3)}
+        outer = Frame(self.frame, padding=2)
+        buttons_frame = Frame(outer, padding=2)
+        self.buttons = {}
+        for txt, cmd in (
+            ('Ok', self.ok),
+            ('Apply', self.apply),
+            ('Cancel', self.cancel),
+            ('Help', self.help)):
+            self.buttons[txt] = Button(buttons_frame, text=txt, command=cmd,
+                       takefocus=FALSE, **padding_args)
+            self.buttons[txt].pack(side=LEFT, padx=5)
+        # Add space above buttons.
+        Frame(outer, height=2, borderwidth=0).pack(side=TOP)
+        buttons_frame.pack(side=BOTTOM)
+        return outer
+
+    def ok(self):
+        """Apply config changes, then dismiss dialog.
+
+        Methods:
+            apply
+            destroy: inherited
+        """
+        self.apply()
+        self.destroy()
+
+    def apply(self):
+        """Apply config changes and leave dialog open.
+
+        Methods:
+            deactivate_current_config
+            save_all_changed_extensions
+            activate_config_changes
+        """
+        self.deactivate_current_config()
+        changes.save_all()
+        self.save_all_changed_extensions()
+        self.activate_config_changes()
+
+    def cancel(self):
+        """Dismiss config dialog.
+
+        Methods:
+            destroy: inherited
+        """
+        changes.clear()
+        self.destroy()
+
+    def destroy(self):
+        global font_sample_text
+        font_sample_text = self.fontpage.font_sample.get('1.0', 'end')
+        self.grab_release()
+        super().destroy()
+
+    def help(self):
+        """Create textview for config dialog help.
+
+        Attributes accessed:
+            note
+        Methods:
+            view_text: Method from textview module.
+        """
+        page = self.note.tab(self.note.select(), option='text').strip()
+        view_text(self, title='Help for IDLE preferences',
+                  contents=help_common+help_pages.get(page, ''))
+
+    def deactivate_current_config(self):
+        """Remove current key bindings.
+        Iterate over window instances defined in parent and remove
+        the keybindings.
+        """
+        # Before a config is saved, some cleanup of current
+        # config must be done - remove the previous keybindings.
+        win_instances = self.parent.instance_dict.keys()
+        for instance in win_instances:
+            instance.RemoveKeybindings()
+
+    def activate_config_changes(self):
+        """Apply configuration changes to current windows.
+
+        Dynamically update the current parent window instances
+        with some of the configuration changes.
+        """
+        win_instances = self.parent.instance_dict.keys()
+        for instance in win_instances:
+            instance.ResetColorizer()
+            instance.ResetFont()
+            instance.set_notabs_indentwidth()
+            instance.ApplyKeybindings()
+            instance.reset_help_menu_entries()
+            instance.update_cursor_blink()
+        for klass in reloadables:
+            klass.reload()
+
+    def create_page_extensions(self):
+        """Part of the config dialog used for configuring IDLE extensions.
+
+        This code is generic - it works for any and all IDLE extensions.
+
+        IDLE extensions save their configuration options using idleConf.
+        This code reads the current configuration using idleConf, supplies a
+        GUI interface to change the configuration values, and saves the
+        changes using idleConf.
+
+        Not all changes take effect immediately - some may require restarting IDLE.
+        This depends on each extension's implementation.
+
+        All values are treated as text, and it is up to the user to supply
+        reasonable values. The only exception to this are the 'enable*' options,
+        which are boolean, and can be toggled with a True/False button.
+
+        Methods:
+            load_extensions:
+            extension_selected: Handle selection from list.
+            create_extension_frame: Hold widgets for one extension.
+            set_extension_value: Set in userCfg['extensions'].
+            save_all_changed_extensions: Call extension page Save().
+        """
+        parent = self.parent
+        frame = Frame(self.note)
+        self.ext_defaultCfg = idleConf.defaultCfg['extensions']
+        self.ext_userCfg = idleConf.userCfg['extensions']
+        self.is_int = self.register(is_int)
+        self.load_extensions()
+        # Create widgets - a listbox shows all available extensions, with the
+        # controls for the extension selected in the listbox to the right.
+        self.extension_names = StringVar(self)
+        frame.rowconfigure(0, weight=1)
+        frame.columnconfigure(2, weight=1)
+        self.extension_list = Listbox(frame, listvariable=self.extension_names,
+                                      selectmode='browse')
+        self.extension_list.bind('<<ListboxSelect>>', self.extension_selected)
+        scroll = Scrollbar(frame, command=self.extension_list.yview)
+        self.extension_list.yscrollcommand=scroll.set
+        self.details_frame = LabelFrame(frame, width=250, height=250)
+        self.extension_list.grid(column=0, row=0, sticky='nws')
+        scroll.grid(column=1, row=0, sticky='ns')
+        self.details_frame.grid(column=2, row=0, sticky='nsew', padx=[10, 0])
+        frame.configure(padding=10)
+        self.config_frame = {}
+        self.current_extension = None
+
+        self.outerframe = self                      # TEMPORARY
+        self.tabbed_page_set = self.extension_list  # TEMPORARY
+
+        # Create the frame holding controls for each extension.
+        ext_names = ''
+        for ext_name in sorted(self.extensions):
+            self.create_extension_frame(ext_name)
+            ext_names = ext_names + '{' + ext_name + '} '
+        self.extension_names.set(ext_names)
+        self.extension_list.selection_set(0)
+        self.extension_selected(None)
+
+        return frame
+
+    def load_extensions(self):
+        "Fill self.extensions with data from the default and user configs."
+        self.extensions = {}
+        for ext_name in idleConf.GetExtensions(active_only=False):
+            # Former built-in extensions are already filtered out.
+            self.extensions[ext_name] = []
+
+        for ext_name in self.extensions:
+            opt_list = sorted(self.ext_defaultCfg.GetOptionList(ext_name))
+
+            # Bring 'enable' options to the beginning of the list.
+            enables = [opt_name for opt_name in opt_list
+                       if opt_name.startswith('enable')]
+            for opt_name in enables:
+                opt_list.remove(opt_name)
+            opt_list = enables + opt_list
+
+            for opt_name in opt_list:
+                def_str = self.ext_defaultCfg.Get(
+                        ext_name, opt_name, raw=True)
+                try:
+                    def_obj = {'True':True, 'False':False}[def_str]
+                    opt_type = 'bool'
+                except KeyError:
+                    try:
+                        def_obj = int(def_str)
+                        opt_type = 'int'
+                    except ValueError:
+                        def_obj = def_str
+                        opt_type = None
+                try:
+                    value = self.ext_userCfg.Get(
+                            ext_name, opt_name, type=opt_type, raw=True,
+                            default=def_obj)
+                except ValueError:  # Need this until .Get fixed.
+                    value = def_obj  # Bad values overwritten by entry.
+                var = StringVar(self)
+                var.set(str(value))
+
+                self.extensions[ext_name].append({'name': opt_name,
+                                                  'type': opt_type,
+                                                  'default': def_str,
+                                                  'value': value,
+                                                  'var': var,
+                                                 })
+
+    def extension_selected(self, event):
+        "Handle selection of an extension from the list."
+        newsel = self.extension_list.curselection()
+        if newsel:
+            newsel = self.extension_list.get(newsel)
+        if newsel is None or newsel != self.current_extension:
+            if self.current_extension:
+                self.details_frame.config(text='')
+                self.config_frame[self.current_extension].grid_forget()
+                self.current_extension = None
+        if newsel:
+            self.details_frame.config(text=newsel)
+            self.config_frame[newsel].grid(column=0, row=0, sticky='nsew')
+            self.current_extension = newsel
+
+    def create_extension_frame(self, ext_name):
+        """Create a frame holding the widgets to configure one extension"""
+        f = VerticalScrolledFrame(self.details_frame, height=250, width=250)
+        self.config_frame[ext_name] = f
+        entry_area = f.interior
+        # Create an entry for each configuration option.
+        for row, opt in enumerate(self.extensions[ext_name]):
+            # Create a row with a label and entry/checkbutton.
+            label = Label(entry_area, text=opt['name'])
+            label.grid(row=row, column=0, sticky=NW)
+            var = opt['var']
+            if opt['type'] == 'bool':
+                Checkbutton(entry_area, variable=var,
+                            onvalue='True', offvalue='False', width=8
+                            ).grid(row=row, column=1, sticky=W, padx=7)
+            elif opt['type'] == 'int':
+                Entry(entry_area, textvariable=var, validate='key',
+                      validatecommand=(self.is_int, '%P'), width=10
+                      ).grid(row=row, column=1, sticky=NSEW, padx=7)
+
+            else:  # type == 'str'
+                # Limit size to fit non-expanding space with larger font.
+                Entry(entry_area, textvariable=var, width=15
+                      ).grid(row=row, column=1, sticky=NSEW, padx=7)
+        return
+
+    def set_extension_value(self, section, opt):
+        """Return True if the configuration was added or changed.
+
+        If the value is the same as the default, then remove it
+        from user config file.
+        """
+        name = opt['name']
+        default = opt['default']
+        value = opt['var'].get().strip() or default
+        opt['var'].set(value)
+        # if self.defaultCfg.has_section(section):
+        # Currently, always true; if not, indent to return.
+        if (value == default):
+            return self.ext_userCfg.RemoveOption(section, name)
+        # Set the option.
+        return self.ext_userCfg.SetOption(section, name, value)
+
+    def save_all_changed_extensions(self):
+        """Save configuration changes to the user config file.
+
+        Attributes accessed:
+            extensions
+
+        Methods:
+            set_extension_value
+        """
+        has_changes = False
+        for ext_name in self.extensions:
+            options = self.extensions[ext_name]
+            for opt in options:
+                if self.set_extension_value(ext_name, opt):
+                    has_changes = True
+        if has_changes:
+            self.ext_userCfg.Save()
+
+
+# class TabPage(Frame):  # A template for Page classes.
+#     def __init__(self, master):
+#         super().__init__(master)
+#         self.create_page_tab()
+#         self.load_tab_cfg()
+#     def create_page_tab(self):
+#         # Define tk vars and register var and callback with tracers.
+#         # Create subframes and widgets.
+#         # Pack widgets.
+#     def load_tab_cfg(self):
+#         # Initialize widgets with data from idleConf.
+#     def var_changed_var_name():
+#         # For each tk var that needs other than default callback.
+#     def other_methods():
+#         # Define tab-specific behavior.
+
+font_sample_text = (
+    '<ASCII/Latin1>\n'
+    'AaBbCcDdEeFfGgHhIiJj\n1234567890#:+=(){}[]\n'
+    '\u00a2\u00a3\u00a5\u00a7\u00a9\u00ab\u00ae\u00b6\u00bd\u011e'
+    '\u00c0\u00c1\u00c2\u00c3\u00c4\u00c5\u00c7\u00d0\u00d8\u00df\n'
+    '\n<IPA,Greek,Cyrillic>\n'
+    '\u0250\u0255\u0258\u025e\u025f\u0264\u026b\u026e\u0270\u0277'
+    '\u027b\u0281\u0283\u0286\u028e\u029e\u02a2\u02ab\u02ad\u02af\n'
+    '\u0391\u03b1\u0392\u03b2\u0393\u03b3\u0394\u03b4\u0395\u03b5'
+    '\u0396\u03b6\u0397\u03b7\u0398\u03b8\u0399\u03b9\u039a\u03ba\n'
+    '\u0411\u0431\u0414\u0434\u0416\u0436\u041f\u043f\u0424\u0444'
+    '\u0427\u0447\u042a\u044a\u042d\u044d\u0460\u0464\u046c\u04dc\n'
+    '\n<Hebrew, Arabic>\n'
+    '\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9'
+    '\u05da\u05db\u05dc\u05dd\u05de\u05df\u05e0\u05e1\u05e2\u05e3\n'
+    '\u0627\u0628\u062c\u062f\u0647\u0648\u0632\u062d\u0637\u064a'
+    '\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\n'
+    '\n<Devanagari, Tamil>\n'
+    '\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f'
+    '\u0905\u0906\u0907\u0908\u0909\u090a\u090f\u0910\u0913\u0914\n'
+    '\u0be6\u0be7\u0be8\u0be9\u0bea\u0beb\u0bec\u0bed\u0bee\u0bef'
+    '\u0b85\u0b87\u0b89\u0b8e\n'
+    '\n<East Asian>\n'
+    '\u3007\u4e00\u4e8c\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\n'
+    '\u6c49\u5b57\u6f22\u5b57\u4eba\u6728\u706b\u571f\u91d1\u6c34\n'
+    '\uac00\ub0d0\ub354\ub824\ubaa8\ubd64\uc218\uc720\uc988\uce58\n'
+    '\u3042\u3044\u3046\u3048\u304a\u30a2\u30a4\u30a6\u30a8\u30aa\n'
+    )
+
+
+class FontPage(Frame):
+
+    def __init__(self, master, highpage):
+        super().__init__(master)
+        self.highlight_sample = highpage.highlight_sample
+        self.create_page_font_tab()
+        self.load_font_cfg()
+        self.load_tab_cfg()
+
+    def create_page_font_tab(self):
+        """Return frame of widgets for Font/Tabs tab.
+
+        Fonts: Enable users to provisionally change font face, size, or
+        boldness and to see the consequence of proposed choices.  Each
+        action set 3 options in changes structuree and changes the
+        corresponding aspect of the font sample on this page and
+        highlight sample on highlight page.
+
+        Function load_font_cfg initializes font vars and widgets from
+        idleConf entries and tk.
+
+        Fontlist: mouse button 1 click or up or down key invoke
+        on_fontlist_select(), which sets var font_name.
+
+        Sizelist: clicking the menubutton opens the dropdown menu. A
+        mouse button 1 click or return key sets var font_size.
+
+        Bold_toggle: clicking the box toggles var font_bold.
+
+        Changing any of the font vars invokes var_changed_font, which
+        adds all 3 font options to changes and calls set_samples.
+        Set_samples applies a new font constructed from the font vars to
+        font_sample and to highlight_sample on the highlight page.
+
+        Tabs: Enable users to change spaces entered for indent tabs.
+        Changing indent_scale value with the mouse sets Var space_num,
+        which invokes the default callback to add an entry to
+        changes.  Load_tab_cfg initializes space_num to default.
+
+        Widgets for FontPage(Frame):  (*) widgets bound to self
+            frame_font: LabelFrame
+                frame_font_name: Frame
+                    font_name_title: Label
+                    (*)fontlist: ListBox - font_name
+                    scroll_font: Scrollbar
+                frame_font_param: Frame
+                    font_size_title: Label
+                    (*)sizelist: DynOptionMenu - font_size
+                    (*)bold_toggle: Checkbutton - font_bold
+            frame_sample: LabelFrame
+                (*)font_sample: Label
+            frame_indent: LabelFrame
+                    indent_title: Label
+                    (*)indent_scale: Scale - space_num
+        """
+        self.font_name = tracers.add(StringVar(self), self.var_changed_font)
+        self.font_size = tracers.add(StringVar(self), self.var_changed_font)
+        self.font_bold = tracers.add(BooleanVar(self), self.var_changed_font)
+        self.space_num = tracers.add(IntVar(self), ('main', 'Indent', 'num-spaces'))
+
+        # Define frames and widgets.
+        frame_font = LabelFrame(
+                self, borderwidth=2, relief=GROOVE, text=' Shell/Editor Font ')
+        frame_sample = LabelFrame(
+                self, borderwidth=2, relief=GROOVE,
+                text=' Font Sample (Editable) ')
+        frame_indent = LabelFrame(
+                self, borderwidth=2, relief=GROOVE, text=' Indentation Width ')
+        # frame_font.
+        frame_font_name = Frame(frame_font)
+        frame_font_param = Frame(frame_font)
+        font_name_title = Label(
+                frame_font_name, justify=LEFT, text='Font Face :')
+        self.fontlist = Listbox(frame_font_name, height=15,
+                                takefocus=True, exportselection=FALSE)
+        self.fontlist.bind('<ButtonRelease-1>', self.on_fontlist_select)
+        self.fontlist.bind('<KeyRelease-Up>', self.on_fontlist_select)
+        self.fontlist.bind('<KeyRelease-Down>', self.on_fontlist_select)
+        scroll_font = Scrollbar(frame_font_name)
+        scroll_font.config(command=self.fontlist.yview)
+        self.fontlist.config(yscrollcommand=scroll_font.set)
+        font_size_title = Label(frame_font_param, text='Size :')
+        self.sizelist = DynOptionMenu(frame_font_param, self.font_size, None)
+        self.bold_toggle = Checkbutton(
+                frame_font_param, variable=self.font_bold,
+                onvalue=1, offvalue=0, text='Bold')
+        # frame_sample.
+        font_sample_frame = ScrollableTextFrame(frame_sample)
+        self.font_sample = font_sample_frame.text
+        self.font_sample.config(wrap=NONE, width=1, height=1)
+        self.font_sample.insert(END, font_sample_text)
+        # frame_indent.
+        indent_title = Label(
+                frame_indent, justify=LEFT,
+                text='Python Standard: 4 Spaces!')
+        self.indent_scale = Scale(
+                frame_indent, variable=self.space_num,
+                orient='horizontal', tickinterval=2, from_=2, to=16)
+
+        # Grid and pack widgets:
+        self.columnconfigure(1, weight=1)
+        self.rowconfigure(2, weight=1)
+        frame_font.grid(row=0, column=0, padx=5, pady=5)
+        frame_sample.grid(row=0, column=1, rowspan=3, padx=5, pady=5,
+                          sticky='nsew')
+        frame_indent.grid(row=1, column=0, padx=5, pady=5, sticky='ew')
+        # frame_font.
+        frame_font_name.pack(side=TOP, padx=5, pady=5, fill=X)
+        frame_font_param.pack(side=TOP, padx=5, pady=5, fill=X)
+        font_name_title.pack(side=TOP, anchor=W)
+        self.fontlist.pack(side=LEFT, expand=TRUE, fill=X)
+        scroll_font.pack(side=LEFT, fill=Y)
+        font_size_title.pack(side=LEFT, anchor=W)
+        self.sizelist.pack(side=LEFT, anchor=W)
+        self.bold_toggle.pack(side=LEFT, anchor=W, padx=20)
+        # frame_sample.
+        font_sample_frame.pack(expand=TRUE, fill=BOTH)
+        # frame_indent.
+        indent_title.pack(side=TOP, anchor=W, padx=5)
+        self.indent_scale.pack(side=TOP, padx=5, fill=X)
+
+    def load_font_cfg(self):
+        """Load current configuration settings for the font options.
+
+        Retrieve current font with idleConf.GetFont and font families
+        from tk. Setup fontlist and set font_name.  Setup sizelist,
+        which sets font_size.  Set font_bold.  Call set_samples.
+        """
+        configured_font = idleConf.GetFont(self, 'main', 'EditorWindow')
+        font_name = configured_font[0].lower()
+        font_size = configured_font[1]
+        font_bold  = configured_font[2]=='bold'
+
+        # Set sorted no-duplicate editor font selection list and font_name.
+        fonts = sorted(set(tkfont.families(self)))
+        for font in fonts:
+            self.fontlist.insert(END, font)
+        self.font_name.set(font_name)
+        lc_fonts = [s.lower() for s in fonts]
+        try:
+            current_font_index = lc_fonts.index(font_name)
+            self.fontlist.see(current_font_index)
+            self.fontlist.select_set(current_font_index)
+            self.fontlist.select_anchor(current_font_index)
+            self.fontlist.activate(current_font_index)
+        except ValueError:
+            pass
+        # Set font size dropdown.
+        self.sizelist.SetMenu(('7', '8', '9', '10', '11', '12', '13', '14',
+                               '16', '18', '20', '22', '25', '29', '34', '40'),
+                              font_size)
+        # Set font weight.
+        self.font_bold.set(font_bold)
+        self.set_samples()
+
+    def var_changed_font(self, *params):
+        """Store changes to font attributes.
+
+        When one font attribute changes, save them all, as they are
+        not independent from each other. In particular, when we are
+        overriding the default font, we need to write out everything.
+        """
+        value = self.font_name.get()
+        changes.add_option('main', 'EditorWindow', 'font', value)
+        value = self.font_size.get()
+        changes.add_option('main', 'EditorWindow', 'font-size', value)
+        value = self.font_bold.get()
+        changes.add_option('main', 'EditorWindow', 'font-bold', value)
+        self.set_samples()
+
+    def on_fontlist_select(self, event):
+        """Handle selecting a font from the list.
+
+        Event can result from either mouse click or Up or Down key.
+        Set font_name and example displays to selection.
+        """
+        font = self.fontlist.get(
+                ACTIVE if event.type.name == 'KeyRelease' else ANCHOR)
+        self.font_name.set(font.lower())
+
+    def set_samples(self, event=None):
+        """Update update both screen samples with the font settings.
+
+        Called on font initialization and change events.
+        Accesses font_name, font_size, and font_bold Variables.
+        Updates font_sample and highlight page highlight_sample.
+        """
+        font_name = self.font_name.get()
+        font_weight = tkfont.BOLD if self.font_bold.get() else tkfont.NORMAL
+        new_font = (font_name, self.font_size.get(), font_weight)
+        self.font_sample['font'] = new_font
+        self.highlight_sample['font'] = new_font
+
+    def load_tab_cfg(self):
+        """Load current configuration settings for the tab options.
+
+        Attributes updated:
+            space_num: Set to value from idleConf.
+        """
+        # Set indent sizes.
+        space_num = idleConf.GetOption(
+            'main', 'Indent', 'num-spaces', default=4, type='int')
+        self.space_num.set(space_num)
+
+    def var_changed_space_num(self, *params):
+        "Store change to indentation size."
+        value = self.space_num.get()
+        changes.add_option('main', 'Indent', 'num-spaces', value)
+
+
+class HighPage(Frame):
+
+    def __init__(self, master):
+        super().__init__(master)
+        self.cd = master.winfo_toplevel()
+        self.style = Style(master)
+        self.create_page_highlight()
+        self.load_theme_cfg()
+
+    def create_page_highlight(self):
+        """Return frame of widgets for Highlighting tab.
+
+        Enable users to provisionally change foreground and background
+        colors applied to textual tags.  Color mappings are stored in
+        complete listings called themes.  Built-in themes in
+        idlelib/config-highlight.def are fixed as far as the dialog is
+        concerned. Any theme can be used as the base for a new custom
+        theme, stored in .idlerc/config-highlight.cfg.
+
+        Function load_theme_cfg() initializes tk variables and theme
+        lists and calls paint_theme_sample() and set_highlight_target()
+        for the current theme.  Radiobuttons builtin_theme_on and
+        custom_theme_on toggle var theme_source, which controls if the
+        current set of colors are from a builtin or custom theme.
+        DynOptionMenus builtinlist and customlist contain lists of the
+        builtin and custom themes, respectively, and the current item
+        from each list is stored in vars builtin_name and custom_name.
+
+        Function paint_theme_sample() applies the colors from the theme
+        to the tags in text widget highlight_sample and then invokes
+        set_color_sample().  Function set_highlight_target() sets the state
+        of the radiobuttons fg_on and bg_on based on the tag and it also
+        invokes set_color_sample().
+
+        Function set_color_sample() sets the background color for the frame
+        holding the color selector.  This provides a larger visual of the
+        color for the current tag and plane (foreground/background).
+
+        Note: set_color_sample() is called from many places and is often
+        called more than once when a change is made.  It is invoked when
+        foreground or background is selected (radiobuttons), from
+        paint_theme_sample() (theme is changed or load_cfg is called), and
+        from set_highlight_target() (target tag is changed or load_cfg called).
+
+        Button delete_custom invokes delete_custom() to delete
+        a custom theme from idleConf.userCfg['highlight'] and changes.
+        Button save_custom invokes save_as_new_theme() which calls
+        get_new_theme_name() and create_new() to save a custom theme
+        and its colors to idleConf.userCfg['highlight'].
+
+        Radiobuttons fg_on and bg_on toggle var fg_bg_toggle to control
+        if the current selected color for a tag is for the foreground or
+        background.
+
+        DynOptionMenu targetlist contains a readable description of the
+        tags applied to Python source within IDLE.  Selecting one of the
+        tags from this list populates highlight_target, which has a callback
+        function set_highlight_target().
+
+        Text widget highlight_sample displays a block of text (which is
+        mock Python code) in which is embedded the defined tags and reflects
+        the color attributes of the current theme and changes for those tags.
+        Mouse button 1 allows for selection of a tag and updates
+        highlight_target with that tag value.
+
+        Note: The font in highlight_sample is set through the config in
+        the fonts tab.
+
+        In other words, a tag can be selected either from targetlist or
+        by clicking on the sample text within highlight_sample.  The
+        plane (foreground/background) is selected via the radiobutton.
+        Together, these two (tag and plane) control what color is
+        shown in set_color_sample() for the current theme.  Button set_color
+        invokes get_color() which displays a ColorChooser to change the
+        color for the selected tag/plane.  If a new color is picked,
+        it will be saved to changes and the highlight_sample and
+        frame background will be updated.
+
+        Tk Variables:
+            color: Color of selected target.
+            builtin_name: Menu variable for built-in theme.
+            custom_name: Menu variable for custom theme.
+            fg_bg_toggle: Toggle for foreground/background color.
+                Note: this has no callback.
+            theme_source: Selector for built-in or custom theme.
+            highlight_target: Menu variable for the highlight tag target.
+
+        Instance Data Attributes:
+            theme_elements: Dictionary of tags for text highlighting.
+                The key is the display name and the value is a tuple of
+                (tag name, display sort order).
+
+        Methods [attachment]:
+            load_theme_cfg: Load current highlight colors.
+            get_color: Invoke colorchooser [button_set_color].
+            set_color_sample_binding: Call set_color_sample [fg_bg_toggle].
+            set_highlight_target: set fg_bg_toggle, set_color_sample().
+            set_color_sample: Set frame background to target.
+            on_new_color_set: Set new color and add option.
+            paint_theme_sample: Recolor sample.
+            get_new_theme_name: Get from popup.
+            create_new: Combine theme with changes and save.
+            save_as_new_theme: Save [button_save_custom].
+            set_theme_type: Command for [theme_source].
+            delete_custom: Activate default [button_delete_custom].
+            save_new: Save to userCfg['theme'] (is function).
+
+        Widgets of highlights page frame:  (*) widgets bound to self
+            frame_custom: LabelFrame
+                (*)highlight_sample: Text
+                (*)frame_color_set: Frame
+                    (*)button_set_color: Button
+                    (*)targetlist: DynOptionMenu - highlight_target
+                frame_fg_bg_toggle: Frame
+                    (*)fg_on: Radiobutton - fg_bg_toggle
+                    (*)bg_on: Radiobutton - fg_bg_toggle
+                (*)button_save_custom: Button
+            frame_theme: LabelFrame
+                theme_type_title: Label
+                (*)builtin_theme_on: Radiobutton - theme_source
+                (*)custom_theme_on: Radiobutton - theme_source
+                (*)builtinlist: DynOptionMenu - builtin_name
+                (*)customlist: DynOptionMenu - custom_name
+                (*)button_delete_custom: Button
+                (*)theme_message: Label
+        """
+        self.theme_elements = {
+            'Normal Code or Text': ('normal', '00'),
+            'Code Context': ('context', '01'),
+            'Python Keywords': ('keyword', '02'),
+            'Python Definitions': ('definition', '03'),
+            'Python Builtins': ('builtin', '04'),
+            'Python Comments': ('comment', '05'),
+            'Python Strings': ('string', '06'),
+            'Selected Text': ('hilite', '07'),
+            'Found Text': ('hit', '08'),
+            'Cursor': ('cursor', '09'),
+            'Editor Breakpoint': ('break', '10'),
+            'Shell Prompt': ('console', '11'),
+            'Error Text': ('error', '12'),
+            'Shell User Output': ('stdout', '13'),
+            'Shell User Exception': ('stderr', '14'),
+            'Line Number': ('linenumber', '16'),
+            }
+        self.builtin_name = tracers.add(
+                StringVar(self), self.var_changed_builtin_name)
+        self.custom_name = tracers.add(
+                StringVar(self), self.var_changed_custom_name)
+        self.fg_bg_toggle = BooleanVar(self)
+        self.color = tracers.add(
+                StringVar(self), self.var_changed_color)
+        self.theme_source = tracers.add(
+                BooleanVar(self), self.var_changed_theme_source)
+        self.highlight_target = tracers.add(
+                StringVar(self), self.var_changed_highlight_target)
+
+        # Create widgets:
+        # body frame and section frames.
+        frame_custom = LabelFrame(self, borderwidth=2, relief=GROOVE,
+                                  text=' Custom Highlighting ')
+        frame_theme = LabelFrame(self, borderwidth=2, relief=GROOVE,
+                                 text=' Highlighting Theme ')
+        # frame_custom.
+        sample_frame = ScrollableTextFrame(
+                frame_custom, relief=SOLID, borderwidth=1)
+        text = self.highlight_sample = sample_frame.text
+        text.configure(
+                font=('courier', 12, ''), cursor='hand2', width=1, height=1,
+                takefocus=FALSE, highlightthickness=0, wrap=NONE)
+        # Prevent perhaps invisible selection of word or slice.
+        text.bind('<Double-Button-1>', lambda e: 'break')
+        text.bind('<B1-Motion>', lambda e: 'break')
+        string_tags=(
+            ('# Click selects item.', 'comment'), ('\n', 'normal'),
+            ('code context section', 'context'), ('\n', 'normal'),
+            ('| cursor', 'cursor'), ('\n', 'normal'),
+            ('def', 'keyword'), (' ', 'normal'),
+            ('func', 'definition'), ('(param):\n  ', 'normal'),
+            ('"Return None."', 'string'), ('\n  var0 = ', 'normal'),
+            ("'string'", 'string'), ('\n  var1 = ', 'normal'),
+            ("'selected'", 'hilite'), ('\n  var2 = ', 'normal'),
+            ("'found'", 'hit'), ('\n  var3 = ', 'normal'),
+            ('list', 'builtin'), ('(', 'normal'),
+            ('None', 'keyword'), (')\n', 'normal'),
+            ('  breakpoint("line")', 'break'), ('\n\n', 'normal'),
+            ('>>>', 'console'), (' 3.14**2\n', 'normal'),
+            ('9.8596', 'stdout'), ('\n', 'normal'),
+            ('>>>', 'console'), (' pri ', 'normal'),
+            ('n', 'error'), ('t(\n', 'normal'),
+            ('SyntaxError', 'stderr'), ('\n', 'normal'))
+        for string, tag in string_tags:
+            text.insert(END, string, tag)
+        n_lines = len(text.get('1.0', END).splitlines())
+        for lineno in range(1, n_lines):
+            text.insert(f'{lineno}.0',
+                        f'{lineno:{len(str(n_lines))}d} ',
+                        'linenumber')
+        for element in self.theme_elements:
+            def tem(event, elem=element):
+                # event.widget.winfo_top_level().highlight_target.set(elem)
+                self.highlight_target.set(elem)
+            text.tag_bind(
+                    self.theme_elements[element][0], '<ButtonPress-1>', tem)
+        text['state'] = 'disabled'
+        self.style.configure('frame_color_set.TFrame', borderwidth=1,
+                             relief='solid')
+        self.frame_color_set = Frame(frame_custom, style='frame_color_set.TFrame')
+        frame_fg_bg_toggle = Frame(frame_custom)
+        self.button_set_color = Button(
+                self.frame_color_set, text='Choose Color for :',
+                command=self.get_color)
+        self.targetlist = DynOptionMenu(
+                self.frame_color_set, self.highlight_target, None,
+                highlightthickness=0) #, command=self.set_highlight_targetBinding
+        self.fg_on = Radiobutton(
+                frame_fg_bg_toggle, variable=self.fg_bg_toggle, value=1,
+                text='Foreground', command=self.set_color_sample_binding)
+        self.bg_on = Radiobutton(
+                frame_fg_bg_toggle, variable=self.fg_bg_toggle, value=0,
+                text='Background', command=self.set_color_sample_binding)
+        self.fg_bg_toggle.set(1)
+        self.button_save_custom = Button(
+                frame_custom, text='Save as New Custom Theme',
+                command=self.save_as_new_theme)
+        # frame_theme.
+        theme_type_title = Label(frame_theme, text='Select : ')
+        self.builtin_theme_on = Radiobutton(
+                frame_theme, variable=self.theme_source, value=1,
+                command=self.set_theme_type, text='a Built-in Theme')
+        self.custom_theme_on = Radiobutton(
+                frame_theme, variable=self.theme_source, value=0,
+                command=self.set_theme_type, text='a Custom Theme')
+        self.builtinlist = DynOptionMenu(
+                frame_theme, self.builtin_name, None, command=None)
+        self.customlist = DynOptionMenu(
+                frame_theme, self.custom_name, None, command=None)
+        self.button_delete_custom = Button(
+                frame_theme, text='Delete Custom Theme',
+                command=self.delete_custom)
+        self.theme_message = Label(frame_theme, borderwidth=2)
+        # Pack widgets:
+        # body.
+        frame_custom.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
+        frame_theme.pack(side=TOP, padx=5, pady=5, fill=X)
+        # frame_custom.
+        self.frame_color_set.pack(side=TOP, padx=5, pady=5, fill=X)
+        frame_fg_bg_toggle.pack(side=TOP, padx=5, pady=0)
+        sample_frame.pack(
+                side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
+        self.button_set_color.pack(side=TOP, expand=TRUE, fill=X, padx=8, pady=4)
+        self.targetlist.pack(side=TOP, expand=TRUE, fill=X, padx=8, pady=3)
+        self.fg_on.pack(side=LEFT, anchor=E)
+        self.bg_on.pack(side=RIGHT, anchor=W)
+        self.button_save_custom.pack(side=BOTTOM, fill=X, padx=5, pady=5)
+        # frame_theme.
+        theme_type_title.pack(side=TOP, anchor=W, padx=5, pady=5)
+        self.builtin_theme_on.pack(side=TOP, anchor=W, padx=5)
+        self.custom_theme_on.pack(side=TOP, anchor=W, padx=5, pady=2)
+        self.builtinlist.pack(side=TOP, fill=X, padx=5, pady=5)
+        self.customlist.pack(side=TOP, fill=X, anchor=W, padx=5, pady=5)
+        self.button_delete_custom.pack(side=TOP, fill=X, padx=5, pady=5)
+        self.theme_message.pack(side=TOP, fill=X, pady=5)
+
+    def load_theme_cfg(self):
+        """Load current configuration settings for the theme options.
+
+        Based on the theme_source toggle, the theme is set as
+        either builtin or custom and the initial widget values
+        reflect the current settings from idleConf.
+
+        Attributes updated:
+            theme_source: Set from idleConf.
+            builtinlist: List of default themes from idleConf.
+            customlist: List of custom themes from idleConf.
+            custom_theme_on: Disabled if there are no custom themes.
+            custom_theme: Message with additional information.
+            targetlist: Create menu from self.theme_elements.
+
+        Methods:
+            set_theme_type
+            paint_theme_sample
+            set_highlight_target
+        """
+        # Set current theme type radiobutton.
+        self.theme_source.set(idleConf.GetOption(
+                'main', 'Theme', 'default', type='bool', default=1))
+        # Set current theme.
+        current_option = idleConf.CurrentTheme()
+        # Load available theme option menus.
+        if self.theme_source.get():  # Default theme selected.
+            item_list = idleConf.GetSectionList('default', 'highlight')
+            item_list.sort()
+            self.builtinlist.SetMenu(item_list, current_option)
+            item_list = idleConf.GetSectionList('user', 'highlight')
+            item_list.sort()
+            if not item_list:
+                self.custom_theme_on.state(('disabled',))
+                self.custom_name.set('- no custom themes -')
+            else:
+                self.customlist.SetMenu(item_list, item_list[0])
+        else:  # User theme selected.
+            item_list = idleConf.GetSectionList('user', 'highlight')
+            item_list.sort()
+            self.customlist.SetMenu(item_list, current_option)
+            item_list = idleConf.GetSectionList('default', 'highlight')
+            item_list.sort()
+            self.builtinlist.SetMenu(item_list, item_list[0])
+        self.set_theme_type()
+        # Load theme element option menu.
+        theme_names = list(self.theme_elements.keys())
+        theme_names.sort(key=lambda x: self.theme_elements[x][1])
+        self.targetlist.SetMenu(theme_names, theme_names[0])
+        self.paint_theme_sample()
+        self.set_highlight_target()
+
+    def var_changed_builtin_name(self, *params):
+        """Process new builtin theme selection.
+
+        Add the changed theme's name to the changed_items and recreate
+        the sample with the values from the selected theme.
+        """
+        old_themes = ('IDLE Classic', 'IDLE New')
+        value = self.builtin_name.get()
+        if value not in old_themes:
+            if idleConf.GetOption('main', 'Theme', 'name') not in old_themes:
+                changes.add_option('main', 'Theme', 'name', old_themes[0])
+            changes.add_option('main', 'Theme', 'name2', value)
+            self.theme_message['text'] = 'New theme, see Help'
+        else:
+            changes.add_option('main', 'Theme', 'name', value)
+            changes.add_option('main', 'Theme', 'name2', '')
+            self.theme_message['text'] = ''
+        self.paint_theme_sample()
+
+    def var_changed_custom_name(self, *params):
+        """Process new custom theme selection.
+
+        If a new custom theme is selected, add the name to the
+        changed_items and apply the theme to the sample.
+        """
+        value = self.custom_name.get()
+        if value != '- no custom themes -':
+            changes.add_option('main', 'Theme', 'name', value)
+            self.paint_theme_sample()
+
+    def var_changed_theme_source(self, *params):
+        """Process toggle between builtin and custom theme.
+
+        Update the default toggle value and apply the newly
+        selected theme type.
+        """
+        value = self.theme_source.get()
+        changes.add_option('main', 'Theme', 'default', value)
+        if value:
+            self.var_changed_builtin_name()
+        else:
+            self.var_changed_custom_name()
+
+    def var_changed_color(self, *params):
+        "Process change to color choice."
+        self.on_new_color_set()
+
+    def var_changed_highlight_target(self, *params):
+        "Process selection of new target tag for highlighting."
+        self.set_highlight_target()
+
+    def set_theme_type(self):
+        """Set available screen options based on builtin or custom theme.
+
+        Attributes accessed:
+            theme_source
+
+        Attributes updated:
+            builtinlist
+            customlist
+            button_delete_custom
+            custom_theme_on
+
+        Called from:
+            handler for builtin_theme_on and custom_theme_on
+            delete_custom
+            create_new
+            load_theme_cfg
+        """
+        if self.theme_source.get():
+            self.builtinlist['state'] = 'normal'
+            self.customlist['state'] = 'disabled'
+            self.button_delete_custom.state(('disabled',))
+        else:
+            self.builtinlist['state'] = 'disabled'
+            self.custom_theme_on.state(('!disabled',))
+            self.customlist['state'] = 'normal'
+            self.button_delete_custom.state(('!disabled',))
+
+    def get_color(self):
+        """Handle button to select a new color for the target tag.
+
+        If a new color is selected while using a builtin theme, a
+        name must be supplied to create a custom theme.
+
+        Attributes accessed:
+            highlight_target
+            frame_color_set
+            theme_source
+
+        Attributes updated:
+            color
+
+        Methods:
+            get_new_theme_name
+            create_new
+        """
+        target = self.highlight_target.get()
+        prev_color = self.style.lookup(self.frame_color_set['style'],
+                                       'background')
+        rgbTuplet, color_string = colorchooser.askcolor(
+                parent=self, title='Pick new color for : '+target,
+                initialcolor=prev_color)
+        if color_string and (color_string != prev_color):
+            # User didn't cancel and they chose a new color.
+            if self.theme_source.get():  # Current theme is a built-in.
+                message = ('Your changes will be saved as a new Custom Theme. '
+                           'Enter a name for your new Custom Theme below.')
+                new_theme = self.get_new_theme_name(message)
+                if not new_theme:  # User cancelled custom theme creation.
+                    return
+                else:  # Create new custom theme based on previously active theme.
+                    self.create_new(new_theme)
+                    self.color.set(color_string)
+            else:  # Current theme is user defined.
+                self.color.set(color_string)
+
+    def on_new_color_set(self):
+        "Display sample of new color selection on the dialog."
+        new_color = self.color.get()
+        self.style.configure('frame_color_set.TFrame', background=new_color)
+        plane = 'foreground' if self.fg_bg_toggle.get() else 'background'
+        sample_element = self.theme_elements[self.highlight_target.get()][0]
+        self.highlight_sample.tag_config(sample_element, **{plane: new_color})
+        theme = self.custom_name.get()
+        theme_element = sample_element + '-' + plane
+        changes.add_option('highlight', theme, theme_element, new_color)
+
+    def get_new_theme_name(self, message):
+        "Return name of new theme from query popup."
+        used_names = (idleConf.GetSectionList('user', 'highlight') +
+                idleConf.GetSectionList('default', 'highlight'))
+        new_theme = SectionName(
+                self, 'New Custom Theme', message, used_names).result
+        return new_theme
+
+    def save_as_new_theme(self):
+        """Prompt for new theme name and create the theme.
+
+        Methods:
+            get_new_theme_name
+            create_new
+        """
+        new_theme_name = self.get_new_theme_name('New Theme Name:')
+        if new_theme_name:
+            self.create_new(new_theme_name)
+
+    def create_new(self, new_theme_name):
+        """Create a new custom theme with the given name.
+
+        Create the new theme based on the previously active theme
+        with the current changes applied.  Once it is saved, then
+        activate the new theme.
+
+        Attributes accessed:
+            builtin_name
+            custom_name
+
+        Attributes updated:
+            customlist
+            theme_source
+
+        Method:
+            save_new
+            set_theme_type
+        """
+        if self.theme_source.get():
+            theme_type = 'default'
+            theme_name = self.builtin_name.get()
+        else:
+            theme_type = 'user'
+            theme_name = self.custom_name.get()
+        new_theme = idleConf.GetThemeDict(theme_type, theme_name)
+        # Apply any of the old theme's unsaved changes to the new theme.
+        if theme_name in changes['highlight']:
+            theme_changes = changes['highlight'][theme_name]
+            for element in theme_changes:
+                new_theme[element] = theme_changes[element]
+        # Save the new theme.
+        self.save_new(new_theme_name, new_theme)
+        # Change GUI over to the new theme.
+        custom_theme_list = idleConf.GetSectionList('user', 'highlight')
+        custom_theme_list.sort()
+        self.customlist.SetMenu(custom_theme_list, new_theme_name)
+        self.theme_source.set(0)
+        self.set_theme_type()
+
+    def set_highlight_target(self):
+        """Set fg/bg toggle and color based on highlight tag target.
+
+        Instance variables accessed:
+            highlight_target
+
+        Attributes updated:
+            fg_on
+            bg_on
+            fg_bg_toggle
+
+        Methods:
+            set_color_sample
+
+        Called from:
+            var_changed_highlight_target
+            load_theme_cfg
+        """
+        if self.highlight_target.get() == 'Cursor':  # bg not possible
+            self.fg_on.state(('disabled',))
+            self.bg_on.state(('disabled',))
+            self.fg_bg_toggle.set(1)
+        else:  # Both fg and bg can be set.
+            self.fg_on.state(('!disabled',))
+            self.bg_on.state(('!disabled',))
+            self.fg_bg_toggle.set(1)
+        self.set_color_sample()
+
+    def set_color_sample_binding(self, *args):
+        """Change color sample based on foreground/background toggle.
+
+        Methods:
+            set_color_sample
+        """
+        self.set_color_sample()
+
+    def set_color_sample(self):
+        """Set the color of the frame background to reflect the selected target.
+
+        Instance variables accessed:
+            theme_elements
+            highlight_target
+            fg_bg_toggle
+            highlight_sample
+
+        Attributes updated:
+            frame_color_set
+        """
+        # Set the color sample area.
+        tag = self.theme_elements[self.highlight_target.get()][0]
+        plane = 'foreground' if self.fg_bg_toggle.get() else 'background'
+        color = self.highlight_sample.tag_cget(tag, plane)
+        self.style.configure('frame_color_set.TFrame', background=color)
+
+    def paint_theme_sample(self):
+        """Apply the theme colors to each element tag in the sample text.
+
+        Instance attributes accessed:
+            theme_elements
+            theme_source
+            builtin_name
+            custom_name
+
+        Attributes updated:
+            highlight_sample: Set the tag elements to the theme.
+
+        Methods:
+            set_color_sample
+
+        Called from:
+            var_changed_builtin_name
+            var_changed_custom_name
+            load_theme_cfg
+        """
+        if self.theme_source.get():  # Default theme
+            theme = self.builtin_name.get()
+        else:  # User theme
+            theme = self.custom_name.get()
+        for element_title in self.theme_elements:
+            element = self.theme_elements[element_title][0]
+            colors = idleConf.GetHighlight(theme, element)
+            if element == 'cursor':  # Cursor sample needs special painting.
+                colors['background'] = idleConf.GetHighlight(
+                        theme, 'normal')['background']
+            # Handle any unsaved changes to this theme.
+            if theme in changes['highlight']:
+                theme_dict = changes['highlight'][theme]
+                if element + '-foreground' in theme_dict:
+                    colors['foreground'] = theme_dict[element + '-foreground']
+                if element + '-background' in theme_dict:
+                    colors['background'] = theme_dict[element + '-background']
+            self.highlight_sample.tag_config(element, **colors)
+        self.set_color_sample()
+
+    def save_new(self, theme_name, theme):
+        """Save a newly created theme to idleConf.
+
+        theme_name - string, the name of the new theme
+        theme - dictionary containing the new theme
+        """
+        idleConf.userCfg['highlight'].AddSection(theme_name)
+        for element in theme:
+            value = theme[element]
+            idleConf.userCfg['highlight'].SetOption(theme_name, element, value)
+
+    def askyesno(self, *args, **kwargs):
+        # Make testing easier.  Could change implementation.
+        return messagebox.askyesno(*args, **kwargs)
+
+    def delete_custom(self):
+        """Handle event to delete custom theme.
+
+        The current theme is deactivated and the default theme is
+        activated.  The custom theme is permanently removed from
+        the config file.
+
+        Attributes accessed:
+            custom_name
+
+        Attributes updated:
+            custom_theme_on
+            customlist
+            theme_source
+            builtin_name
+
+        Methods:
+            deactivate_current_config
+            save_all_changed_extensions
+            activate_config_changes
+            set_theme_type
+        """
+        theme_name = self.custom_name.get()
+        delmsg = 'Are you sure you wish to delete the theme %r ?'
+        if not self.askyesno(
+                'Delete Theme',  delmsg % theme_name, parent=self):
+            return
+        self.cd.deactivate_current_config()
+        # Remove theme from changes, config, and file.
+        changes.delete_section('highlight', theme_name)
+        # Reload user theme list.
+        item_list = idleConf.GetSectionList('user', 'highlight')
+        item_list.sort()
+        if not item_list:
+            self.custom_theme_on.state(('disabled',))
+            self.customlist.SetMenu(item_list, '- no custom themes -')
+        else:
+            self.customlist.SetMenu(item_list, item_list[0])
+        # Revert to default theme.
+        self.theme_source.set(idleConf.defaultCfg['main'].Get('Theme', 'default'))
+        self.builtin_name.set(idleConf.defaultCfg['main'].Get('Theme', 'name'))
+        # User can't back out of these changes, they must be applied now.
+        changes.save_all()
+        self.cd.save_all_changed_extensions()
+        self.cd.activate_config_changes()
+        self.set_theme_type()
+
+
+class KeysPage(Frame):
+
+    def __init__(self, master):
+        super().__init__(master)
+        self.cd = master.winfo_toplevel()
+        self.create_page_keys()
+        self.load_key_cfg()
+
+    def create_page_keys(self):
+        """Return frame of widgets for Keys tab.
+
+        Enable users to provisionally change both individual and sets of
+        keybindings (shortcut keys). Except for features implemented as
+        extensions, keybindings are stored in complete sets called
+        keysets. Built-in keysets in idlelib/config-keys.def are fixed
+        as far as the dialog is concerned. Any keyset can be used as the
+        base for a new custom keyset, stored in .idlerc/config-keys.cfg.
+
+        Function load_key_cfg() initializes tk variables and keyset
+        lists and calls load_keys_list for the current keyset.
+        Radiobuttons builtin_keyset_on and custom_keyset_on toggle var
+        keyset_source, which controls if the current set of keybindings
+        are from a builtin or custom keyset. DynOptionMenus builtinlist
+        and customlist contain lists of the builtin and custom keysets,
+        respectively, and the current item from each list is stored in
+        vars builtin_name and custom_name.
+
+        Button delete_custom_keys invokes delete_custom_keys() to delete
+        a custom keyset from idleConf.userCfg['keys'] and changes.  Button
+        save_custom_keys invokes save_as_new_key_set() which calls
+        get_new_keys_name() and create_new_key_set() to save a custom keyset
+        and its keybindings to idleConf.userCfg['keys'].
+
+        Listbox bindingslist contains all of the keybindings for the
+        selected keyset.  The keybindings are loaded in load_keys_list()
+        and are pairs of (event, [keys]) where keys can be a list
+        of one or more key combinations to bind to the same event.
+        Mouse button 1 click invokes on_bindingslist_select(), which
+        allows button_new_keys to be clicked.
+
+        So, an item is selected in listbindings, which activates
+        button_new_keys, and clicking button_new_keys calls function
+        get_new_keys().  Function get_new_keys() gets the key mappings from the
+        current keyset for the binding event item that was selected.  The
+        function then displays another dialog, GetKeysDialog, with the
+        selected binding event and current keys and allows new key sequences
+        to be entered for that binding event.  If the keys aren't
+        changed, nothing happens.  If the keys are changed and the keyset
+        is a builtin, function get_new_keys_name() will be called
+        for input of a custom keyset name.  If no name is given, then the
+        change to the keybinding will abort and no updates will be made.  If
+        a custom name is entered in the prompt or if the current keyset was
+        already custom (and thus didn't require a prompt), then
+        idleConf.userCfg['keys'] is updated in function create_new_key_set()
+        with the change to the event binding.  The item listing in bindingslist
+        is updated with the new keys.  Var keybinding is also set which invokes
+        the callback function, var_changed_keybinding, to add the change to
+        the 'keys' or 'extensions' changes tracker based on the binding type.
+
+        Tk Variables:
+            keybinding: Action/key bindings.
+
+        Methods:
+            load_keys_list: Reload active set.
+            create_new_key_set: Combine active keyset and changes.
+            set_keys_type: Command for keyset_source.
+            save_new_key_set: Save to idleConf.userCfg['keys'] (is function).
+            deactivate_current_config: Remove keys bindings in editors.
+
+        Widgets for KeysPage(frame):  (*) widgets bound to self
+            frame_key_sets: LabelFrame
+                frames[0]: Frame
+                    (*)builtin_keyset_on: Radiobutton - var keyset_source
+                    (*)custom_keyset_on: Radiobutton - var keyset_source
+                    (*)builtinlist: DynOptionMenu - var builtin_name,
+                            func keybinding_selected
+                    (*)customlist: DynOptionMenu - var custom_name,
+                            func keybinding_selected
+                    (*)keys_message: Label
+                frames[1]: Frame
+                    (*)button_delete_custom_keys: Button - delete_custom_keys
+                    (*)button_save_custom_keys: Button -  save_as_new_key_set
+            frame_custom: LabelFrame
+                frame_target: Frame
+                    target_title: Label
+                    scroll_target_y: Scrollbar
+                    scroll_target_x: Scrollbar
+                    (*)bindingslist: ListBox - on_bindingslist_select
+                    (*)button_new_keys: Button - get_new_keys & ..._name
+        """
+        self.builtin_name = tracers.add(
+                StringVar(self), self.var_changed_builtin_name)
+        self.custom_name = tracers.add(
+                StringVar(self), self.var_changed_custom_name)
+        self.keyset_source = tracers.add(
+                BooleanVar(self), self.var_changed_keyset_source)
+        self.keybinding = tracers.add(
+                StringVar(self), self.var_changed_keybinding)
+
+        # Create widgets:
+        # body and section frames.
+        frame_custom = LabelFrame(
+                self, borderwidth=2, relief=GROOVE,
+                text=' Custom Key Bindings ')
+        frame_key_sets = LabelFrame(
+                self, borderwidth=2, relief=GROOVE, text=' Key Set ')
+        # frame_custom.
+        frame_target = Frame(frame_custom)
+        target_title = Label(frame_target, text='Action - Key(s)')
+        scroll_target_y = Scrollbar(frame_target)
+        scroll_target_x = Scrollbar(frame_target, orient=HORIZONTAL)
+        self.bindingslist = Listbox(
+                frame_target, takefocus=FALSE, exportselection=FALSE)
+        self.bindingslist.bind('<ButtonRelease-1>',
+                               self.on_bindingslist_select)
+        scroll_target_y['command'] = self.bindingslist.yview
+        scroll_target_x['command'] = self.bindingslist.xview
+        self.bindingslist['yscrollcommand'] = scroll_target_y.set
+        self.bindingslist['xscrollcommand'] = scroll_target_x.set
+        self.button_new_keys = Button(
+                frame_custom, text='Get New Keys for Selection',
+                command=self.get_new_keys, state='disabled')
+        # frame_key_sets.
+        frames = [Frame(frame_key_sets, padding=2, borderwidth=0)
+                  for i in range(2)]
+        self.builtin_keyset_on = Radiobutton(
+                frames[0], variable=self.keyset_source, value=1,
+                command=self.set_keys_type, text='Use a Built-in Key Set')
+        self.custom_keyset_on = Radiobutton(
+                frames[0], variable=self.keyset_source, value=0,
+                command=self.set_keys_type, text='Use a Custom Key Set')
+        self.builtinlist = DynOptionMenu(
+                frames[0], self.builtin_name, None, command=None)
+        self.customlist = DynOptionMenu(
+                frames[0], self.custom_name, None, command=None)
+        self.button_delete_custom_keys = Button(
+                frames[1], text='Delete Custom Key Set',
+                command=self.delete_custom_keys)
+        self.button_save_custom_keys = Button(
+                frames[1], text='Save as New Custom Key Set',
+                command=self.save_as_new_key_set)
+        self.keys_message = Label(frames[0], borderwidth=2)
+
+        # Pack widgets:
+        # body.
+        frame_custom.pack(side=BOTTOM, padx=5, pady=5, expand=TRUE, fill=BOTH)
+        frame_key_sets.pack(side=BOTTOM, padx=5, pady=5, fill=BOTH)
+        # frame_custom.
+        self.button_new_keys.pack(side=BOTTOM, fill=X, padx=5, pady=5)
+        frame_target.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
+        # frame_target.
+        frame_target.columnconfigure(0, weight=1)
+        frame_target.rowconfigure(1, weight=1)
+        target_title.grid(row=0, column=0, columnspan=2, sticky=W)
+        self.bindingslist.grid(row=1, column=0, sticky=NSEW)
+        scroll_target_y.grid(row=1, column=1, sticky=NS)
+        scroll_target_x.grid(row=2, column=0, sticky=EW)
+        # frame_key_sets.
+        self.builtin_keyset_on.grid(row=0, column=0, sticky=W+NS)
+        self.custom_keyset_on.grid(row=1, column=0, sticky=W+NS)
+        self.builtinlist.grid(row=0, column=1, sticky=NSEW)
+        self.customlist.grid(row=1, column=1, sticky=NSEW)
+        self.keys_message.grid(row=0, column=2, sticky=NSEW, padx=5, pady=5)
+        self.button_delete_custom_keys.pack(side=LEFT, fill=X, expand=True, padx=2)
+        self.button_save_custom_keys.pack(side=LEFT, fill=X, expand=True, padx=2)
+        frames[0].pack(side=TOP, fill=BOTH, expand=True)
+        frames[1].pack(side=TOP, fill=X, expand=True, pady=2)
+
+    def load_key_cfg(self):
+        "Load current configuration settings for the keybinding options."
+        # Set current keys type radiobutton.
+        self.keyset_source.set(idleConf.GetOption(
+                'main', 'Keys', 'default', type='bool', default=1))
+        # Set current keys.
+        current_option = idleConf.CurrentKeys()
+        # Load available keyset option menus.
+        if self.keyset_source.get():  # Default theme selected.
+            item_list = idleConf.GetSectionList('default', 'keys')
+            item_list.sort()
+            self.builtinlist.SetMenu(item_list, current_option)
+            item_list = idleConf.GetSectionList('user', 'keys')
+            item_list.sort()
+            if not item_list:
+                self.custom_keyset_on.state(('disabled',))
+                self.custom_name.set('- no custom keys -')
+            else:
+                self.customlist.SetMenu(item_list, item_list[0])
+        else:  # User key set selected.
+            item_list = idleConf.GetSectionList('user', 'keys')
+            item_list.sort()
+            self.customlist.SetMenu(item_list, current_option)
+            item_list = idleConf.GetSectionList('default', 'keys')
+            item_list.sort()
+            self.builtinlist.SetMenu(item_list, idleConf.default_keys())
+        self.set_keys_type()
+        # Load keyset element list.
+        keyset_name = idleConf.CurrentKeys()
+        self.load_keys_list(keyset_name)
+
+    def var_changed_builtin_name(self, *params):
+        "Process selection of builtin key set."
+        old_keys = (
+            'IDLE Classic Windows',
+            'IDLE Classic Unix',
+            'IDLE Classic Mac',
+            'IDLE Classic OSX',
+        )
+        value = self.builtin_name.get()
+        if value not in old_keys:
+            if idleConf.GetOption('main', 'Keys', 'name') not in old_keys:
+                changes.add_option('main', 'Keys', 'name', old_keys[0])
+            changes.add_option('main', 'Keys', 'name2', value)
+            self.keys_message['text'] = 'New key set, see Help'
+        else:
+            changes.add_option('main', 'Keys', 'name', value)
+            changes.add_option('main', 'Keys', 'name2', '')
+            self.keys_message['text'] = ''
+        self.load_keys_list(value)
+
+    def var_changed_custom_name(self, *params):
+        "Process selection of custom key set."
+        value = self.custom_name.get()
+        if value != '- no custom keys -':
+            changes.add_option('main', 'Keys', 'name', value)
+            self.load_keys_list(value)
+
+    def var_changed_keyset_source(self, *params):
+        "Process toggle between builtin key set and custom key set."
+        value = self.keyset_source.get()
+        changes.add_option('main', 'Keys', 'default', value)
+        if value:
+            self.var_changed_builtin_name()
+        else:
+            self.var_changed_custom_name()
+
+    def var_changed_keybinding(self, *params):
+        "Store change to a keybinding."
+        value = self.keybinding.get()
+        key_set = self.custom_name.get()
+        event = self.bindingslist.get(ANCHOR).split()[0]
+        if idleConf.IsCoreBinding(event):
+            changes.add_option('keys', key_set, event, value)
+        else:  # Event is an extension binding.
+            ext_name = idleConf.GetExtnNameForEvent(event)
+            ext_keybind_section = ext_name + '_cfgBindings'
+            changes.add_option('extensions', ext_keybind_section, event, value)
+
+    def set_keys_type(self):
+        "Set available screen options based on builtin or custom key set."
+        if self.keyset_source.get():
+            self.builtinlist['state'] = 'normal'
+            self.customlist['state'] = 'disabled'
+            self.button_delete_custom_keys.state(('disabled',))
+        else:
+            self.builtinlist['state'] = 'disabled'
+            self.custom_keyset_on.state(('!disabled',))
+            self.customlist['state'] = 'normal'
+            self.button_delete_custom_keys.state(('!disabled',))
+
+    def get_new_keys(self):
+        """Handle event to change key binding for selected line.
+
+        A selection of a key/binding in the list of current
+        bindings pops up a dialog to enter a new binding.  If
+        the current key set is builtin and a binding has
+        changed, then a name for a custom key set needs to be
+        entered for the change to be applied.
+        """
+        list_index = self.bindingslist.index(ANCHOR)
+        binding = self.bindingslist.get(list_index)
+        bind_name = binding.split()[0]
+        if self.keyset_source.get():
+            current_key_set_name = self.builtin_name.get()
+        else:
+            current_key_set_name = self.custom_name.get()
+        current_bindings = idleConf.GetCurrentKeySet()
+        if current_key_set_name in changes['keys']:  # unsaved changes
+            key_set_changes = changes['keys'][current_key_set_name]
+            for event in key_set_changes:
+                current_bindings[event] = key_set_changes[event].split()
+        current_key_sequences = list(current_bindings.values())
+        new_keys = GetKeysDialog(self, 'Get New Keys', bind_name,
+                current_key_sequences).result
+        if new_keys:
+            if self.keyset_source.get():  # Current key set is a built-in.
+                message = ('Your changes will be saved as a new Custom Key Set.'
+                           ' Enter a name for your new Custom Key Set below.')
+                new_keyset = self.get_new_keys_name(message)
+                if not new_keyset:  # User cancelled custom key set creation.
+                    self.bindingslist.select_set(list_index)
+                    self.bindingslist.select_anchor(list_index)
+                    return
+                else:  # Create new custom key set based on previously active key set.
+                    self.create_new_key_set(new_keyset)
+            self.bindingslist.delete(list_index)
+            self.bindingslist.insert(list_index, bind_name+' - '+new_keys)
+            self.bindingslist.select_set(list_index)
+            self.bindingslist.select_anchor(list_index)
+            self.keybinding.set(new_keys)
+        else:
+            self.bindingslist.select_set(list_index)
+            self.bindingslist.select_anchor(list_index)
+
+    def get_new_keys_name(self, message):
+        "Return new key set name from query popup."
+        used_names = (idleConf.GetSectionList('user', 'keys') +
+                idleConf.GetSectionList('default', 'keys'))
+        new_keyset = SectionName(
+                self, 'New Custom Key Set', message, used_names).result
+        return new_keyset
+
+    def save_as_new_key_set(self):
+        "Prompt for name of new key set and save changes using that name."
+        new_keys_name = self.get_new_keys_name('New Key Set Name:')
+        if new_keys_name:
+            self.create_new_key_set(new_keys_name)
+
+    def on_bindingslist_select(self, event):
+        "Activate button to assign new keys to selected action."
+        self.button_new_keys.state(('!disabled',))
+
+    def create_new_key_set(self, new_key_set_name):
+        """Create a new custom key set with the given name.
+
+        Copy the bindings/keys from the previously active keyset
+        to the new keyset and activate the new custom keyset.
+        """
+        if self.keyset_source.get():
+            prev_key_set_name = self.builtin_name.get()
+        else:
+            prev_key_set_name = self.custom_name.get()
+        prev_keys = idleConf.GetCoreKeys(prev_key_set_name)
+        new_keys = {}
+        for event in prev_keys:  # Add key set to changed items.
+            event_name = event[2:-2]  # Trim off the angle brackets.
+            binding = ' '.join(prev_keys[event])
+            new_keys[event_name] = binding
+        # Handle any unsaved changes to prev key set.
+        if prev_key_set_name in changes['keys']:
+            key_set_changes = changes['keys'][prev_key_set_name]
+            for event in key_set_changes:
+                new_keys[event] = key_set_changes[event]
+        # Save the new key set.
+        self.save_new_key_set(new_key_set_name, new_keys)
+        # Change GUI over to the new key set.
+        custom_key_list = idleConf.GetSectionList('user', 'keys')
+        custom_key_list.sort()
+        self.customlist.SetMenu(custom_key_list, new_key_set_name)
+        self.keyset_source.set(0)
+        self.set_keys_type()
+
+    def load_keys_list(self, keyset_name):
+        """Reload the list of action/key binding pairs for the active key set.
+
+        An action/key binding can be selected to change the key binding.
+        """
+        reselect = False
+        if self.bindingslist.curselection():
+            reselect = True
+            list_index = self.bindingslist.index(ANCHOR)
+        keyset = idleConf.GetKeySet(keyset_name)
+        bind_names = list(keyset.keys())
+        bind_names.sort()
+        self.bindingslist.delete(0, END)
+        for bind_name in bind_names:
+            key = ' '.join(keyset[bind_name])
+            bind_name = bind_name[2:-2]  # Trim off the angle brackets.
+            if keyset_name in changes['keys']:
+                # Handle any unsaved changes to this key set.
+                if bind_name in changes['keys'][keyset_name]:
+                    key = changes['keys'][keyset_name][bind_name]
+            self.bindingslist.insert(END, bind_name+' - '+key)
+        if reselect:
+            self.bindingslist.see(list_index)
+            self.bindingslist.select_set(list_index)
+            self.bindingslist.select_anchor(list_index)
+
+    @staticmethod
+    def save_new_key_set(keyset_name, keyset):
+        """Save a newly created core key set.
+
+        Add keyset to idleConf.userCfg['keys'], not to disk.
+        If the keyset doesn't exist, it is created.  The
+        binding/keys are taken from the keyset argument.
+
+        keyset_name - string, the name of the new key set
+        keyset - dictionary containing the new keybindings
+        """
+        idleConf.userCfg['keys'].AddSection(keyset_name)
+        for event in keyset:
+            value = keyset[event]
+            idleConf.userCfg['keys'].SetOption(keyset_name, event, value)
+
+    def askyesno(self, *args, **kwargs):
+        # Make testing easier.  Could change implementation.
+        return messagebox.askyesno(*args, **kwargs)
+
+    def delete_custom_keys(self):
+        """Handle event to delete a custom key set.
+
+        Applying the delete deactivates the current configuration and
+        reverts to the default.  The custom key set is permanently
+        deleted from the config file.
+        """
+        keyset_name = self.custom_name.get()
+        delmsg = 'Are you sure you wish to delete the key set %r ?'
+        if not self.askyesno(
+                'Delete Key Set',  delmsg % keyset_name, parent=self):
+            return
+        self.cd.deactivate_current_config()
+        # Remove key set from changes, config, and file.
+        changes.delete_section('keys', keyset_name)
+        # Reload user key set list.
+        item_list = idleConf.GetSectionList('user', 'keys')
+        item_list.sort()
+        if not item_list:
+            self.custom_keyset_on.state(('disabled',))
+            self.customlist.SetMenu(item_list, '- no custom keys -')
+        else:
+            self.customlist.SetMenu(item_list, item_list[0])
+        # Revert to default key set.
+        self.keyset_source.set(idleConf.defaultCfg['main']
+                               .Get('Keys', 'default'))
+        self.builtin_name.set(idleConf.defaultCfg['main'].Get('Keys', 'name')
+                              or idleConf.default_keys())
+        # User can't back out of these changes, they must be applied now.
+        changes.save_all()
+        self.cd.save_all_changed_extensions()
+        self.cd.activate_config_changes()
+        self.set_keys_type()
+
+
+class GenPage(Frame):
+
+    def __init__(self, master):
+        super().__init__(master)
+
+        self.init_validators()
+        self.create_page_general()
+        self.load_general_cfg()
+
+    def init_validators(self):
+        digits_or_empty_re = re.compile(r'[0-9]*')
+        def is_digits_or_empty(s):
+            "Return 's is blank or contains only digits'"
+            return digits_or_empty_re.fullmatch(s) is not None
+        self.digits_only = (self.register(is_digits_or_empty), '%P',)
+
+    def create_page_general(self):
+        """Return frame of widgets for General tab.
+
+        Enable users to provisionally change general options. Function
+        load_general_cfg initializes tk variables and helplist using
+        idleConf.  Radiobuttons startup_shell_on and startup_editor_on
+        set var startup_edit. Radiobuttons save_ask_on and save_auto_on
+        set var autosave. Entry boxes win_width_int and win_height_int
+        set var win_width and win_height.  Setting var_name invokes the
+        default callback that adds option to changes.
+
+        Helplist: load_general_cfg loads list user_helplist with
+        name, position pairs and copies names to listbox helplist.
+        Clicking a name invokes help_source selected. Clicking
+        button_helplist_name invokes helplist_item_name, which also
+        changes user_helplist.  These functions all call
+        set_add_delete_state. All but load call update_help_changes to
+        rewrite changes['main']['HelpFiles'].
+
+        Widgets for GenPage(Frame):  (*) widgets bound to self
+            frame_window: LabelFrame
+                frame_run: Frame
+                    startup_title: Label
+                    (*)startup_editor_on: Radiobutton - startup_edit
+                    (*)startup_shell_on: Radiobutton - startup_edit
+                frame_win_size: Frame
+                    win_size_title: Label
+                    win_width_title: Label
+                    (*)win_width_int: Entry - win_width
+                    win_height_title: Label
+                    (*)win_height_int: Entry - win_height
+                frame_cursor_blink: Frame
+                    cursor_blink_title: Label
+                    (*)cursor_blink_bool: Checkbutton - cursor_blink
+                frame_autocomplete: Frame
+                    auto_wait_title: Label
+                    (*)auto_wait_int: Entry - autocomplete_wait
+                frame_paren1: Frame
+                    paren_style_title: Label
+                    (*)paren_style_type: OptionMenu - paren_style
+                frame_paren2: Frame
+                    paren_time_title: Label
+                    (*)paren_flash_time: Entry - flash_delay
+                    (*)bell_on: Checkbutton - paren_bell
+            frame_editor: LabelFrame
+                frame_save: Frame
+                    run_save_title: Label
+                    (*)save_ask_on: Radiobutton - autosave
+                    (*)save_auto_on: Radiobutton - autosave
+                frame_format: Frame
+                    format_width_title: Label
+                    (*)format_width_int: Entry - format_width
+                frame_line_numbers_default: Frame
+                    line_numbers_default_title: Label
+                    (*)line_numbers_default_bool: Checkbutton - line_numbers_default
+                frame_context: Frame
+                    context_title: Label
+                    (*)context_int: Entry - context_lines
+            frame_shell: LabelFrame
+                frame_auto_squeeze_min_lines: Frame
+                    auto_squeeze_min_lines_title: Label
+                    (*)auto_squeeze_min_lines_int: Entry - auto_squeeze_min_lines
+            frame_help: LabelFrame
+                frame_helplist: Frame
+                    frame_helplist_buttons: Frame
+                        (*)button_helplist_edit
+                        (*)button_helplist_add
+                        (*)button_helplist_remove
+                    (*)helplist: ListBox
+                    scroll_helplist: Scrollbar
+        """
+        # Integer values need StringVar because int('') raises.
+        self.startup_edit = tracers.add(
+                IntVar(self), ('main', 'General', 'editor-on-startup'))
+        self.win_width = tracers.add(
+                StringVar(self), ('main', 'EditorWindow', 'width'))
+        self.win_height = tracers.add(
+                StringVar(self), ('main', 'EditorWindow', 'height'))
+        self.cursor_blink = tracers.add(
+                BooleanVar(self), ('main', 'EditorWindow', 'cursor-blink'))
+        self.autocomplete_wait = tracers.add(
+                StringVar(self), ('extensions', 'AutoComplete', 'popupwait'))
+        self.paren_style = tracers.add(
+                StringVar(self), ('extensions', 'ParenMatch', 'style'))
+        self.flash_delay = tracers.add(
+                StringVar(self), ('extensions', 'ParenMatch', 'flash-delay'))
+        self.paren_bell = tracers.add(
+                BooleanVar(self), ('extensions', 'ParenMatch', 'bell'))
+
+        self.auto_squeeze_min_lines = tracers.add(
+                StringVar(self), ('main', 'PyShell', 'auto-squeeze-min-lines'))
+
+        self.autosave = tracers.add(
+                IntVar(self), ('main', 'General', 'autosave'))
+        self.format_width = tracers.add(
+                StringVar(self), ('extensions', 'FormatParagraph', 'max-width'))
+        self.line_numbers_default = tracers.add(
+                BooleanVar(self),
+                ('main', 'EditorWindow', 'line-numbers-default'))
+        self.context_lines = tracers.add(
+                StringVar(self), ('extensions', 'CodeContext', 'maxlines'))
+
+        # Create widgets:
+        # Section frames.
+        frame_window = LabelFrame(self, borderwidth=2, relief=GROOVE,
+                                  text=' Window Preferences')
+        frame_editor = LabelFrame(self, borderwidth=2, relief=GROOVE,
+                                  text=' Editor Preferences')
+        frame_shell = LabelFrame(self, borderwidth=2, relief=GROOVE,
+                                 text=' Shell Preferences')
+        frame_help = LabelFrame(self, borderwidth=2, relief=GROOVE,
+                                text=' Additional Help Sources ')
+        # Frame_window.
+        frame_run = Frame(frame_window, borderwidth=0)
+        startup_title = Label(frame_run, text='At Startup')
+        self.startup_editor_on = Radiobutton(
+                frame_run, variable=self.startup_edit, value=1,
+                text="Open Edit Window")
+        self.startup_shell_on = Radiobutton(
+                frame_run, variable=self.startup_edit, value=0,
+                text='Open Shell Window')
+
+        frame_win_size = Frame(frame_window, borderwidth=0)
+        win_size_title = Label(
+                frame_win_size, text='Initial Window Size  (in characters)')
+        win_width_title = Label(frame_win_size, text='Width')
+        self.win_width_int = Entry(
+                frame_win_size, textvariable=self.win_width, width=3,
+                validatecommand=self.digits_only, validate='key',
+        )
+        win_height_title = Label(frame_win_size, text='Height')
+        self.win_height_int = Entry(
+                frame_win_size, textvariable=self.win_height, width=3,
+                validatecommand=self.digits_only, validate='key',
+        )
+
+        frame_cursor_blink = Frame(frame_window, borderwidth=0)
+        cursor_blink_title = Label(frame_cursor_blink, text='Cursor Blink')
+        self.cursor_blink_bool = Checkbutton(frame_cursor_blink,
+                variable=self.cursor_blink, width=1)
+
+        frame_autocomplete = Frame(frame_window, borderwidth=0,)
+        auto_wait_title = Label(frame_autocomplete,
+                               text='Completions Popup Wait (milliseconds)')
+        self.auto_wait_int = Entry(frame_autocomplete, width=6,
+                                   textvariable=self.autocomplete_wait,
+                                   validatecommand=self.digits_only,
+                                   validate='key',
+                                   )
+
+        frame_paren1 = Frame(frame_window, borderwidth=0)
+        paren_style_title = Label(frame_paren1, text='Paren Match Style')
+        self.paren_style_type = OptionMenu(
+                frame_paren1, self.paren_style, 'expression',
+                "opener","parens","expression")
+        frame_paren2 = Frame(frame_window, borderwidth=0)
+        paren_time_title = Label(
+                frame_paren2, text='Time Match Displayed (milliseconds)\n'
+                                  '(0 is until next input)')
+        self.paren_flash_time = Entry(
+                frame_paren2, textvariable=self.flash_delay, width=6)
+        self.bell_on = Checkbutton(
+                frame_paren2, text="Bell on Mismatch", variable=self.paren_bell)
+
+        # Frame_editor.
+        frame_save = Frame(frame_editor, borderwidth=0)
+        run_save_title = Label(frame_save, text='At Start of Run (F5)  ')
+        self.save_ask_on = Radiobutton(
+                frame_save, variable=self.autosave, value=0,
+                text="Prompt to Save")
+        self.save_auto_on = Radiobutton(
+                frame_save, variable=self.autosave, value=1,
+                text='No Prompt')
+
+        frame_format = Frame(frame_editor, borderwidth=0)
+        format_width_title = Label(frame_format,
+                                   text='Format Paragraph Max Width')
+        self.format_width_int = Entry(
+                frame_format, textvariable=self.format_width, width=4,
+                validatecommand=self.digits_only, validate='key',
+        )
+
+        frame_line_numbers_default = Frame(frame_editor, borderwidth=0)
+        line_numbers_default_title = Label(
+            frame_line_numbers_default, text='Show line numbers in new windows')
+        self.line_numbers_default_bool = Checkbutton(
+                frame_line_numbers_default,
+                variable=self.line_numbers_default,
+                width=1)
+
+        frame_context = Frame(frame_editor, borderwidth=0)
+        context_title = Label(frame_context, text='Max Context Lines :')
+        self.context_int = Entry(
+                frame_context, textvariable=self.context_lines, width=3,
+                validatecommand=self.digits_only, validate='key',
+        )
+
+        # Frame_shell.
+        frame_auto_squeeze_min_lines = Frame(frame_shell, borderwidth=0)
+        auto_squeeze_min_lines_title = Label(frame_auto_squeeze_min_lines,
+                                             text='Auto-Squeeze Min. Lines:')
+        self.auto_squeeze_min_lines_int = Entry(
+                frame_auto_squeeze_min_lines, width=4,
+                textvariable=self.auto_squeeze_min_lines,
+                validatecommand=self.digits_only, validate='key',
+        )
+
+        # frame_help.
+        frame_helplist = Frame(frame_help)
+        frame_helplist_buttons = Frame(frame_helplist)
+        self.helplist = Listbox(
+                frame_helplist, height=5, takefocus=True,
+                exportselection=FALSE)
+        scroll_helplist = Scrollbar(frame_helplist)
+        scroll_helplist['command'] = self.helplist.yview
+        self.helplist['yscrollcommand'] = scroll_helplist.set
+        self.helplist.bind('<ButtonRelease-1>', self.help_source_selected)
+        self.button_helplist_edit = Button(
+                frame_helplist_buttons, text='Edit', state='disabled',
+                width=8, command=self.helplist_item_edit)
+        self.button_helplist_add = Button(
+                frame_helplist_buttons, text='Add',
+                width=8, command=self.helplist_item_add)
+        self.button_helplist_remove = Button(
+                frame_helplist_buttons, text='Remove', state='disabled',
+                width=8, command=self.helplist_item_remove)
+
+        # Pack widgets:
+        # Body.
+        frame_window.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
+        frame_editor.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
+        frame_shell.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
+        frame_help.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
+        # frame_run.
+        frame_run.pack(side=TOP, padx=5, pady=0, fill=X)
+        startup_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
+        self.startup_shell_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
+        self.startup_editor_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
+        # frame_win_size.
+        frame_win_size.pack(side=TOP, padx=5, pady=0, fill=X)
+        win_size_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
+        self.win_height_int.pack(side=RIGHT, anchor=E, padx=10, pady=5)
+        win_height_title.pack(side=RIGHT, anchor=E, pady=5)
+        self.win_width_int.pack(side=RIGHT, anchor=E, padx=10, pady=5)
+        win_width_title.pack(side=RIGHT, anchor=E, pady=5)
+        # frame_cursor_blink.
+        frame_cursor_blink.pack(side=TOP, padx=5, pady=0, fill=X)
+        cursor_blink_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
+        self.cursor_blink_bool.pack(side=LEFT, padx=5, pady=5)
+        # frame_autocomplete.
+        frame_autocomplete.pack(side=TOP, padx=5, pady=0, fill=X)
+        auto_wait_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
+        self.auto_wait_int.pack(side=TOP, padx=10, pady=5)
+        # frame_paren.
+        frame_paren1.pack(side=TOP, padx=5, pady=0, fill=X)
+        paren_style_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
+        self.paren_style_type.pack(side=TOP, padx=10, pady=5)
+        frame_paren2.pack(side=TOP, padx=5, pady=0, fill=X)
+        paren_time_title.pack(side=LEFT, anchor=W, padx=5)
+        self.bell_on.pack(side=RIGHT, anchor=E, padx=15, pady=5)
+        self.paren_flash_time.pack(side=TOP, anchor=W, padx=15, pady=5)
+
+        # frame_save.
+        frame_save.pack(side=TOP, padx=5, pady=0, fill=X)
+        run_save_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
+        self.save_auto_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
+        self.save_ask_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
+        # frame_format.
+        frame_format.pack(side=TOP, padx=5, pady=0, fill=X)
+        format_width_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
+        self.format_width_int.pack(side=TOP, padx=10, pady=5)
+        # frame_line_numbers_default.
+        frame_line_numbers_default.pack(side=TOP, padx=5, pady=0, fill=X)
+        line_numbers_default_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
+        self.line_numbers_default_bool.pack(side=LEFT, padx=5, pady=5)
+        # frame_context.
+        frame_context.pack(side=TOP, padx=5, pady=0, fill=X)
+        context_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
+        self.context_int.pack(side=TOP, padx=5, pady=5)
+
+        # frame_auto_squeeze_min_lines
+        frame_auto_squeeze_min_lines.pack(side=TOP, padx=5, pady=0, fill=X)
+        auto_squeeze_min_lines_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
+        self.auto_squeeze_min_lines_int.pack(side=TOP, padx=5, pady=5)
+
+        # frame_help.
+        frame_helplist_buttons.pack(side=RIGHT, padx=5, pady=5, fill=Y)
+        frame_helplist.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
+        scroll_helplist.pack(side=RIGHT, anchor=W, fill=Y)
+        self.helplist.pack(side=LEFT, anchor=E, expand=TRUE, fill=BOTH)
+        self.button_helplist_edit.pack(side=TOP, anchor=W, pady=5)
+        self.button_helplist_add.pack(side=TOP, anchor=W)
+        self.button_helplist_remove.pack(side=TOP, anchor=W, pady=5)
+
+    def load_general_cfg(self):
+        "Load current configuration settings for the general options."
+        # Set variables for all windows.
+        self.startup_edit.set(idleConf.GetOption(
+                'main', 'General', 'editor-on-startup', type='bool'))
+        self.win_width.set(idleConf.GetOption(
+                'main', 'EditorWindow', 'width', type='int'))
+        self.win_height.set(idleConf.GetOption(
+                'main', 'EditorWindow', 'height', type='int'))
+        self.cursor_blink.set(idleConf.GetOption(
+                'main', 'EditorWindow', 'cursor-blink', type='bool'))
+        self.autocomplete_wait.set(idleConf.GetOption(
+                'extensions', 'AutoComplete', 'popupwait', type='int'))
+        self.paren_style.set(idleConf.GetOption(
+                'extensions', 'ParenMatch', 'style'))
+        self.flash_delay.set(idleConf.GetOption(
+                'extensions', 'ParenMatch', 'flash-delay', type='int'))
+        self.paren_bell.set(idleConf.GetOption(
+                'extensions', 'ParenMatch', 'bell'))
+
+        # Set variables for editor windows.
+        self.autosave.set(idleConf.GetOption(
+                'main', 'General', 'autosave', default=0, type='bool'))
+        self.format_width.set(idleConf.GetOption(
+                'extensions', 'FormatParagraph', 'max-width', type='int'))
+        self.line_numbers_default.set(idleConf.GetOption(
+                'main', 'EditorWindow', 'line-numbers-default', type='bool'))
+        self.context_lines.set(idleConf.GetOption(
+                'extensions', 'CodeContext', 'maxlines', type='int'))
+
+        # Set variables for shell windows.
+        self.auto_squeeze_min_lines.set(idleConf.GetOption(
+                'main', 'PyShell', 'auto-squeeze-min-lines', type='int'))
+
+        # Set additional help sources.
+        self.user_helplist = idleConf.GetAllExtraHelpSourcesList()
+        self.helplist.delete(0, 'end')
+        for help_item in self.user_helplist:
+            self.helplist.insert(END, help_item[0])
+        self.set_add_delete_state()
+
+    def help_source_selected(self, event):
+        "Handle event for selecting additional help."
+        self.set_add_delete_state()
+
+    def set_add_delete_state(self):
+        "Toggle the state for the help list buttons based on list entries."
+        if self.helplist.size() < 1:  # No entries in list.
+            self.button_helplist_edit.state(('disabled',))
+            self.button_helplist_remove.state(('disabled',))
+        else:  # Some entries.
+            if self.helplist.curselection():  # There currently is a selection.
+                self.button_helplist_edit.state(('!disabled',))
+                self.button_helplist_remove.state(('!disabled',))
+            else:  # There currently is not a selection.
+                self.button_helplist_edit.state(('disabled',))
+                self.button_helplist_remove.state(('disabled',))
+
+    def helplist_item_add(self):
+        """Handle add button for the help list.
+
+        Query for name and location of new help sources and add
+        them to the list.
+        """
+        help_source = HelpSource(self, 'New Help Source').result
+        if help_source:
+            self.user_helplist.append(help_source)
+            self.helplist.insert(END, help_source[0])
+            self.update_help_changes()
+
+    def helplist_item_edit(self):
+        """Handle edit button for the help list.
+
+        Query with existing help source information and update
+        config if the values are changed.
+        """
+        item_index = self.helplist.index(ANCHOR)
+        help_source = self.user_helplist[item_index]
+        new_help_source = HelpSource(
+                self, 'Edit Help Source',
+                menuitem=help_source[0],
+                filepath=help_source[1],
+                ).result
+        if new_help_source and new_help_source != help_source:
+            self.user_helplist[item_index] = new_help_source
+            self.helplist.delete(item_index)
+            self.helplist.insert(item_index, new_help_source[0])
+            self.update_help_changes()
+            self.set_add_delete_state()  # Selected will be un-selected
+
+    def helplist_item_remove(self):
+        """Handle remove button for the help list.
+
+        Delete the help list item from config.
+        """
+        item_index = self.helplist.index(ANCHOR)
+        del(self.user_helplist[item_index])
+        self.helplist.delete(item_index)
+        self.update_help_changes()
+        self.set_add_delete_state()
+
+    def update_help_changes(self):
+        "Clear and rebuild the HelpFiles section in changes"
+        changes['main']['HelpFiles'] = {}
+        for num in range(1, len(self.user_helplist) + 1):
+            changes.add_option(
+                    'main', 'HelpFiles', str(num),
+                    ';'.join(self.user_helplist[num-1][:2]))
+
+
+class VarTrace:
+    """Maintain Tk variables trace state."""
+
+    def __init__(self):
+        """Store Tk variables and callbacks.
+
+        untraced: List of tuples (var, callback)
+            that do not have the callback attached
+            to the Tk var.
+        traced: List of tuples (var, callback) where
+            that callback has been attached to the var.
+        """
+        self.untraced = []
+        self.traced = []
+
+    def clear(self):
+        "Clear lists (for tests)."
+        # Call after all tests in a module to avoid memory leaks.
+        self.untraced.clear()
+        self.traced.clear()
+
+    def add(self, var, callback):
+        """Add (var, callback) tuple to untraced list.
+
+        Args:
+            var: Tk variable instance.
+            callback: Either function name to be used as a callback
+                or a tuple with IdleConf config-type, section, and
+                option names used in the default callback.
+
+        Return:
+            Tk variable instance.
+        """
+        if isinstance(callback, tuple):
+            callback = self.make_callback(var, callback)
+        self.untraced.append((var, callback))
+        return var
+
+    @staticmethod
+    def make_callback(var, config):
+        "Return default callback function to add values to changes instance."
+        def default_callback(*params):
+            "Add config values to changes instance."
+            changes.add_option(*config, var.get())
+        return default_callback
+
+    def attach(self):
+        "Attach callback to all vars that are not traced."
+        while self.untraced:
+            var, callback = self.untraced.pop()
+            var.trace_add('write', callback)
+            self.traced.append((var, callback))
+
+    def detach(self):
+        "Remove callback from traced vars."
+        while self.traced:
+            var, callback = self.traced.pop()
+            var.trace_remove('write', var.trace_info()[0][1])
+            self.untraced.append((var, callback))
+
+
+tracers = VarTrace()
+
+help_common = '''\
+When you click either the Apply or Ok buttons, settings in this
+dialog that are different from IDLE's default are saved in
+a .idlerc directory in your home directory. Except as noted,
+these changes apply to all versions of IDLE installed on this
+machine. [Cancel] only cancels changes made since the last save.
+'''
+help_pages = {
+    'Fonts/Tabs':'''
+Font sample: This shows what a selection of Basic Multilingual Plane
+unicode characters look like for the current font selection.  If the
+selected font does not define a character, Tk attempts to find another
+font that does.  Substitute glyphs depend on what is available on a
+particular system and will not necessarily have the same size as the
+font selected.  Line contains 20 characters up to Devanagari, 14 for
+Tamil, and 10 for East Asia.
+
+Hebrew and Arabic letters should display right to left, starting with
+alef, \u05d0 and \u0627.  Arabic digits display left to right.  The
+Devanagari and Tamil lines start with digits.  The East Asian lines
+are Chinese digits, Chinese Hanzi, Korean Hangul, and Japanese
+Hiragana and Katakana.
+
+You can edit the font sample. Changes remain until IDLE is closed.
+''',
+    'Highlights': '''
+Highlighting:
+The IDLE Dark color theme is new in October 2015.  It can only
+be used with older IDLE releases if it is saved as a custom
+theme, with a different name.
+''',
+    'Keys': '''
+Keys:
+The IDLE Modern Unix key set is new in June 2016.  It can only
+be used with older IDLE releases if it is saved as a custom
+key set, with a different name.
+''',
+     'General': '''
+General:
+
+AutoComplete: Popupwait is milliseconds to wait after key char, without
+cursor movement, before popping up completion box.  Key char is '.' after
+identifier or a '/' (or '\\' on Windows) within a string.
+
+FormatParagraph: Max-width is max chars in lines after re-formatting.
+Use with paragraphs in both strings and comment blocks.
+
+ParenMatch: Style indicates what is highlighted when closer is entered:
+'opener' - opener '({[' corresponding to closer; 'parens' - both chars;
+'expression' (default) - also everything in between.  Flash-delay is how
+long to highlight if cursor is not moved (0 means forever).
+
+CodeContext: Maxlines is the maximum number of code context lines to
+display when Code Context is turned on for an editor window.
+
+Shell Preferences: Auto-Squeeze Min. Lines is the minimum number of lines
+of output to automatically "squeeze".
+''',
+    'Extensions': '''
+ZzDummy: This extension is provided as an example for how to create and
+use an extension.  Enable indicates whether the extension is active or
+not; likewise enable_editor and enable_shell indicate which windows it
+will be active on.  For this extension, z-text is the text that will be
+inserted at or removed from the beginning of the lines of selected text,
+or the current line if no selection.
+''',
+}
+
+
+def is_int(s):
+    "Return 's is blank or represents an int'"
+    if not s:
+        return True
+    try:
+        int(s)
+        return True
+    except ValueError:
+        return False
+
+
+class VerticalScrolledFrame(Frame):
+    """A pure Tkinter vertically scrollable frame.
+
+    * Use the 'interior' attribute to place widgets inside the scrollable frame
+    * Construct and pack/place/grid normally
+    * This frame only allows vertical scrolling
+    """
+    def __init__(self, parent, *args, **kw):
+        Frame.__init__(self, parent, *args, **kw)
+
+        # Create a canvas object and a vertical scrollbar for scrolling it.
+        vscrollbar = Scrollbar(self, orient=VERTICAL)
+        vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE)
+        canvas = Canvas(self, borderwidth=0, highlightthickness=0,
+                        yscrollcommand=vscrollbar.set, width=240)
+        canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)
+        vscrollbar.config(command=canvas.yview)
+
+        # Reset the view.
+        canvas.xview_moveto(0)
+        canvas.yview_moveto(0)
+
+        # Create a frame inside the canvas which will be scrolled with it.
+        self.interior = interior = Frame(canvas)
+        interior_id = canvas.create_window(0, 0, window=interior, anchor=NW)
+
+        # Track changes to the canvas and frame width and sync them,
+        # also updating the scrollbar.
+        def _configure_interior(event):
+            # Update the scrollbars to match the size of the inner frame.
+            size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
+            canvas.config(scrollregion="0 0 %s %s" % size)
+        interior.bind('<Configure>', _configure_interior)
+
+        def _configure_canvas(event):
+            if interior.winfo_reqwidth() != canvas.winfo_width():
+                # Update the inner frame's width to fill the canvas.
+                canvas.itemconfigure(interior_id, width=canvas.winfo_width())
+        canvas.bind('<Configure>', _configure_canvas)
+
+        return
+
+
+if __name__ == '__main__':
+    from unittest import main
+    main('idlelib.idle_test.test_configdialog', verbosity=2, exit=False)
+
+    from idlelib.idle_test.htest import run
+    run(ConfigDialog)
diff --git a/rootfs/usr/lib/python3.8/idlelib/debugger.py b/rootfs/usr/lib/python3.8/idlelib/debugger.py
new file mode 100644
index 0000000..ccd03e4
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/debugger.py
@@ -0,0 +1,550 @@
+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):
+
+    def __init__(self, gui):
+        self.gui = gui  # An instance of Debugger or proxy of remote.
+        bdb.Bdb.__init__(self)
+
+    def user_line(self, frame):
+        if self.in_rpc_code(frame):
+            self.set_step()
+            return
+        message = self.__frame2message(frame)
+        try:
+            self.gui.interaction(message, frame)
+        except TclError:  # When closing debugger window with [x] in 3.x
+            pass
+
+    def user_exception(self, frame, info):
+        if self.in_rpc_code(frame):
+            self.set_step()
+            return
+        message = self.__frame2message(frame)
+        self.gui.interaction(message, frame, info)
+
+    def in_rpc_code(self, frame):
+        if frame.f_code.co_filename.count('rpc.py'):
+            return True
+        else:
+            prev_frame = frame.f_back
+            prev_name = prev_frame.f_code.co_filename
+            if 'idlelib' in prev_name and 'debugger' in prev_name:
+                # catch both idlelib/debugger.py and idlelib/debugger_r.py
+                # on both Posix and Windows
+                return False
+            return self.in_rpc_code(prev_frame)
+
+    def __frame2message(self, frame):
+        code = frame.f_code
+        filename = code.co_filename
+        lineno = frame.f_lineno
+        basename = os.path.basename(filename)
+        message = "%s:%s" % (basename, lineno)
+        if code.co_name != "?":
+            message = "%s: %s()" % (message, code.co_name)
+        return message
+
+
+class Debugger:
+
+    vstack = vsource = vlocals = vglobals = None
+
+    def __init__(self, pyshell, idb=None):
+        if idb is None:
+            idb = Idb(self)
+        self.pyshell = pyshell
+        self.idb = idb  # If passed, a proxy of remote instance.
+        self.frame = None
+        self.make_gui()
+        self.interacting = 0
+        self.nesting_level = 0
+
+    def run(self, *args):
+        # Deal with the scenario where we've already got a program running
+        # in the debugger and we want to start another. If that is the case,
+        # our second 'run' was invoked from an event dispatched not from
+        # the main event loop, but from the nested event loop in 'interaction'
+        # below. So our stack looks something like this:
+        #       outer main event loop
+        #         run()
+        #           <running program with traces>
+        #             callback to debugger's interaction()
+        #               nested event loop
+        #                 run() for second command
+        #
+        # This kind of nesting of event loops causes all kinds of problems
+        # (see e.g. issue #24455) especially when dealing with running as a
+        # subprocess, where there's all kinds of extra stuff happening in
+        # there - insert a traceback.print_stack() to check it out.
+        #
+        # By this point, we've already called restart_subprocess() in
+        # ScriptBinding. However, we also need to unwind the stack back to
+        # that outer event loop.  To accomplish this, we:
+        #   - return immediately from the nested run()
+        #   - abort_loop ensures the nested event loop will terminate
+        #   - the debugger's interaction routine completes normally
+        #   - the restart_subprocess() will have taken care of stopping
+        #     the running program, which will also let the outer run complete
+        #
+        # That leaves us back at the outer main event loop, at which point our
+        # after event can fire, and we'll come back to this routine with a
+        # clean stack.
+        if self.nesting_level > 0:
+            self.abort_loop()
+            self.root.after(100, lambda: self.run(*args))
+            return
+        try:
+            self.interacting = 1
+            return self.idb.run(*args)
+        finally:
+            self.interacting = 0
+
+    def close(self, event=None):
+        try:
+            self.quit()
+        except Exception:
+            pass
+        if self.interacting:
+            self.top.bell()
+            return
+        if self.stackviewer:
+            self.stackviewer.close(); self.stackviewer = None
+        # Clean up pyshell if user clicked debugger control close widget.
+        # (Causes a harmless extra cycle through close_debugger() if user
+        # toggled debugger from pyshell Debug menu)
+        self.pyshell.close_debugger()
+        # Now close the debugger control window....
+        self.top.destroy()
+
+    def make_gui(self):
+        pyshell = self.pyshell
+        self.flist = pyshell.flist
+        self.root = root = pyshell.root
+        self.top = top = ListedToplevel(root)
+        self.top.wm_title("Debug Control")
+        self.top.wm_iconname("Debug")
+        top.wm_protocol("WM_DELETE_WINDOW", self.close)
+        self.top.bind("<Escape>", self.close)
+        #
+        self.bframe = bframe = Frame(top)
+        self.bframe.pack(anchor="w")
+        self.buttons = bl = []
+        #
+        self.bcont = b = Button(bframe, text="Go", command=self.cont)
+        bl.append(b)
+        self.bstep = b = Button(bframe, text="Step", command=self.step)
+        bl.append(b)
+        self.bnext = b = Button(bframe, text="Over", command=self.next)
+        bl.append(b)
+        self.bret = b = Button(bframe, text="Out", command=self.ret)
+        bl.append(b)
+        self.bret = b = Button(bframe, text="Quit", command=self.quit)
+        bl.append(b)
+        #
+        for b in bl:
+            b.configure(state="disabled")
+            b.pack(side="left")
+        #
+        self.cframe = cframe = Frame(bframe)
+        self.cframe.pack(side="left")
+        #
+        if not self.vstack:
+            self.__class__.vstack = BooleanVar(top)
+            self.vstack.set(1)
+        self.bstack = Checkbutton(cframe,
+            text="Stack", command=self.show_stack, variable=self.vstack)
+        self.bstack.grid(row=0, column=0)
+        if not self.vsource:
+            self.__class__.vsource = BooleanVar(top)
+        self.bsource = Checkbutton(cframe,
+            text="Source", command=self.show_source, variable=self.vsource)
+        self.bsource.grid(row=0, column=1)
+        if not self.vlocals:
+            self.__class__.vlocals = BooleanVar(top)
+            self.vlocals.set(1)
+        self.blocals = Checkbutton(cframe,
+            text="Locals", command=self.show_locals, variable=self.vlocals)
+        self.blocals.grid(row=1, column=0)
+        if not self.vglobals:
+            self.__class__.vglobals = BooleanVar(top)
+        self.bglobals = Checkbutton(cframe,
+            text="Globals", command=self.show_globals, variable=self.vglobals)
+        self.bglobals.grid(row=1, column=1)
+        #
+        self.status = Label(top, anchor="w")
+        self.status.pack(anchor="w")
+        self.error = Label(top, anchor="w")
+        self.error.pack(anchor="w", fill="x")
+        self.errorbg = self.error.cget("background")
+        #
+        self.fstack = Frame(top, height=1)
+        self.fstack.pack(expand=1, fill="both")
+        self.flocals = Frame(top)
+        self.flocals.pack(expand=1, fill="both")
+        self.fglobals = Frame(top, height=1)
+        self.fglobals.pack(expand=1, fill="both")
+        #
+        if self.vstack.get():
+            self.show_stack()
+        if self.vlocals.get():
+            self.show_locals()
+        if self.vglobals.get():
+            self.show_globals()
+
+    def interaction(self, message, frame, info=None):
+        self.frame = frame
+        self.status.configure(text=message)
+        #
+        if info:
+            type, value, tb = info
+            try:
+                m1 = type.__name__
+            except AttributeError:
+                m1 = "%s" % str(type)
+            if value is not None:
+                try:
+                    m1 = "%s: %s" % (m1, str(value))
+                except:
+                    pass
+            bg = "yellow"
+        else:
+            m1 = ""
+            tb = None
+            bg = self.errorbg
+        self.error.configure(text=m1, background=bg)
+        #
+        sv = self.stackviewer
+        if sv:
+            stack, i = self.idb.get_stack(self.frame, tb)
+            sv.load_stack(stack, i)
+        #
+        self.show_variables(1)
+        #
+        if self.vsource.get():
+            self.sync_source_line()
+        #
+        for b in self.buttons:
+            b.configure(state="normal")
+        #
+        self.top.wakeup()
+        # Nested main loop: Tkinter's main loop is not reentrant, so use
+        # Tcl's vwait facility, which reenters the event loop until an
+        # event handler sets the variable we're waiting on
+        self.nesting_level += 1
+        self.root.tk.call('vwait', '::idledebugwait')
+        self.nesting_level -= 1
+        #
+        for b in self.buttons:
+            b.configure(state="disabled")
+        self.status.configure(text="")
+        self.error.configure(text="", background=self.errorbg)
+        self.frame = None
+
+    def sync_source_line(self):
+        frame = self.frame
+        if not frame:
+            return
+        filename, lineno = self.__frame2fileline(frame)
+        if filename[:1] + filename[-1:] != "<>" and os.path.exists(filename):
+            self.flist.gotofileline(filename, lineno)
+
+    def __frame2fileline(self, frame):
+        code = frame.f_code
+        filename = code.co_filename
+        lineno = frame.f_lineno
+        return filename, lineno
+
+    def cont(self):
+        self.idb.set_continue()
+        self.abort_loop()
+
+    def step(self):
+        self.idb.set_step()
+        self.abort_loop()
+
+    def next(self):
+        self.idb.set_next(self.frame)
+        self.abort_loop()
+
+    def ret(self):
+        self.idb.set_return(self.frame)
+        self.abort_loop()
+
+    def quit(self):
+        self.idb.set_quit()
+        self.abort_loop()
+
+    def abort_loop(self):
+        self.root.tk.call('set', '::idledebugwait', '1')
+
+    stackviewer = None
+
+    def show_stack(self):
+        if not self.stackviewer and self.vstack.get():
+            self.stackviewer = sv = StackViewer(self.fstack, self.flist, self)
+            if self.frame:
+                stack, i = self.idb.get_stack(self.frame, None)
+                sv.load_stack(stack, i)
+        else:
+            sv = self.stackviewer
+            if sv and not self.vstack.get():
+                self.stackviewer = None
+                sv.close()
+            self.fstack['height'] = 1
+
+    def show_source(self):
+        if self.vsource.get():
+            self.sync_source_line()
+
+    def show_frame(self, stackitem):
+        self.frame = stackitem[0]  # lineno is stackitem[1]
+        self.show_variables()
+
+    localsviewer = None
+    globalsviewer = None
+
+    def show_locals(self):
+        lv = self.localsviewer
+        if self.vlocals.get():
+            if not lv:
+                self.localsviewer = NamespaceViewer(self.flocals, "Locals")
+        else:
+            if lv:
+                self.localsviewer = None
+                lv.close()
+                self.flocals['height'] = 1
+        self.show_variables()
+
+    def show_globals(self):
+        gv = self.globalsviewer
+        if self.vglobals.get():
+            if not gv:
+                self.globalsviewer = NamespaceViewer(self.fglobals, "Globals")
+        else:
+            if gv:
+                self.globalsviewer = None
+                gv.close()
+                self.fglobals['height'] = 1
+        self.show_variables()
+
+    def show_variables(self, force=0):
+        lv = self.localsviewer
+        gv = self.globalsviewer
+        frame = self.frame
+        if not frame:
+            ldict = gdict = None
+        else:
+            ldict = frame.f_locals
+            gdict = frame.f_globals
+            if lv and gv and ldict is gdict:
+                ldict = None
+        if lv:
+            lv.load_dict(ldict, force, self.pyshell.interp.rpcclt)
+        if gv:
+            gv.load_dict(gdict, force, self.pyshell.interp.rpcclt)
+
+    def set_breakpoint_here(self, filename, lineno):
+        self.idb.set_break(filename, lineno)
+
+    def clear_breakpoint_here(self, filename, lineno):
+        self.idb.clear_break(filename, lineno)
+
+    def clear_file_breaks(self, filename):
+        self.idb.clear_all_file_breaks(filename)
+
+    def load_breakpoints(self):
+        "Load PyShellEditorWindow breakpoints into subprocess debugger"
+        for editwin in self.pyshell.flist.inversedict:
+            filename = editwin.io.filename
+            try:
+                for lineno in editwin.breakpoints:
+                    self.set_breakpoint_here(filename, lineno)
+            except AttributeError:
+                continue
+
+class StackViewer(ScrolledList):
+
+    def __init__(self, master, flist, gui):
+        if macosx.isAquaTk():
+            # At least on with the stock AquaTk version on OSX 10.4 you'll
+            # get a shaking GUI that eventually kills IDLE if the width
+            # argument is specified.
+            ScrolledList.__init__(self, master)
+        else:
+            ScrolledList.__init__(self, master, width=80)
+        self.flist = flist
+        self.gui = gui
+        self.stack = []
+
+    def load_stack(self, stack, index=None):
+        self.stack = stack
+        self.clear()
+        for i in range(len(stack)):
+            frame, lineno = stack[i]
+            try:
+                modname = frame.f_globals["__name__"]
+            except:
+                modname = "?"
+            code = frame.f_code
+            filename = code.co_filename
+            funcname = code.co_name
+            import linecache
+            sourceline = linecache.getline(filename, lineno)
+            sourceline = sourceline.strip()
+            if funcname in ("?", "", None):
+                item = "%s, line %d: %s" % (modname, lineno, sourceline)
+            else:
+                item = "%s.%s(), line %d: %s" % (modname, funcname,
+                                                 lineno, sourceline)
+            if i == index:
+                item = "> " + item
+            self.append(item)
+        if index is not None:
+            self.select(index)
+
+    def popup_event(self, event):
+        "override base method"
+        if self.stack:
+            return ScrolledList.popup_event(self, event)
+
+    def fill_menu(self):
+        "override base method"
+        menu = self.menu
+        menu.add_command(label="Go to source line",
+                         command=self.goto_source_line)
+        menu.add_command(label="Show stack frame",
+                         command=self.show_stack_frame)
+
+    def on_select(self, index):
+        "override base method"
+        if 0 <= index < len(self.stack):
+            self.gui.show_frame(self.stack[index])
+
+    def on_double(self, index):
+        "override base method"
+        self.show_source(index)
+
+    def goto_source_line(self):
+        index = self.listbox.index("active")
+        self.show_source(index)
+
+    def show_stack_frame(self):
+        index = self.listbox.index("active")
+        if 0 <= index < len(self.stack):
+            self.gui.show_frame(self.stack[index])
+
+    def show_source(self, index):
+        if not (0 <= index < len(self.stack)):
+            return
+        frame, lineno = self.stack[index]
+        code = frame.f_code
+        filename = code.co_filename
+        if os.path.isfile(filename):
+            edit = self.flist.open(filename)
+            if edit:
+                edit.gotoline(lineno)
+
+
+class NamespaceViewer:
+
+    def __init__(self, master, title, dict=None):
+        width = 0
+        height = 40
+        if dict:
+            height = 20*len(dict) # XXX 20 == observed height of Entry widget
+        self.master = master
+        self.title = title
+        import reprlib
+        self.repr = reprlib.Repr()
+        self.repr.maxstring = 60
+        self.repr.maxother = 60
+        self.frame = frame = Frame(master)
+        self.frame.pack(expand=1, fill="both")
+        self.label = Label(frame, text=title, borderwidth=2, relief="groove")
+        self.label.pack(fill="x")
+        self.vbar = vbar = Scrollbar(frame, name="vbar")
+        vbar.pack(side="right", fill="y")
+        self.canvas = canvas = Canvas(frame,
+                                      height=min(300, max(40, height)),
+                                      scrollregion=(0, 0, width, height))
+        canvas.pack(side="left", fill="both", expand=1)
+        vbar["command"] = canvas.yview
+        canvas["yscrollcommand"] = vbar.set
+        self.subframe = subframe = Frame(canvas)
+        self.sfid = canvas.create_window(0, 0, window=subframe, anchor="nw")
+        self.load_dict(dict)
+
+    dict = -1
+
+    def load_dict(self, dict, force=0, rpc_client=None):
+        if dict is self.dict and not force:
+            return
+        subframe = self.subframe
+        frame = self.frame
+        for c in list(subframe.children.values()):
+            c.destroy()
+        self.dict = None
+        if not dict:
+            l = Label(subframe, text="None")
+            l.grid(row=0, column=0)
+        else:
+            #names = sorted(dict)
+            ###
+            # Because of (temporary) limitations on the dict_keys type (not yet
+            # public or pickleable), have the subprocess to send a list of
+            # keys, not a dict_keys object.  sorted() will take a dict_keys
+            # (no subprocess) or a list.
+            #
+            # There is also an obscure bug in sorted(dict) where the
+            # interpreter gets into a loop requesting non-existing dict[0],
+            # dict[1], dict[2], etc from the debugger_r.DictProxy.
+            ###
+            keys_list = dict.keys()
+            names = sorted(keys_list)
+            ###
+            row = 0
+            for name in names:
+                value = dict[name]
+                svalue = self.repr.repr(value) # repr(value)
+                # Strip extra quotes caused by calling repr on the (already)
+                # repr'd value sent across the RPC interface:
+                if rpc_client:
+                    svalue = svalue[1:-1]
+                l = Label(subframe, text=name)
+                l.grid(row=row, column=0, sticky="nw")
+                l = Entry(subframe, width=0, borderwidth=0)
+                l.insert(0, svalue)
+                l.grid(row=row, column=1, sticky="nw")
+                row = row+1
+        self.dict = dict
+        # XXX Could we use a <Configure> callback for the following?
+        subframe.update_idletasks() # Alas!
+        width = subframe.winfo_reqwidth()
+        height = subframe.winfo_reqheight()
+        canvas = self.canvas
+        self.canvas["scrollregion"] = (0, 0, width, height)
+        if height > 300:
+            canvas["height"] = 300
+            frame.pack(expand=1)
+        else:
+            canvas["height"] = height
+            frame.pack(expand=0)
+
+    def close(self):
+        self.frame.destroy()
+
+if __name__ == "__main__":
+    from unittest import main
+    main('idlelib.idle_test.test_debugger', verbosity=2, exit=False)
+
+# TODO: htest?
diff --git a/rootfs/usr/lib/python3.8/idlelib/debugger_r.py b/rootfs/usr/lib/python3.8/idlelib/debugger_r.py
new file mode 100644
index 0000000..2620443
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/debugger_r.py
@@ -0,0 +1,393 @@
+"""Support for remote Python debugging.
+
+Some ASCII art to describe the structure:
+
+       IN PYTHON SUBPROCESS          #             IN IDLE PROCESS
+                                     #
+                                     #        oid='gui_adapter'
+                 +----------+        #       +------------+          +-----+
+                 | GUIProxy |--remote#call-->| GUIAdapter |--calls-->| GUI |
++-----+--calls-->+----------+        #       +------------+          +-----+
+| Idb |                               #                             /
++-----+<-calls--+------------+         #      +----------+<--calls-/
+                | IdbAdapter |<--remote#call--| IdbProxy |
+                +------------+         #      +----------+
+                oid='idb_adapter'      #
+
+The purpose of the Proxy and Adapter classes is to translate certain
+arguments and return values that cannot be transported through the RPC
+barrier, in particular frame and traceback objects.
+
+"""
+import 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]
+        dict = frame.f_globals
+        did = id(dict)
+        dicttable[did] = dict
+        return did
+
+    def frame_locals(self, fid):
+        frame = frametable[fid]
+        dict = frame.f_locals
+        did = id(dict)
+        dicttable[did] = dict
+        return did
+
+    def frame_code(self, fid):
+        frame = frametable[fid]
+        code = frame.f_code
+        cid = id(code)
+        codetable[cid] = code
+        return cid
+
+    #----------called by a CodeProxy----------
+
+    def code_name(self, cid):
+        code = codetable[cid]
+        return code.co_name
+
+    def code_filename(self, cid):
+        code = codetable[cid]
+        return code.co_filename
+
+    #----------called by a DictProxy----------
+
+    def dict_keys(self, did):
+        raise NotImplementedError("dict_keys not public or pickleable")
+##         dict = dicttable[did]
+##         return dict.keys()
+
+    ### Needed until dict_keys is type is finished and pickealable.
+    ### Will probably need to extend rpc.py:SocketIO._proxify at that time.
+    def dict_keys_list(self, did):
+        dict = dicttable[did]
+        return list(dict.keys())
+
+    def dict_item(self, did, key):
+        dict = dicttable[did]
+        value = dict[key]
+        value = reprlib.repr(value) ### can't pickle module 'builtins'
+        return value
+
+#----------end class IdbAdapter----------
+
+
+def start_debugger(rpchandler, gui_adap_oid):
+    """Start the debugger and its RPC link in the Python subprocess
+
+    Start the subprocess side of the split debugger and set up that side of the
+    RPC link by instantiating the GUIProxy, Idb debugger, and IdbAdapter
+    objects and linking them together.  Register the IdbAdapter with the
+    RPCServer to handle RPC requests from the split debugger GUI via the
+    IdbProxy.
+
+    """
+    gui_proxy = GUIProxy(rpchandler, gui_adap_oid)
+    idb = debugger.Idb(gui_proxy)
+    idb_adap = IdbAdapter(idb)
+    rpchandler.register(idb_adap_oid, idb_adap)
+    return idb_adap_oid
+
+
+#=======================================
+#
+# In the IDLE process:
+
+
+class FrameProxy:
+
+    def __init__(self, conn, fid):
+        self._conn = conn
+        self._fid = fid
+        self._oid = "idb_adapter"
+        self._dictcache = {}
+
+    def __getattr__(self, name):
+        if name[:1] == "_":
+            raise AttributeError(name)
+        if name == "f_code":
+            return self._get_f_code()
+        if name == "f_globals":
+            return self._get_f_globals()
+        if name == "f_locals":
+            return self._get_f_locals()
+        return self._conn.remotecall(self._oid, "frame_attr",
+                                     (self._fid, name), {})
+
+    def _get_f_code(self):
+        cid = self._conn.remotecall(self._oid, "frame_code", (self._fid,), {})
+        return CodeProxy(self._conn, self._oid, cid)
+
+    def _get_f_globals(self):
+        did = self._conn.remotecall(self._oid, "frame_globals",
+                                    (self._fid,), {})
+        return self._get_dict_proxy(did)
+
+    def _get_f_locals(self):
+        did = self._conn.remotecall(self._oid, "frame_locals",
+                                    (self._fid,), {})
+        return self._get_dict_proxy(did)
+
+    def _get_dict_proxy(self, did):
+        if did in self._dictcache:
+            return self._dictcache[did]
+        dp = DictProxy(self._conn, self._oid, did)
+        self._dictcache[did] = dp
+        return dp
+
+
+class CodeProxy:
+
+    def __init__(self, conn, oid, cid):
+        self._conn = conn
+        self._oid = oid
+        self._cid = cid
+
+    def __getattr__(self, name):
+        if name == "co_name":
+            return self._conn.remotecall(self._oid, "code_name",
+                                         (self._cid,), {})
+        if name == "co_filename":
+            return self._conn.remotecall(self._oid, "code_filename",
+                                         (self._cid,), {})
+
+
+class DictProxy:
+
+    def __init__(self, conn, oid, did):
+        self._conn = conn
+        self._oid = oid
+        self._did = did
+
+##    def keys(self):
+##        return self._conn.remotecall(self._oid, "dict_keys", (self._did,), {})
+
+    # 'temporary' until dict_keys is a pickleable built-in type
+    def keys(self):
+        return self._conn.remotecall(self._oid,
+                                     "dict_keys_list", (self._did,), {})
+
+    def __getitem__(self, key):
+        return self._conn.remotecall(self._oid, "dict_item",
+                                     (self._did, key), {})
+
+    def __getattr__(self, name):
+        ##print("*** Failed DictProxy.__getattr__:", name)
+        raise AttributeError(name)
+
+
+class GUIAdapter:
+
+    def __init__(self, conn, gui):
+        self.conn = conn
+        self.gui = gui
+
+    def interaction(self, message, fid, modified_info):
+        ##print("*** Interaction: (%s, %s, %s)" % (message, fid, modified_info))
+        frame = FrameProxy(self.conn, fid)
+        self.gui.interaction(message, frame, modified_info)
+
+
+class IdbProxy:
+
+    def __init__(self, conn, shell, oid):
+        self.oid = oid
+        self.conn = conn
+        self.shell = shell
+
+    def call(self, methodname, /, *args, **kwargs):
+        ##print("*** IdbProxy.call %s %s %s" % (methodname, args, kwargs))
+        value = self.conn.remotecall(self.oid, methodname, args, kwargs)
+        ##print("*** IdbProxy.call %s returns %r" % (methodname, value))
+        return value
+
+    def run(self, cmd, locals):
+        # Ignores locals on purpose!
+        seq = self.conn.asyncqueue(self.oid, "run", (cmd,), {})
+        self.shell.interp.active_seq = seq
+
+    def get_stack(self, frame, tbid):
+        # passing frame and traceback IDs, not the objects themselves
+        stack, i = self.call("get_stack", frame._fid, tbid)
+        stack = [(FrameProxy(self.conn, fid), k) for fid, k in stack]
+        return stack, i
+
+    def set_continue(self):
+        self.call("set_continue")
+
+    def set_step(self):
+        self.call("set_step")
+
+    def set_next(self, frame):
+        self.call("set_next", frame._fid)
+
+    def set_return(self, frame):
+        self.call("set_return", frame._fid)
+
+    def set_quit(self):
+        self.call("set_quit")
+
+    def set_break(self, filename, lineno):
+        msg = self.call("set_break", filename, lineno)
+        return msg
+
+    def clear_break(self, filename, lineno):
+        msg = self.call("clear_break", filename, lineno)
+        return msg
+
+    def clear_all_file_breaks(self, filename):
+        msg = self.call("clear_all_file_breaks", filename)
+        return msg
+
+def start_remote_debugger(rpcclt, pyshell):
+    """Start the subprocess debugger, initialize the debugger GUI and RPC link
+
+    Request the RPCServer start the Python subprocess debugger and link.  Set
+    up the Idle side of the split debugger by instantiating the IdbProxy,
+    debugger GUI, and debugger GUIAdapter objects and linking them together.
+
+    Register the GUIAdapter with the RPCClient to handle debugger GUI
+    interaction requests coming from the subprocess debugger via the GUIProxy.
+
+    The IdbAdapter will pass execution and environment requests coming from the
+    Idle debugger GUI to the subprocess debugger via the IdbProxy.
+
+    """
+    global idb_adap_oid
+
+    idb_adap_oid = rpcclt.remotecall("exec", "start_the_debugger",\
+                                   (gui_adap_oid,), {})
+    idb_proxy = IdbProxy(rpcclt, pyshell, idb_adap_oid)
+    gui = debugger.Debugger(pyshell, idb_proxy)
+    gui_adap = GUIAdapter(rpcclt, gui)
+    rpcclt.register(gui_adap_oid, gui_adap)
+    return gui
+
+def close_remote_debugger(rpcclt):
+    """Shut down subprocess debugger and Idle side of debugger RPC link
+
+    Request that the RPCServer shut down the subprocess debugger and link.
+    Unregister the GUIAdapter, which will cause a GC on the Idle process
+    debugger and RPC link objects.  (The second reference to the debugger GUI
+    is deleted in pyshell.close_remote_debugger().)
+
+    """
+    close_subprocess_debugger(rpcclt)
+    rpcclt.unregister(gui_adap_oid)
+
+def close_subprocess_debugger(rpcclt):
+    rpcclt.remotecall("exec", "stop_the_debugger", (idb_adap_oid,), {})
+
+def restart_subprocess_debugger(rpcclt):
+    idb_adap_oid_ret = rpcclt.remotecall("exec", "start_the_debugger",\
+                                         (gui_adap_oid,), {})
+    assert idb_adap_oid_ret == idb_adap_oid, 'Idb restarted with different oid'
+
+
+if __name__ == "__main__":
+    from unittest import main
+    main('idlelib.idle_test.test_debugger_r', verbosity=2, exit=False)
diff --git a/rootfs/usr/lib/python3.8/idlelib/debugobj.py b/rootfs/usr/lib/python3.8/idlelib/debugobj.py
new file mode 100644
index 0000000..5a4c997
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/debugobj.py
@@ -0,0 +1,142 @@
+# XXX TO DO:
+# - popup menu
+# - support partial or total redisplay
+# - more doc strings
+# - tooltips
+
+# object browser
+
+# XXX TO DO:
+# - for classes/modules, add "open source" to object browser
+from reprlib import Repr
+
+from idlelib.tree import TreeItem, TreeNode, ScrolledCanvas
+
+myrepr = Repr()
+myrepr.maxstring = 100
+myrepr.maxother = 100
+
+class ObjectTreeItem(TreeItem):
+    def __init__(self, labeltext, object, setfunction=None):
+        self.labeltext = labeltext
+        self.object = object
+        self.setfunction = setfunction
+    def GetLabelText(self):
+        return self.labeltext
+    def GetText(self):
+        return myrepr.repr(self.object)
+    def GetIconName(self):
+        if not self.IsExpandable():
+            return "python"
+    def IsEditable(self):
+        return self.setfunction is not None
+    def SetText(self, text):
+        try:
+            value = eval(text)
+            self.setfunction(value)
+        except:
+            pass
+        else:
+            self.object = value
+    def IsExpandable(self):
+        return not not dir(self.object)
+    def GetSubList(self):
+        keys = dir(self.object)
+        sublist = []
+        for key in keys:
+            try:
+                value = getattr(self.object, key)
+            except AttributeError:
+                continue
+            item = make_objecttreeitem(
+                str(key) + " =",
+                value,
+                lambda value, key=key, object=self.object:
+                    setattr(object, key, value))
+            sublist.append(item)
+        return sublist
+
+class ClassTreeItem(ObjectTreeItem):
+    def IsExpandable(self):
+        return True
+    def GetSubList(self):
+        sublist = ObjectTreeItem.GetSubList(self)
+        if len(self.object.__bases__) == 1:
+            item = make_objecttreeitem("__bases__[0] =",
+                self.object.__bases__[0])
+        else:
+            item = make_objecttreeitem("__bases__ =", self.object.__bases__)
+        sublist.insert(0, item)
+        return sublist
+
+class AtomicObjectTreeItem(ObjectTreeItem):
+    def IsExpandable(self):
+        return False
+
+class SequenceTreeItem(ObjectTreeItem):
+    def IsExpandable(self):
+        return len(self.object) > 0
+    def keys(self):
+        return range(len(self.object))
+    def GetSubList(self):
+        sublist = []
+        for key in self.keys():
+            try:
+                value = self.object[key]
+            except KeyError:
+                continue
+            def setfunction(value, key=key, object=self.object):
+                object[key] = value
+            item = make_objecttreeitem("%r:" % (key,), value, setfunction)
+            sublist.append(item)
+        return sublist
+
+class DictTreeItem(SequenceTreeItem):
+    def keys(self):
+        keys = list(self.object.keys())
+        try:
+            keys.sort()
+        except:
+            pass
+        return keys
+
+dispatch = {
+    int: AtomicObjectTreeItem,
+    float: AtomicObjectTreeItem,
+    str: AtomicObjectTreeItem,
+    tuple: SequenceTreeItem,
+    list: SequenceTreeItem,
+    dict: DictTreeItem,
+    type: ClassTreeItem,
+}
+
+def make_objecttreeitem(labeltext, object, setfunction=None):
+    t = type(object)
+    if t in dispatch:
+        c = dispatch[t]
+    else:
+        c = ObjectTreeItem
+    return c(labeltext, object, setfunction)
+
+
+def _object_browser(parent):  # htest #
+    import sys
+    from tkinter import Toplevel
+    top = Toplevel(parent)
+    top.title("Test debug object browser")
+    x, y = map(int, parent.geometry().split('+')[1:])
+    top.geometry("+%d+%d" % (x + 100, y + 175))
+    top.configure(bd=0, bg="yellow")
+    top.focus_set()
+    sc = ScrolledCanvas(top, bg="white", highlightthickness=0, takefocus=1)
+    sc.frame.pack(expand=1, fill="both")
+    item = make_objecttreeitem("sys", sys)
+    node = TreeNode(sc.canvas, None, item)
+    node.update()
+
+if __name__ == '__main__':
+    from unittest import main
+    main('idlelib.idle_test.test_debugobj', verbosity=2, exit=False)
+
+    from idlelib.idle_test.htest import run
+    run(_object_browser)
diff --git a/rootfs/usr/lib/python3.8/idlelib/debugobj_r.py b/rootfs/usr/lib/python3.8/idlelib/debugobj_r.py
new file mode 100644
index 0000000..75e75eb
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/debugobj_r.py
@@ -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)
diff --git a/rootfs/usr/lib/python3.8/idlelib/delegator.py b/rootfs/usr/lib/python3.8/idlelib/delegator.py
new file mode 100644
index 0000000..55c95da
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/delegator.py
@@ -0,0 +1,33 @@
+class Delegator:
+
+    def __init__(self, delegate=None):
+        self.delegate = delegate
+        self.__cache = set()
+        # Cache is used to only remove added attributes
+        # when changing the delegate.
+
+    def __getattr__(self, name):
+        attr = getattr(self.delegate, name) # May raise AttributeError
+        setattr(self, name, attr)
+        self.__cache.add(name)
+        return attr
+
+    def resetcache(self):
+        "Removes added attributes while leaving original attributes."
+        # Function is really about resetting 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)
diff --git a/rootfs/usr/lib/python3.8/idlelib/dynoption.py b/rootfs/usr/lib/python3.8/idlelib/dynoption.py
new file mode 100644
index 0000000..9c6ffa4
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/dynoption.py
@@ -0,0 +1,58 @@
+"""
+OptionMenu widget modified to allow dynamic menu reconfiguration
+and setting of highlightthickness
+"""
+import copy
+
+from tkinter import OptionMenu, _setit, StringVar, Button
+
+class DynOptionMenu(OptionMenu):
+    """
+    unlike OptionMenu, our kwargs can include highlightthickness
+    """
+    def __init__(self, master, variable, value, *values, **kwargs):
+        # TODO copy value instead of whole dict
+        kwargsCopy=copy.copy(kwargs)
+        if 'highlightthickness' in list(kwargs.keys()):
+            del(kwargs['highlightthickness'])
+        OptionMenu.__init__(self, master, variable, value, *values, **kwargs)
+        self.config(highlightthickness=kwargsCopy.get('highlightthickness'))
+        #self.menu=self['menu']
+        self.variable=variable
+        self.command=kwargs.get('command')
+
+    def SetMenu(self,valueList,value=None):
+        """
+        clear and reload the menu with a new set of options.
+        valueList - list of new options
+        value - initial value to set the optionmenu's menubutton to
+        """
+        self['menu'].delete(0,'end')
+        for item in valueList:
+            self['menu'].add_command(label=item,
+                    command=_setit(self.variable,item,self.command))
+        if value:
+            self.variable.set(value)
+
+def _dyn_option_menu(parent):  # htest #
+    from tkinter import Toplevel # + StringVar, Button
+
+    top = Toplevel(parent)
+    top.title("Tets dynamic option menu")
+    x, y = map(int, parent.geometry().split('+')[1:])
+    top.geometry("200x100+%d+%d" % (x + 250, y + 175))
+    top.focus_set()
+
+    var = StringVar(top)
+    var.set("Old option set") #Set the default value
+    dyn = DynOptionMenu(top,var, "old1","old2","old3","old4")
+    dyn.pack()
+
+    def update():
+        dyn.SetMenu(["new1","new2","new3","new4"], value="new option set")
+    button = Button(top, text="Change option set", command=update)
+    button.pack()
+
+if __name__ == '__main__':
+    from idlelib.idle_test.htest import run
+    run(_dyn_option_menu)
diff --git a/rootfs/usr/lib/python3.8/idlelib/editor.py b/rootfs/usr/lib/python3.8/idlelib/editor.py
new file mode 100644
index 0000000..b9cb502
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/editor.py
@@ -0,0 +1,1672 @@
+import importlib.abc
+import importlib.util
+import os
+import platform
+import re
+import string
+import sys
+import tokenize
+import traceback
+import webbrowser
+
+from tkinter import *
+from tkinter.font import Font
+from tkinter.ttk import Scrollbar
+from tkinter import simpledialog
+from tkinter import messagebox
+
+from idlelib.config import idleConf
+from idlelib import configdialog
+from idlelib import grep
+from idlelib import help
+from idlelib import help_about
+from idlelib import macosx
+from idlelib.multicall import MultiCallCreator
+from idlelib import pyparse
+from idlelib import query
+from idlelib import replace
+from idlelib import search
+from idlelib.tree import wheel_event
+from idlelib import window
+
+# The default tab setting for a Text widget, in average-width characters.
+TK_TABWIDTH_DEFAULT = 8
+_py_version = ' (%s)' % platform.python_version()
+darwin = sys.platform == 'darwin'
+
+def _sphinx_version():
+    "Format sys.version_info to produce the Sphinx version string used to install the chm docs"
+    major, minor, micro, level, serial = sys.version_info
+    release = '%s%s' % (major, minor)
+    release += '%s' % (micro,)
+    if level == 'candidate':
+        release += 'rc%s' % (serial,)
+    elif level != 'final':
+        release += '%s%s' % (level[0], serial)
+    return release
+
+
+class EditorWindow:
+    from idlelib.percolator import Percolator
+    from idlelib.colorizer import ColorDelegator, color_config
+    from idlelib.undo import UndoDelegator
+    from idlelib.iomenu import IOBinding, encoding
+    from idlelib import mainmenu
+    from idlelib.statusbar import MultiStatusBar
+    from idlelib.autocomplete import AutoComplete
+    from idlelib.autoexpand import AutoExpand
+    from idlelib.calltip import Calltip
+    from idlelib.codecontext import CodeContext
+    from idlelib.sidebar import LineNumbers
+    from idlelib.format import FormatParagraph, FormatRegion, Indents, Rstrip
+    from idlelib.parenmatch import ParenMatch
+    from idlelib.squeezer import Squeezer
+    from idlelib.zoomheight import ZoomHeight
+
+    filesystemencoding = sys.getfilesystemencoding()  # for file names
+    help_url = None
+
+    allow_code_context = True
+    allow_line_numbers = True
+
+    def __init__(self, flist=None, filename=None, key=None, root=None):
+        # Delay import: runscript imports pyshell imports EditorWindow.
+        from idlelib.runscript import ScriptBinding
+
+        if EditorWindow.help_url is None:
+            dochome =  os.path.join(sys.base_prefix, 'Doc', 'index.html')
+            if sys.platform.count('linux'):
+                # look for html docs in a couple of standard places
+                pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
+                if os.path.isdir('/var/www/html/python/'):  # "python2" rpm
+                    dochome = '/var/www/html/python/index.html'
+                else:
+                    basepath = '/usr/share/doc/'  # standard location
+                    dochome = os.path.join(basepath, pyver,
+                                           'Doc', 'index.html')
+            elif sys.platform[:3] == 'win':
+                chmfile = os.path.join(sys.base_prefix, 'Doc',
+                                       'Python%s.chm' % _sphinx_version())
+                if os.path.isfile(chmfile):
+                    dochome = chmfile
+            elif sys.platform == 'darwin':
+                # documentation may be stored inside a python framework
+                dochome = os.path.join(sys.base_prefix,
+                        'Resources/English.lproj/Documentation/index.html')
+            dochome = os.path.normpath(dochome)
+            if os.path.isfile(dochome):
+                EditorWindow.help_url = dochome
+                if sys.platform == 'darwin':
+                    # Safari requires real file:-URLs
+                    EditorWindow.help_url = 'file://' + EditorWindow.help_url
+            else:
+                EditorWindow.help_url = ("https://docs.python.org/%d.%d/"
+                                         % sys.version_info[:2])
+        self.flist = flist
+        root = root or flist.root
+        self.root = root
+        self.menubar = Menu(root)
+        self.top = top = window.ListedToplevel(root, menu=self.menubar)
+        if flist:
+            self.tkinter_vars = flist.vars
+            #self.top.instance_dict makes flist.inversedict available to
+            #configdialog.py so it can access all EditorWindow instances
+            self.top.instance_dict = flist.inversedict
+        else:
+            self.tkinter_vars = {}  # keys: Tkinter event names
+                                    # values: Tkinter variable instances
+            self.top.instance_dict = {}
+        self.recent_files_path = idleConf.userdir and os.path.join(
+                idleConf.userdir, 'recent-files.lst')
+
+        self.prompt_last_line = ''  # Override in PyShell
+        self.text_frame = text_frame = Frame(top)
+        self.vbar = vbar = Scrollbar(text_frame, name='vbar')
+        width = idleConf.GetOption('main', 'EditorWindow', 'width', type='int')
+        text_options = {
+                'name': 'text',
+                'padx': 5,
+                'wrap': 'none',
+                'highlightthickness': 0,
+                'width': width,
+                'tabstyle': 'wordprocessor',  # new in 8.5
+                'height': idleConf.GetOption(
+                        'main', 'EditorWindow', 'height', type='int'),
+                }
+        self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
+        self.top.focused_widget = self.text
+
+        self.createmenubar()
+        self.apply_bindings()
+
+        self.top.protocol("WM_DELETE_WINDOW", self.close)
+        self.top.bind("<<close-window>>", self.close_event)
+        if macosx.isAquaTk():
+            # Command-W on editor windows doesn't work without this.
+            text.bind('<<close-window>>', self.close_event)
+            # Some OS X systems have only one mouse button, so use
+            # control-click for popup context menus there. For two
+            # buttons, AquaTk defines <2> as the right button, not <3>.
+            text.bind("<Control-Button-1>",self.right_menu_event)
+            text.bind("<2>", self.right_menu_event)
+        else:
+            # Elsewhere, use right-click for popup menus.
+            text.bind("<3>",self.right_menu_event)
+
+        text.bind('<MouseWheel>', wheel_event)
+        text.bind('<Button-4>', wheel_event)
+        text.bind('<Button-5>', wheel_event)
+        text.bind('<Configure>', self.handle_winconfig)
+        text.bind("<<cut>>", self.cut)
+        text.bind("<<copy>>", self.copy)
+        text.bind("<<paste>>", self.paste)
+        text.bind("<<center-insert>>", self.center_insert_event)
+        text.bind("<<help>>", self.help_dialog)
+        text.bind("<<python-docs>>", self.python_docs)
+        text.bind("<<about-idle>>", self.about_dialog)
+        text.bind("<<open-config-dialog>>", self.config_dialog)
+        text.bind("<<open-module>>", self.open_module_event)
+        text.bind("<<do-nothing>>", lambda event: "break")
+        text.bind("<<select-all>>", self.select_all)
+        text.bind("<<remove-selection>>", self.remove_selection)
+        text.bind("<<find>>", self.find_event)
+        text.bind("<<find-again>>", self.find_again_event)
+        text.bind("<<find-in-files>>", self.find_in_files_event)
+        text.bind("<<find-selection>>", self.find_selection_event)
+        text.bind("<<replace>>", self.replace_event)
+        text.bind("<<goto-line>>", self.goto_line_event)
+        text.bind("<<smart-backspace>>",self.smart_backspace_event)
+        text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
+        text.bind("<<smart-indent>>",self.smart_indent_event)
+        self.fregion = fregion = self.FormatRegion(self)
+        # self.fregion used in smart_indent_event to access indent_region.
+        text.bind("<<indent-region>>", fregion.indent_region_event)
+        text.bind("<<dedent-region>>", fregion.dedent_region_event)
+        text.bind("<<comment-region>>", fregion.comment_region_event)
+        text.bind("<<uncomment-region>>", fregion.uncomment_region_event)
+        text.bind("<<tabify-region>>", fregion.tabify_region_event)
+        text.bind("<<untabify-region>>", fregion.untabify_region_event)
+        indents = self.Indents(self)
+        text.bind("<<toggle-tabs>>", indents.toggle_tabs_event)
+        text.bind("<<change-indentwidth>>", indents.change_indentwidth_event)
+        text.bind("<Left>", self.move_at_edge_if_selection(0))
+        text.bind("<Right>", self.move_at_edge_if_selection(1))
+        text.bind("<<del-word-left>>", self.del_word_left)
+        text.bind("<<del-word-right>>", self.del_word_right)
+        text.bind("<<beginning-of-line>>", self.home_callback)
+
+        if flist:
+            flist.inversedict[self] = key
+            if key:
+                flist.dict[key] = self
+            text.bind("<<open-new-window>>", self.new_callback)
+            text.bind("<<close-all-windows>>", self.flist.close_all_callback)
+            text.bind("<<open-class-browser>>", self.open_module_browser)
+            text.bind("<<open-path-browser>>", self.open_path_browser)
+            text.bind("<<open-turtle-demo>>", self.open_turtle_demo)
+
+        self.set_status_bar()
+        text_frame.pack(side=LEFT, fill=BOTH, expand=1)
+        text_frame.rowconfigure(1, weight=1)
+        text_frame.columnconfigure(1, weight=1)
+        vbar['command'] = self.handle_yview
+        vbar.grid(row=1, column=2, sticky=NSEW)
+        text['yscrollcommand'] = vbar.set
+        text['font'] = idleConf.GetFont(self.root, 'main', 'EditorWindow')
+        text.grid(row=1, column=1, sticky=NSEW)
+        text.focus_set()
+        self.set_width()
+
+        # usetabs true  -> literal tab characters are used by indent and
+        #                  dedent cmds, possibly mixed with spaces if
+        #                  indentwidth is not a multiple of tabwidth,
+        #                  which will cause Tabnanny to nag!
+        #         false -> tab characters are converted to spaces by indent
+        #                  and dedent cmds, and ditto TAB keystrokes
+        # Although use-spaces=0 can be configured manually in config-main.def,
+        # configuration of tabs v. spaces is not supported in the configuration
+        # dialog.  IDLE promotes the preferred Python indentation: use spaces!
+        usespaces = idleConf.GetOption('main', 'Indent',
+                                       'use-spaces', type='bool')
+        self.usetabs = not usespaces
+
+        # tabwidth is the display width of a literal tab character.
+        # CAUTION:  telling Tk to use anything other than its default
+        # tab setting causes it to use an entirely different tabbing algorithm,
+        # treating tab stops as fixed distances from the left margin.
+        # Nobody expects this, so for now tabwidth should never be changed.
+        self.tabwidth = 8    # must remain 8 until Tk is fixed.
+
+        # indentwidth is the number of screen characters per indent level.
+        # The recommended Python indentation is four spaces.
+        self.indentwidth = self.tabwidth
+        self.set_notabs_indentwidth()
+
+        # Store the current value of the insertofftime now so we can restore
+        # it if needed.
+        if not hasattr(idleConf, 'blink_off_time'):
+            idleConf.blink_off_time = self.text['insertofftime']
+        self.update_cursor_blink()
+
+        # When searching backwards for a reliable place to begin parsing,
+        # first start num_context_lines[0] lines back, then
+        # num_context_lines[1] lines back if that didn't work, and so on.
+        # The last value should be huge (larger than the # of lines in a
+        # conceivable file).
+        # Making the initial values larger slows things down more often.
+        self.num_context_lines = 50, 500, 5000000
+        self.per = per = self.Percolator(text)
+        self.undo = undo = self.UndoDelegator()
+        per.insertfilter(undo)
+        text.undo_block_start = undo.undo_block_start
+        text.undo_block_stop = undo.undo_block_stop
+        undo.set_saved_change_hook(self.saved_change_hook)
+        # IOBinding implements file I/O and printing functionality
+        self.io = io = self.IOBinding(self)
+        io.set_filename_change_hook(self.filename_change_hook)
+        self.good_load = False
+        self.set_indentation_params(False)
+        self.color = None # initialized below in self.ResetColorizer
+        self.code_context = None # optionally initialized later below
+        self.line_numbers = None # optionally initialized later below
+        if filename:
+            if os.path.exists(filename) and not os.path.isdir(filename):
+                if io.loadfile(filename):
+                    self.good_load = True
+                    is_py_src = self.ispythonsource(filename)
+                    self.set_indentation_params(is_py_src)
+            else:
+                io.set_filename(filename)
+                self.good_load = True
+
+        self.ResetColorizer()
+        self.saved_change_hook()
+        self.update_recent_files_list()
+        self.load_extensions()
+        menu = self.menudict.get('window')
+        if menu:
+            end = menu.index("end")
+            if end is None:
+                end = -1
+            if end >= 0:
+                menu.add_separator()
+                end = end + 1
+            self.wmenu_end = end
+            window.register_callback(self.postwindowsmenu)
+
+        # Some abstractions so IDLE extensions are cross-IDE
+        self.askinteger = simpledialog.askinteger
+        self.askyesno = messagebox.askyesno
+        self.showerror = messagebox.showerror
+
+        # Add pseudoevents for former extension fixed keys.
+        # (This probably needs to be done once in the process.)
+        text.event_add('<<autocomplete>>', '<Key-Tab>')
+        text.event_add('<<try-open-completions>>', '<KeyRelease-period>',
+                       '<KeyRelease-slash>', '<KeyRelease-backslash>')
+        text.event_add('<<try-open-calltip>>', '<KeyRelease-parenleft>')
+        text.event_add('<<refresh-calltip>>', '<KeyRelease-parenright>')
+        text.event_add('<<paren-closed>>', '<KeyRelease-parenright>',
+                       '<KeyRelease-bracketright>', '<KeyRelease-braceright>')
+
+        # Former extension bindings depends on frame.text being packed
+        # (called from self.ResetColorizer()).
+        autocomplete = self.AutoComplete(self)
+        text.bind("<<autocomplete>>", autocomplete.autocomplete_event)
+        text.bind("<<try-open-completions>>",
+                  autocomplete.try_open_completions_event)
+        text.bind("<<force-open-completions>>",
+                  autocomplete.force_open_completions_event)
+        text.bind("<<expand-word>>", self.AutoExpand(self).expand_word_event)
+        text.bind("<<format-paragraph>>",
+                  self.FormatParagraph(self).format_paragraph_event)
+        parenmatch = self.ParenMatch(self)
+        text.bind("<<flash-paren>>", parenmatch.flash_paren_event)
+        text.bind("<<paren-closed>>", parenmatch.paren_closed_event)
+        scriptbinding = ScriptBinding(self)
+        text.bind("<<check-module>>", scriptbinding.check_module_event)
+        text.bind("<<run-module>>", scriptbinding.run_module_event)
+        text.bind("<<run-custom>>", scriptbinding.run_custom_event)
+        text.bind("<<do-rstrip>>", self.Rstrip(self).do_rstrip)
+        self.ctip = ctip = self.Calltip(self)
+        text.bind("<<try-open-calltip>>", ctip.try_open_calltip_event)
+        #refresh-calltip must come after paren-closed to work right
+        text.bind("<<refresh-calltip>>", ctip.refresh_calltip_event)
+        text.bind("<<force-open-calltip>>", ctip.force_open_calltip_event)
+        text.bind("<<zoom-height>>", self.ZoomHeight(self).zoom_height_event)
+        if self.allow_code_context:
+            self.code_context = self.CodeContext(self)
+            text.bind("<<toggle-code-context>>",
+                      self.code_context.toggle_code_context_event)
+        else:
+            self.update_menu_state('options', '*ode*ontext', 'disabled')
+        if self.allow_line_numbers:
+            self.line_numbers = self.LineNumbers(self)
+            if idleConf.GetOption('main', 'EditorWindow',
+                                  'line-numbers-default', type='bool'):
+                self.toggle_line_numbers_event()
+            text.bind("<<toggle-line-numbers>>", self.toggle_line_numbers_event)
+        else:
+            self.update_menu_state('options', '*ine*umbers', 'disabled')
+
+    def handle_winconfig(self, event=None):
+        self.set_width()
+
+    def set_width(self):
+        text = self.text
+        inner_padding = sum(map(text.tk.getint, [text.cget('border'),
+                                                 text.cget('padx')]))
+        pixel_width = text.winfo_width() - 2 * inner_padding
+
+        # Divide the width of the Text widget by the font width,
+        # which is taken to be the width of '0' (zero).
+        # http://www.tcl.tk/man/tcl8.6/TkCmd/text.htm#M21
+        zero_char_width = \
+            Font(text, font=text.cget('font')).measure('0')
+        self.width = pixel_width // zero_char_width
+
+    def new_callback(self, event):
+        dirname, basename = self.io.defaultfilename()
+        self.flist.new(dirname)
+        return "break"
+
+    def home_callback(self, event):
+        if (event.state & 4) != 0 and event.keysym == "Home":
+            # state&4==Control. If <Control-Home>, use the Tk binding.
+            return None
+        if self.text.index("iomark") and \
+           self.text.compare("iomark", "<=", "insert lineend") and \
+           self.text.compare("insert linestart", "<=", "iomark"):
+            # In Shell on input line, go to just after prompt
+            insertpt = int(self.text.index("iomark").split(".")[1])
+        else:
+            line = self.text.get("insert linestart", "insert lineend")
+            for insertpt in range(len(line)):
+                if line[insertpt] not in (' ','\t'):
+                    break
+            else:
+                insertpt=len(line)
+        lineat = int(self.text.index("insert").split('.')[1])
+        if insertpt == lineat:
+            insertpt = 0
+        dest = "insert linestart+"+str(insertpt)+"c"
+        if (event.state&1) == 0:
+            # shift was not pressed
+            self.text.tag_remove("sel", "1.0", "end")
+        else:
+            if not self.text.index("sel.first"):
+                # there was no previous selection
+                self.text.mark_set("my_anchor", "insert")
+            else:
+                if self.text.compare(self.text.index("sel.first"), "<",
+                                     self.text.index("insert")):
+                    self.text.mark_set("my_anchor", "sel.first") # extend back
+                else:
+                    self.text.mark_set("my_anchor", "sel.last") # extend forward
+            first = self.text.index(dest)
+            last = self.text.index("my_anchor")
+            if self.text.compare(first,">",last):
+                first,last = last,first
+            self.text.tag_remove("sel", "1.0", "end")
+            self.text.tag_add("sel", first, last)
+        self.text.mark_set("insert", dest)
+        self.text.see("insert")
+        return "break"
+
+    def set_status_bar(self):
+        self.status_bar = self.MultiStatusBar(self.top)
+        sep = Frame(self.top, height=1, borderwidth=1, background='grey75')
+        if sys.platform == "darwin":
+            # Insert some padding to avoid obscuring some of the statusbar
+            # by the resize widget.
+            self.status_bar.set_label('_padding1', '    ', side=RIGHT)
+        self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
+        self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
+        self.status_bar.pack(side=BOTTOM, fill=X)
+        sep.pack(side=BOTTOM, fill=X)
+        self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
+        self.text.event_add("<<set-line-and-column>>",
+                            "<KeyRelease>", "<ButtonRelease>")
+        self.text.after_idle(self.set_line_and_column)
+
+    def set_line_and_column(self, event=None):
+        line, column = self.text.index(INSERT).split('.')
+        self.status_bar.set_label('column', 'Col: %s' % column)
+        self.status_bar.set_label('line', 'Ln: %s' % line)
+
+    menu_specs = [
+        ("file", "_File"),
+        ("edit", "_Edit"),
+        ("format", "F_ormat"),
+        ("run", "_Run"),
+        ("options", "_Options"),
+        ("window", "_Window"),
+        ("help", "_Help"),
+    ]
+
+
+    def createmenubar(self):
+        mbar = self.menubar
+        self.menudict = menudict = {}
+        for name, label in self.menu_specs:
+            underline, label = prepstr(label)
+            postcommand = getattr(self, f'{name}_menu_postcommand', None)
+            menudict[name] = menu = Menu(mbar, name=name, tearoff=0,
+                                         postcommand=postcommand)
+            mbar.add_cascade(label=label, menu=menu, underline=underline)
+        if macosx.isCarbonTk():
+            # Insert the application menu
+            menudict['application'] = menu = Menu(mbar, name='apple',
+                                                  tearoff=0)
+            mbar.add_cascade(label='IDLE', menu=menu)
+        self.fill_menus()
+        self.recent_files_menu = Menu(self.menubar, tearoff=0)
+        self.menudict['file'].insert_cascade(3, label='Recent Files',
+                                             underline=0,
+                                             menu=self.recent_files_menu)
+        self.base_helpmenu_length = self.menudict['help'].index(END)
+        self.reset_help_menu_entries()
+
+    def postwindowsmenu(self):
+        # Only called when Window menu exists
+        menu = self.menudict['window']
+        end = menu.index("end")
+        if end is None:
+            end = -1
+        if end > self.wmenu_end:
+            menu.delete(self.wmenu_end+1, end)
+        window.add_windows_to_menu(menu)
+
+    def update_menu_label(self, menu, index, label):
+        "Update label for menu item at index."
+        menuitem = self.menudict[menu]
+        menuitem.entryconfig(index, label=label)
+
+    def update_menu_state(self, menu, index, state):
+        "Update state for menu item at index."
+        menuitem = self.menudict[menu]
+        menuitem.entryconfig(index, state=state)
+
+    def handle_yview(self, event, *args):
+        "Handle scrollbar."
+        if event == 'moveto':
+            fraction = float(args[0])
+            lines = (round(self.getlineno('end') * fraction) -
+                     self.getlineno('@0,0'))
+            event = 'scroll'
+            args = (lines, 'units')
+        self.text.yview(event, *args)
+        return 'break'
+
+    rmenu = None
+
+    def right_menu_event(self, event):
+        text = self.text
+        newdex = text.index(f'@{event.x},{event.y}')
+        try:
+            in_selection = (text.compare('sel.first', '<=', newdex) and
+                           text.compare(newdex, '<=',  'sel.last'))
+        except TclError:
+            in_selection = False
+        if not in_selection:
+            text.tag_remove("sel", "1.0", "end")
+            text.mark_set("insert", newdex)
+        if not self.rmenu:
+            self.make_rmenu()
+        rmenu = self.rmenu
+        self.event = event
+        iswin = sys.platform[:3] == 'win'
+        if iswin:
+            text.config(cursor="arrow")
+
+        for item in self.rmenu_specs:
+            try:
+                label, eventname, verify_state = item
+            except ValueError: # see issue1207589
+                continue
+
+            if verify_state is None:
+                continue
+            state = getattr(self, verify_state)()
+            rmenu.entryconfigure(label, state=state)
+
+        rmenu.tk_popup(event.x_root, event.y_root)
+        if iswin:
+            self.text.config(cursor="ibeam")
+        return "break"
+
+    rmenu_specs = [
+        # ("Label", "<<virtual-event>>", "statefuncname"), ...
+        ("Close", "<<close-window>>", None), # Example
+    ]
+
+    def make_rmenu(self):
+        rmenu = Menu(self.text, tearoff=0)
+        for item in self.rmenu_specs:
+            label, eventname = item[0], item[1]
+            if label is not None:
+                def command(text=self.text, eventname=eventname):
+                    text.event_generate(eventname)
+                rmenu.add_command(label=label, command=command)
+            else:
+                rmenu.add_separator()
+        self.rmenu = rmenu
+
+    def rmenu_check_cut(self):
+        return self.rmenu_check_copy()
+
+    def rmenu_check_copy(self):
+        try:
+            indx = self.text.index('sel.first')
+        except TclError:
+            return 'disabled'
+        else:
+            return 'normal' if indx else 'disabled'
+
+    def rmenu_check_paste(self):
+        try:
+            self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
+        except TclError:
+            return 'disabled'
+        else:
+            return 'normal'
+
+    def about_dialog(self, event=None):
+        "Handle Help 'About IDLE' event."
+        # Synchronize with macosx.overrideRootMenu.about_dialog.
+        help_about.AboutDialog(self.top)
+        return "break"
+
+    def config_dialog(self, event=None):
+        "Handle Options 'Configure IDLE' event."
+        # Synchronize with macosx.overrideRootMenu.config_dialog.
+        configdialog.ConfigDialog(self.top,'Settings')
+        return "break"
+
+    def help_dialog(self, event=None):
+        "Handle Help 'IDLE Help' event."
+        # Synchronize with macosx.overrideRootMenu.help_dialog.
+        if self.root:
+            parent = self.root
+        else:
+            parent = self.top
+        help.show_idlehelp(parent)
+        return "break"
+
+    def python_docs(self, event=None):
+        if sys.platform[:3] == 'win':
+            try:
+                os.startfile(self.help_url)
+            except OSError as why:
+                messagebox.showerror(title='Document Start Failure',
+                    message=str(why), parent=self.text)
+        else:
+            webbrowser.open(self.help_url)
+        return "break"
+
+    def cut(self,event):
+        self.text.event_generate("<<Cut>>")
+        return "break"
+
+    def copy(self,event):
+        if not self.text.tag_ranges("sel"):
+            # There is no selection, so do nothing and maybe interrupt.
+            return None
+        self.text.event_generate("<<Copy>>")
+        return "break"
+
+    def paste(self,event):
+        self.text.event_generate("<<Paste>>")
+        self.text.see("insert")
+        return "break"
+
+    def select_all(self, event=None):
+        self.text.tag_add("sel", "1.0", "end-1c")
+        self.text.mark_set("insert", "1.0")
+        self.text.see("insert")
+        return "break"
+
+    def remove_selection(self, event=None):
+        self.text.tag_remove("sel", "1.0", "end")
+        self.text.see("insert")
+        return "break"
+
+    def move_at_edge_if_selection(self, edge_index):
+        """Cursor move begins at start or end of selection
+
+        When a left/right cursor key is pressed create and return to Tkinter a
+        function which causes a cursor move from the associated edge of the
+        selection.
+
+        """
+        self_text_index = self.text.index
+        self_text_mark_set = self.text.mark_set
+        edges_table = ("sel.first+1c", "sel.last-1c")
+        def move_at_edge(event):
+            if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
+                try:
+                    self_text_index("sel.first")
+                    self_text_mark_set("insert", edges_table[edge_index])
+                except TclError:
+                    pass
+        return move_at_edge
+
+    def del_word_left(self, event):
+        self.text.event_generate('<Meta-Delete>')
+        return "break"
+
+    def del_word_right(self, event):
+        self.text.event_generate('<Meta-d>')
+        return "break"
+
+    def find_event(self, event):
+        search.find(self.text)
+        return "break"
+
+    def find_again_event(self, event):
+        search.find_again(self.text)
+        return "break"
+
+    def find_selection_event(self, event):
+        search.find_selection(self.text)
+        return "break"
+
+    def find_in_files_event(self, event):
+        grep.grep(self.text, self.io, self.flist)
+        return "break"
+
+    def replace_event(self, event):
+        replace.replace(self.text)
+        return "break"
+
+    def goto_line_event(self, event):
+        text = self.text
+        lineno = query.Goto(
+                text, "Go To Line",
+                "Enter a positive integer\n"
+                "('big' = end of file):"
+                ).result
+        if lineno is not None:
+            text.tag_remove("sel", "1.0", "end")
+            text.mark_set("insert", f'{lineno}.0')
+            text.see("insert")
+            self.set_line_and_column()
+        return "break"
+
+    def open_module(self):
+        """Get module name from user and open it.
+
+        Return module path or None for calls by open_module_browser
+        when latter is not invoked in named editor window.
+        """
+        # XXX This, open_module_browser, and open_path_browser
+        # would fit better in iomenu.IOBinding.
+        try:
+            name = self.text.get("sel.first", "sel.last").strip()
+        except TclError:
+            name = ''
+        file_path = query.ModuleName(
+                self.text, "Open Module",
+                "Enter the name of a Python module\n"
+                "to search on sys.path and open:",
+                name).result
+        if file_path is not None:
+            if self.flist:
+                self.flist.open(file_path)
+            else:
+                self.io.loadfile(file_path)
+        return file_path
+
+    def open_module_event(self, event):
+        self.open_module()
+        return "break"
+
+    def open_module_browser(self, event=None):
+        filename = self.io.filename
+        if not (self.__class__.__name__ == 'PyShellEditorWindow'
+                and filename):
+            filename = self.open_module()
+            if filename is None:
+                return "break"
+        from idlelib import browser
+        browser.ModuleBrowser(self.root, filename)
+        return "break"
+
+    def open_path_browser(self, event=None):
+        from idlelib import pathbrowser
+        pathbrowser.PathBrowser(self.root)
+        return "break"
+
+    def open_turtle_demo(self, event = None):
+        import subprocess
+
+        cmd = [sys.executable,
+               '-c',
+               'from turtledemo.__main__ import main; main()']
+        subprocess.Popen(cmd, shell=False)
+        return "break"
+
+    def gotoline(self, lineno):
+        if lineno is not None and lineno > 0:
+            self.text.mark_set("insert", "%d.0" % lineno)
+            self.text.tag_remove("sel", "1.0", "end")
+            self.text.tag_add("sel", "insert", "insert +1l")
+            self.center()
+
+    def ispythonsource(self, filename):
+        if not filename or os.path.isdir(filename):
+            return True
+        base, ext = os.path.splitext(os.path.basename(filename))
+        if os.path.normcase(ext) in (".py", ".pyw"):
+            return True
+        line = self.text.get('1.0', '1.0 lineend')
+        return line.startswith('#!') and 'python' in line
+
+    def close_hook(self):
+        if self.flist:
+            self.flist.unregister_maybe_terminate(self)
+            self.flist = None
+
+    def set_close_hook(self, close_hook):
+        self.close_hook = close_hook
+
+    def filename_change_hook(self):
+        if self.flist:
+            self.flist.filename_changed_edit(self)
+        self.saved_change_hook()
+        self.top.update_windowlist_registry(self)
+        self.ResetColorizer()
+
+    def _addcolorizer(self):
+        if self.color:
+            return
+        if self.ispythonsource(self.io.filename):
+            self.color = self.ColorDelegator()
+        # can add more colorizers here...
+        if self.color:
+            self.per.removefilter(self.undo)
+            self.per.insertfilter(self.color)
+            self.per.insertfilter(self.undo)
+
+    def _rmcolorizer(self):
+        if not self.color:
+            return
+        self.color.removecolors()
+        self.per.removefilter(self.color)
+        self.color = None
+
+    def ResetColorizer(self):
+        "Update the color theme"
+        # Called from self.filename_change_hook and from configdialog.py
+        self._rmcolorizer()
+        self._addcolorizer()
+        EditorWindow.color_config(self.text)
+
+        if self.code_context is not None:
+            self.code_context.update_highlight_colors()
+
+        if self.line_numbers is not None:
+            self.line_numbers.update_colors()
+
+    IDENTCHARS = string.ascii_letters + string.digits + "_"
+
+    def colorize_syntax_error(self, text, pos):
+        text.tag_add("ERROR", pos)
+        char = text.get(pos)
+        if char and char in self.IDENTCHARS:
+            text.tag_add("ERROR", pos + " wordstart", pos)
+        if '\n' == text.get(pos):   # error at line end
+            text.mark_set("insert", pos)
+        else:
+            text.mark_set("insert", pos + "+1c")
+        text.see(pos)
+
+    def update_cursor_blink(self):
+        "Update the cursor blink configuration."
+        cursorblink = idleConf.GetOption(
+                'main', 'EditorWindow', 'cursor-blink', type='bool')
+        if not cursorblink:
+            self.text['insertofftime'] = 0
+        else:
+            # Restore the original value
+            self.text['insertofftime'] = idleConf.blink_off_time
+
+    def ResetFont(self):
+        "Update the text widgets' font if it is changed"
+        # Called from configdialog.py
+
+        # Update the code context widget first, since its height affects
+        # the height of the text widget.  This avoids double re-rendering.
+        if self.code_context is not None:
+            self.code_context.update_font()
+        # Next, update the line numbers widget, since its width affects
+        # the width of the text widget.
+        if self.line_numbers is not None:
+            self.line_numbers.update_font()
+        # Finally, update the main text widget.
+        new_font = idleConf.GetFont(self.root, 'main', 'EditorWindow')
+        self.text['font'] = new_font
+        self.set_width()
+
+    def RemoveKeybindings(self):
+        "Remove the keybindings before they are changed."
+        # Called from configdialog.py
+        self.mainmenu.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
+        for event, keylist in keydefs.items():
+            self.text.event_delete(event, *keylist)
+        for extensionName in self.get_standard_extension_names():
+            xkeydefs = idleConf.GetExtensionBindings(extensionName)
+            if xkeydefs:
+                for event, keylist in xkeydefs.items():
+                    self.text.event_delete(event, *keylist)
+
+    def ApplyKeybindings(self):
+        "Update the keybindings after they are changed"
+        # Called from configdialog.py
+        self.mainmenu.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
+        self.apply_bindings()
+        for extensionName in self.get_standard_extension_names():
+            xkeydefs = idleConf.GetExtensionBindings(extensionName)
+            if xkeydefs:
+                self.apply_bindings(xkeydefs)
+        #update menu accelerators
+        menuEventDict = {}
+        for menu in self.mainmenu.menudefs:
+            menuEventDict[menu[0]] = {}
+            for item in menu[1]:
+                if item:
+                    menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
+        for menubarItem in self.menudict:
+            menu = self.menudict[menubarItem]
+            end = menu.index(END)
+            if end is None:
+                # Skip empty menus
+                continue
+            end += 1
+            for index in range(0, end):
+                if menu.type(index) == 'command':
+                    accel = menu.entrycget(index, 'accelerator')
+                    if accel:
+                        itemName = menu.entrycget(index, 'label')
+                        event = ''
+                        if menubarItem in menuEventDict:
+                            if itemName in menuEventDict[menubarItem]:
+                                event = menuEventDict[menubarItem][itemName]
+                        if event:
+                            accel = get_accelerator(keydefs, event)
+                            menu.entryconfig(index, accelerator=accel)
+
+    def set_notabs_indentwidth(self):
+        "Update the indentwidth if changed and not using tabs in this window"
+        # Called from configdialog.py
+        if not self.usetabs:
+            self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
+                                                  type='int')
+
+    def reset_help_menu_entries(self):
+        "Update the additional help entries on the Help menu"
+        help_list = idleConf.GetAllExtraHelpSourcesList()
+        helpmenu = self.menudict['help']
+        # first delete the extra help entries, if any
+        helpmenu_length = helpmenu.index(END)
+        if helpmenu_length > self.base_helpmenu_length:
+            helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
+        # then rebuild them
+        if help_list:
+            helpmenu.add_separator()
+            for entry in help_list:
+                cmd = self.__extra_help_callback(entry[1])
+                helpmenu.add_command(label=entry[0], command=cmd)
+        # and update the menu dictionary
+        self.menudict['help'] = helpmenu
+
+    def __extra_help_callback(self, helpfile):
+        "Create a callback with the helpfile value frozen at definition time"
+        def display_extra_help(helpfile=helpfile):
+            if not helpfile.startswith(('www', 'http')):
+                helpfile = os.path.normpath(helpfile)
+            if sys.platform[:3] == 'win':
+                try:
+                    os.startfile(helpfile)
+                except OSError as why:
+                    messagebox.showerror(title='Document Start Failure',
+                        message=str(why), parent=self.text)
+            else:
+                webbrowser.open(helpfile)
+        return display_extra_help
+
+    def update_recent_files_list(self, new_file=None):
+        "Load and update the recent files list and menus"
+        # TODO: move to iomenu.
+        rf_list = []
+        file_path = self.recent_files_path
+        if file_path and os.path.exists(file_path):
+            with open(file_path, 'r',
+                      encoding='utf_8', errors='replace') as rf_list_file:
+                rf_list = rf_list_file.readlines()
+        if new_file:
+            new_file = os.path.abspath(new_file) + '\n'
+            if new_file in rf_list:
+                rf_list.remove(new_file)  # move to top
+            rf_list.insert(0, new_file)
+        # clean and save the recent files list
+        bad_paths = []
+        for path in rf_list:
+            if '\0' in path or not os.path.exists(path[0:-1]):
+                bad_paths.append(path)
+        rf_list = [path for path in rf_list if path not in bad_paths]
+        ulchars = "1234567890ABCDEFGHIJK"
+        rf_list = rf_list[0:len(ulchars)]
+        if file_path:
+            try:
+                with open(file_path, 'w',
+                          encoding='utf_8', errors='replace') as rf_file:
+                    rf_file.writelines(rf_list)
+            except OSError as err:
+                if not getattr(self.root, "recentfiles_message", False):
+                    self.root.recentfiles_message = True
+                    messagebox.showwarning(title='IDLE Warning',
+                        message="Cannot save Recent Files list to disk.\n"
+                                f"  {err}\n"
+                                "Select OK to continue.",
+                        parent=self.text)
+        # for each edit window instance, construct the recent files menu
+        for instance in self.top.instance_dict:
+            menu = instance.recent_files_menu
+            menu.delete(0, END)  # clear, and rebuild:
+            for i, file_name in enumerate(rf_list):
+                file_name = file_name.rstrip()  # zap \n
+                callback = instance.__recent_file_callback(file_name)
+                menu.add_command(label=ulchars[i] + " " + file_name,
+                                 command=callback,
+                                 underline=0)
+
+    def __recent_file_callback(self, file_name):
+        def open_recent_file(fn_closure=file_name):
+            self.io.open(editFile=fn_closure)
+        return open_recent_file
+
+    def saved_change_hook(self):
+        short = self.short_title()
+        long = self.long_title()
+        if short and long:
+            title = short + " - " + long + _py_version
+        elif short:
+            title = short
+        elif long:
+            title = long
+        else:
+            title = "untitled"
+        icon = short or long or title
+        if not self.get_saved():
+            title = "*%s*" % title
+            icon = "*%s" % icon
+        self.top.wm_title(title)
+        self.top.wm_iconname(icon)
+
+    def get_saved(self):
+        return self.undo.get_saved()
+
+    def set_saved(self, flag):
+        self.undo.set_saved(flag)
+
+    def reset_undo(self):
+        self.undo.reset_undo()
+
+    def short_title(self):
+        filename = self.io.filename
+        return os.path.basename(filename) if filename else "untitled"
+
+    def long_title(self):
+        return self.io.filename or ""
+
+    def center_insert_event(self, event):
+        self.center()
+        return "break"
+
+    def center(self, mark="insert"):
+        text = self.text
+        top, bot = self.getwindowlines()
+        lineno = self.getlineno(mark)
+        height = bot - top
+        newtop = max(1, lineno - height//2)
+        text.yview(float(newtop))
+
+    def getwindowlines(self):
+        text = self.text
+        top = self.getlineno("@0,0")
+        bot = self.getlineno("@0,65535")
+        if top == bot and text.winfo_height() == 1:
+            # Geometry manager hasn't run yet
+            height = int(text['height'])
+            bot = top + height - 1
+        return top, bot
+
+    def getlineno(self, mark="insert"):
+        text = self.text
+        return int(float(text.index(mark)))
+
+    def get_geometry(self):
+        "Return (width, height, x, y)"
+        geom = self.top.wm_geometry()
+        m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
+        return list(map(int, m.groups()))
+
+    def close_event(self, event):
+        self.close()
+        return "break"
+
+    def maybesave(self):
+        if self.io:
+            if not self.get_saved():
+                if self.top.state()!='normal':
+                    self.top.deiconify()
+                self.top.lower()
+                self.top.lift()
+            return self.io.maybesave()
+
+    def close(self):
+        try:
+            reply = self.maybesave()
+            if str(reply) != "cancel":
+                self._close()
+            return reply
+        except AttributeError:  # bpo-35379: close called twice
+            pass
+
+    def _close(self):
+        if self.io.filename:
+            self.update_recent_files_list(new_file=self.io.filename)
+        window.unregister_callback(self.postwindowsmenu)
+        self.unload_extensions()
+        self.io.close()
+        self.io = None
+        self.undo = None
+        if self.color:
+            self.color.close()
+            self.color = None
+        self.text = None
+        self.tkinter_vars = None
+        self.per.close()
+        self.per = None
+        self.top.destroy()
+        if self.close_hook:
+            # unless override: unregister from flist, terminate if last window
+            self.close_hook()
+
+    def load_extensions(self):
+        self.extensions = {}
+        self.load_standard_extensions()
+
+    def unload_extensions(self):
+        for ins in list(self.extensions.values()):
+            if hasattr(ins, "close"):
+                ins.close()
+        self.extensions = {}
+
+    def load_standard_extensions(self):
+        for name in self.get_standard_extension_names():
+            try:
+                self.load_extension(name)
+            except:
+                print("Failed to load extension", repr(name))
+                traceback.print_exc()
+
+    def get_standard_extension_names(self):
+        return idleConf.GetExtensions(editor_only=True)
+
+    extfiles = {  # Map built-in config-extension section names to file names.
+        'ZzDummy': 'zzdummy',
+        }
+
+    def load_extension(self, name):
+        fname = self.extfiles.get(name, name)
+        try:
+            try:
+                mod = importlib.import_module('.' + fname, package=__package__)
+            except (ImportError, TypeError):
+                mod = importlib.import_module(fname)
+        except ImportError:
+            print("\nFailed to import extension: ", name)
+            raise
+        cls = getattr(mod, name)
+        keydefs = idleConf.GetExtensionBindings(name)
+        if hasattr(cls, "menudefs"):
+            self.fill_menus(cls.menudefs, keydefs)
+        ins = cls(self)
+        self.extensions[name] = ins
+        if keydefs:
+            self.apply_bindings(keydefs)
+            for vevent in keydefs:
+                methodname = vevent.replace("-", "_")
+                while methodname[:1] == '<':
+                    methodname = methodname[1:]
+                while methodname[-1:] == '>':
+                    methodname = methodname[:-1]
+                methodname = methodname + "_event"
+                if hasattr(ins, methodname):
+                    self.text.bind(vevent, getattr(ins, methodname))
+
+    def apply_bindings(self, keydefs=None):
+        if keydefs is None:
+            keydefs = self.mainmenu.default_keydefs
+        text = self.text
+        text.keydefs = keydefs
+        for event, keylist in keydefs.items():
+            if keylist:
+                text.event_add(event, *keylist)
+
+    def fill_menus(self, menudefs=None, keydefs=None):
+        """Add appropriate entries to the menus and submenus
+
+        Menus that are absent or None in self.menudict are ignored.
+        """
+        if menudefs is None:
+            menudefs = self.mainmenu.menudefs
+        if keydefs is None:
+            keydefs = self.mainmenu.default_keydefs
+        menudict = self.menudict
+        text = self.text
+        for mname, entrylist in menudefs:
+            menu = menudict.get(mname)
+            if not menu:
+                continue
+            for entry in entrylist:
+                if not entry:
+                    menu.add_separator()
+                else:
+                    label, eventname = entry
+                    checkbutton = (label[:1] == '!')
+                    if checkbutton:
+                        label = label[1:]
+                    underline, label = prepstr(label)
+                    accelerator = get_accelerator(keydefs, eventname)
+                    def command(text=text, eventname=eventname):
+                        text.event_generate(eventname)
+                    if checkbutton:
+                        var = self.get_var_obj(eventname, BooleanVar)
+                        menu.add_checkbutton(label=label, underline=underline,
+                            command=command, accelerator=accelerator,
+                            variable=var)
+                    else:
+                        menu.add_command(label=label, underline=underline,
+                                         command=command,
+                                         accelerator=accelerator)
+
+    def getvar(self, name):
+        var = self.get_var_obj(name)
+        if var:
+            value = var.get()
+            return value
+        else:
+            raise NameError(name)
+
+    def setvar(self, name, value, vartype=None):
+        var = self.get_var_obj(name, vartype)
+        if var:
+            var.set(value)
+        else:
+            raise NameError(name)
+
+    def get_var_obj(self, name, vartype=None):
+        var = self.tkinter_vars.get(name)
+        if not var and vartype:
+            # create a Tkinter variable object with self.text as master:
+            self.tkinter_vars[name] = var = vartype(self.text)
+        return var
+
+    # Tk implementations of "virtual text methods" -- each platform
+    # reusing IDLE's support code needs to define these for its GUI's
+    # flavor of widget.
+
+    # Is character at text_index in a Python string?  Return 0 for
+    # "guaranteed no", true for anything else.  This info is expensive
+    # to compute ab initio, but is probably already known by the
+    # platform's colorizer.
+
+    def is_char_in_string(self, text_index):
+        if self.color:
+            # Return true iff colorizer hasn't (re)gotten this far
+            # yet, or the character is tagged as being in a string
+            return self.text.tag_prevrange("TODO", text_index) or \
+                   "STRING" in self.text.tag_names(text_index)
+        else:
+            # The colorizer is missing: assume the worst
+            return 1
+
+    # If a selection is defined in the text widget, return (start,
+    # end) as Tkinter text indices, otherwise return (None, None)
+    def get_selection_indices(self):
+        try:
+            first = self.text.index("sel.first")
+            last = self.text.index("sel.last")
+            return first, last
+        except TclError:
+            return None, None
+
+    # Return the text widget's current view of what a tab stop means
+    # (equivalent width in spaces).
+
+    def get_tk_tabwidth(self):
+        current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
+        return int(current)
+
+    # Set the text widget's current view of what a tab stop means.
+
+    def set_tk_tabwidth(self, newtabwidth):
+        text = self.text
+        if self.get_tk_tabwidth() != newtabwidth:
+            # Set text widget tab width
+            pixels = text.tk.call("font", "measure", text["font"],
+                                  "-displayof", text.master,
+                                  "n" * newtabwidth)
+            text.configure(tabs=pixels)
+
+### begin autoindent code ###  (configuration was moved to beginning of class)
+
+    def set_indentation_params(self, is_py_src, guess=True):
+        if is_py_src and guess:
+            i = self.guess_indent()
+            if 2 <= i <= 8:
+                self.indentwidth = i
+            if self.indentwidth != self.tabwidth:
+                self.usetabs = False
+        self.set_tk_tabwidth(self.tabwidth)
+
+    def smart_backspace_event(self, event):
+        text = self.text
+        first, last = self.get_selection_indices()
+        if first and last:
+            text.delete(first, last)
+            text.mark_set("insert", first)
+            return "break"
+        # Delete whitespace left, until hitting a real char or closest
+        # preceding virtual tab stop.
+        chars = text.get("insert linestart", "insert")
+        if chars == '':
+            if text.compare("insert", ">", "1.0"):
+                # easy: delete preceding newline
+                text.delete("insert-1c")
+            else:
+                text.bell()     # at start of buffer
+            return "break"
+        if  chars[-1] not in " \t":
+            # easy: delete preceding real char
+            text.delete("insert-1c")
+            return "break"
+        # Ick.  It may require *inserting* spaces if we back up over a
+        # tab character!  This is written to be clear, not fast.
+        tabwidth = self.tabwidth
+        have = len(chars.expandtabs(tabwidth))
+        assert have > 0
+        want = ((have - 1) // self.indentwidth) * self.indentwidth
+        # Debug prompt is multilined....
+        ncharsdeleted = 0
+        while 1:
+            if chars == self.prompt_last_line:  # '' unless PyShell
+                break
+            chars = chars[:-1]
+            ncharsdeleted = ncharsdeleted + 1
+            have = len(chars.expandtabs(tabwidth))
+            if have <= want or chars[-1] not in " \t":
+                break
+        text.undo_block_start()
+        text.delete("insert-%dc" % ncharsdeleted, "insert")
+        if have < want:
+            text.insert("insert", ' ' * (want - have))
+        text.undo_block_stop()
+        return "break"
+
+    def smart_indent_event(self, event):
+        # if intraline selection:
+        #     delete it
+        # elif multiline selection:
+        #     do indent-region
+        # else:
+        #     indent one level
+        text = self.text
+        first, last = self.get_selection_indices()
+        text.undo_block_start()
+        try:
+            if first and last:
+                if index2line(first) != index2line(last):
+                    return self.fregion.indent_region_event(event)
+                text.delete(first, last)
+                text.mark_set("insert", first)
+            prefix = text.get("insert linestart", "insert")
+            raw, effective = get_line_indent(prefix, self.tabwidth)
+            if raw == len(prefix):
+                # only whitespace to the left
+                self.reindent_to(effective + self.indentwidth)
+            else:
+                # tab to the next 'stop' within or to right of line's text:
+                if self.usetabs:
+                    pad = '\t'
+                else:
+                    effective = len(prefix.expandtabs(self.tabwidth))
+                    n = self.indentwidth
+                    pad = ' ' * (n - effective % n)
+                text.insert("insert", pad)
+            text.see("insert")
+            return "break"
+        finally:
+            text.undo_block_stop()
+
+    def newline_and_indent_event(self, event):
+        """Insert a newline and indentation after Enter keypress event.
+
+        Properly position the cursor on the new line based on information
+        from the current line.  This takes into account if the current line
+        is a shell prompt, is empty, has selected text, contains a block
+        opener, contains a block closer, is a continuation line, or
+        is inside a string.
+        """
+        text = self.text
+        first, last = self.get_selection_indices()
+        text.undo_block_start()
+        try:  # Close undo block and expose new line in finally clause.
+            if first and last:
+                text.delete(first, last)
+                text.mark_set("insert", first)
+            line = text.get("insert linestart", "insert")
+
+            # Count leading whitespace for indent size.
+            i, n = 0, len(line)
+            while i < n and line[i] in " \t":
+                i += 1
+            if i == n:
+                # The cursor is in or at leading indentation in a continuation
+                # line; just inject an empty line at the start.
+                text.insert("insert linestart", '\n')
+                return "break"
+            indent = line[:i]
+
+            # Strip whitespace before insert point unless it's in the prompt.
+            i = 0
+            while line and line[-1] in " \t" and line != self.prompt_last_line:
+                line = line[:-1]
+                i += 1
+            if i:
+                text.delete("insert - %d chars" % i, "insert")
+
+            # Strip whitespace after insert point.
+            while text.get("insert") in " \t":
+                text.delete("insert")
+
+            # Insert new line.
+            text.insert("insert", '\n')
+
+            # Adjust indentation for continuations and block open/close.
+            # First need to find the last statement.
+            lno = index2line(text.index('insert'))
+            y = pyparse.Parser(self.indentwidth, self.tabwidth)
+            if not self.prompt_last_line:
+                for context in self.num_context_lines:
+                    startat = max(lno - context, 1)
+                    startatindex = repr(startat) + ".0"
+                    rawtext = text.get(startatindex, "insert")
+                    y.set_code(rawtext)
+                    bod = y.find_good_parse_start(
+                            self._build_char_in_string_func(startatindex))
+                    if bod is not None or startat == 1:
+                        break
+                y.set_lo(bod or 0)
+            else:
+                r = text.tag_prevrange("console", "insert")
+                if r:
+                    startatindex = r[1]
+                else:
+                    startatindex = "1.0"
+                rawtext = text.get(startatindex, "insert")
+                y.set_code(rawtext)
+                y.set_lo(0)
+
+            c = y.get_continuation_type()
+            if c != pyparse.C_NONE:
+                # The current statement hasn't ended yet.
+                if c == pyparse.C_STRING_FIRST_LINE:
+                    # After the first line of a string do not indent at all.
+                    pass
+                elif c == pyparse.C_STRING_NEXT_LINES:
+                    # Inside a string which started before this line;
+                    # just mimic the current indent.
+                    text.insert("insert", indent)
+                elif c == pyparse.C_BRACKET:
+                    # Line up with the first (if any) element of the
+                    # last open bracket structure; else indent one
+                    # level beyond the indent of the line with the
+                    # last open bracket.
+                    self.reindent_to(y.compute_bracket_indent())
+                elif c == pyparse.C_BACKSLASH:
+                    # If more than one line in this statement already, just
+                    # mimic the current indent; else if initial line
+                    # has a start on an assignment stmt, indent to
+                    # beyond leftmost =; else to beyond first chunk of
+                    # non-whitespace on initial line.
+                    if y.get_num_lines_in_stmt() > 1:
+                        text.insert("insert", indent)
+                    else:
+                        self.reindent_to(y.compute_backslash_indent())
+                else:
+                    assert 0, "bogus continuation type %r" % (c,)
+                return "break"
+
+            # This line starts a brand new statement; indent relative to
+            # indentation of initial line of closest preceding
+            # interesting statement.
+            indent = y.get_base_indent_string()
+            text.insert("insert", indent)
+            if y.is_block_opener():
+                self.smart_indent_event(event)
+            elif indent and y.is_block_closer():
+                self.smart_backspace_event(event)
+            return "break"
+        finally:
+            text.see("insert")
+            text.undo_block_stop()
+
+    # Our editwin provides an is_char_in_string function that works
+    # with a Tk text index, but PyParse only knows about offsets into
+    # a string. This builds a function for PyParse that accepts an
+    # offset.
+
+    def _build_char_in_string_func(self, startindex):
+        def inner(offset, _startindex=startindex,
+                  _icis=self.is_char_in_string):
+            return _icis(_startindex + "+%dc" % offset)
+        return inner
+
+    # XXX this isn't bound to anything -- see tabwidth comments
+##     def change_tabwidth_event(self, event):
+##         new = self._asktabwidth()
+##         if new != self.tabwidth:
+##             self.tabwidth = new
+##             self.set_indentation_params(0, guess=0)
+##         return "break"
+
+    # Make string that displays as n leading blanks.
+
+    def _make_blanks(self, n):
+        if self.usetabs:
+            ntabs, nspaces = divmod(n, self.tabwidth)
+            return '\t' * ntabs + ' ' * nspaces
+        else:
+            return ' ' * n
+
+    # Delete from beginning of line to insert point, then reinsert
+    # column logical (meaning use tabs if appropriate) spaces.
+
+    def reindent_to(self, column):
+        text = self.text
+        text.undo_block_start()
+        if text.compare("insert linestart", "!=", "insert"):
+            text.delete("insert linestart", "insert")
+        if column:
+            text.insert("insert", self._make_blanks(column))
+        text.undo_block_stop()
+
+    # Guess indentwidth from text content.
+    # Return guessed indentwidth.  This should not be believed unless
+    # it's in a reasonable range (e.g., it will be 0 if no indented
+    # blocks are found).
+
+    def guess_indent(self):
+        opener, indented = IndentSearcher(self.text, self.tabwidth).run()
+        if opener and indented:
+            raw, indentsmall = get_line_indent(opener, self.tabwidth)
+            raw, indentlarge = get_line_indent(indented, self.tabwidth)
+        else:
+            indentsmall = indentlarge = 0
+        return indentlarge - indentsmall
+
+    def toggle_line_numbers_event(self, event=None):
+        if self.line_numbers is None:
+            return
+
+        if self.line_numbers.is_shown:
+            self.line_numbers.hide_sidebar()
+            menu_label = "Show"
+        else:
+            self.line_numbers.show_sidebar()
+            menu_label = "Hide"
+        self.update_menu_label(menu='options', index='*ine*umbers',
+                               label=f'{menu_label} Line Numbers')
+
+# "line.col" -> line, as an int
+def index2line(index):
+    return int(float(index))
+
+
+_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 IndentSearcher:
+
+    # .run() chews over the Text widget, looking for a block opener
+    # and the stmt following it.  Returns a pair,
+    #     (line containing block opener, line containing stmt)
+    # Either or both may be None.
+
+    def __init__(self, text, tabwidth):
+        self.text = text
+        self.tabwidth = tabwidth
+        self.i = self.finished = 0
+        self.blkopenline = self.indentedline = None
+
+    def readline(self):
+        if self.finished:
+            return ""
+        i = self.i = self.i + 1
+        mark = repr(i) + ".0"
+        if self.text.compare(mark, ">=", "end"):
+            return ""
+        return self.text.get(mark, mark + " lineend+1c")
+
+    def tokeneater(self, type, token, start, end, line,
+                   INDENT=tokenize.INDENT,
+                   NAME=tokenize.NAME,
+                   OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
+        if self.finished:
+            pass
+        elif type == NAME and token in OPENERS:
+            self.blkopenline = line
+        elif type == INDENT and self.blkopenline:
+            self.indentedline = line
+            self.finished = 1
+
+    def run(self):
+        save_tabsize = tokenize.tabsize
+        tokenize.tabsize = self.tabwidth
+        try:
+            try:
+                tokens = tokenize.generate_tokens(self.readline)
+                for token in tokens:
+                    self.tokeneater(*token)
+            except (tokenize.TokenError, SyntaxError):
+                # since we cut off the tokenizer early, we can trigger
+                # spurious errors
+                pass
+        finally:
+            tokenize.tabsize = save_tabsize
+        return self.blkopenline, self.indentedline
+
+### end autoindent code ###
+
+def prepstr(s):
+    # Helper to extract the underscore from a string, e.g.
+    # prepstr("Co_py") returns (2, "Copy").
+    i = s.find('_')
+    if i >= 0:
+        s = s[:i] + s[i+1:]
+    return i, s
+
+
+keynames = {
+ 'bracketleft': '[',
+ 'bracketright': ']',
+ 'slash': '/',
+}
+
+def get_accelerator(keydefs, eventname):
+    keylist = keydefs.get(eventname)
+    # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
+    # if not keylist:
+    if (not keylist) or (macosx.isCocoaTk() and eventname in {
+                            "<<open-module>>",
+                            "<<goto-line>>",
+                            "<<change-indentwidth>>"}):
+        return ""
+    s = keylist[0]
+    s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
+    s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
+    s = re.sub("Key-", "", s)
+    s = re.sub("Cancel","Ctrl-Break",s)   # dscherer@cmu.edu
+    s = re.sub("Control-", "Ctrl-", s)
+    s = re.sub("-", "+", s)
+    s = re.sub("><", " ", s)
+    s = re.sub("<", "", s)
+    s = re.sub(">", "", s)
+    return s
+
+
+def fixwordbreaks(root):
+    # On Windows, tcl/tk breaks 'words' only on spaces, as in Command Prompt.
+    # We want Motif style everywhere. See #21474, msg218992 and followup.
+    tk = root.tk
+    tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
+    tk.call('set', 'tcl_wordchars', r'\w')
+    tk.call('set', 'tcl_nonwordchars', r'\W')
+
+
+def _editor_window(parent):  # htest #
+    # error if close master window first - timer event, after script
+    root = parent
+    fixwordbreaks(root)
+    if sys.argv[1:]:
+        filename = sys.argv[1]
+    else:
+        filename = None
+    macosx.setupApp(root, None)
+    edit = EditorWindow(root=root, filename=filename)
+    text = edit.text
+    text['height'] = 10
+    for i in range(20):
+        text.insert('insert', '  '*i + str(i) + '\n')
+    # text.bind("<<close-all-windows>>", edit.close_event)
+    # Does not stop error, neither does following
+    # edit.text.bind("<<close-window>>", edit.close_event)
+
+if __name__ == '__main__':
+    from unittest import main
+    main('idlelib.idle_test.test_editor', verbosity=2, exit=False)
+
+    from idlelib.idle_test.htest import run
+    run(_editor_window)
diff --git a/rootfs/usr/lib/python3.8/idlelib/extend.txt b/rootfs/usr/lib/python3.8/idlelib/extend.txt
new file mode 100644
index 0000000..b482f76
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/extend.txt
@@ -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 cofig-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.
diff --git a/rootfs/usr/lib/python3.8/idlelib/filelist.py b/rootfs/usr/lib/python3.8/idlelib/filelist.py
new file mode 100644
index 0000000..254f5ca
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/filelist.py
@@ -0,0 +1,131 @@
+"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",
+                "%r is a directory." % (filename,),
+                master=self.root)
+            return None
+        key = os.path.normcase(filename)
+        if key in self.dict:
+            edit = self.dict[key]
+            edit.top.wakeup()
+            return edit
+        if action:
+            # Don't create window, perform 'action', e.g. open in same window
+            return action(filename)
+        else:
+            edit = self.EditorWindow(self, filename, key)
+            if edit.good_load:
+                return edit
+            else:
+                edit._close()
+                return None
+
+    def gotofileline(self, filename, lineno=None):
+        edit = self.open(filename)
+        if edit is not None and lineno is not None:
+            edit.gotoline(lineno)
+
+    def new(self, filename=None):
+        return self.EditorWindow(self, filename)
+
+    def close_all_callback(self, *args, **kwds):
+        for edit in list(self.inversedict):
+            reply = edit.close()
+            if reply == "cancel":
+                break
+        return "break"
+
+    def unregister_maybe_terminate(self, edit):
+        try:
+            key = self.inversedict[edit]
+        except KeyError:
+            print("Don't know this EditorWindow object.  (close)")
+            return
+        if key:
+            del self.dict[key]
+        del self.inversedict[edit]
+        if not self.inversedict:
+            self.root.quit()
+
+    def filename_changed_edit(self, edit):
+        edit.saved_change_hook()
+        try:
+            key = self.inversedict[edit]
+        except KeyError:
+            print("Don't know this EditorWindow object.  (rename)")
+            return
+        filename = edit.io.filename
+        if not filename:
+            if key:
+                del self.dict[key]
+            self.inversedict[edit] = None
+            return
+        filename = self.canonize(filename)
+        newkey = os.path.normcase(filename)
+        if newkey == key:
+            return
+        if newkey in self.dict:
+            conflict = self.dict[newkey]
+            self.inversedict[conflict] = None
+            messagebox.showerror(
+                "Name Conflict",
+                "You now have multiple edit windows open for %r" % (filename,),
+                master=self.root)
+        self.dict[newkey] = edit
+        self.inversedict[edit] = newkey
+        if key:
+            try:
+                del self.dict[key]
+            except KeyError:
+                pass
+
+    def canonize(self, filename):
+        if not os.path.isabs(filename):
+            try:
+                pwd = os.getcwd()
+            except OSError:
+                pass
+            else:
+                filename = os.path.join(pwd, filename)
+        return os.path.normpath(filename)
+
+
+def _test():  # TODO check and convert to htest
+    from tkinter import Tk
+    from idlelib.editor import fixwordbreaks
+    from idlelib.run import fix_scaling
+    root = Tk()
+    fix_scaling(root)
+    fixwordbreaks(root)
+    root.withdraw()
+    flist = FileList(root)
+    flist.new()
+    if flist.inversedict:
+        root.mainloop()
+
+if __name__ == '__main__':
+    from unittest import main
+    main('idlelib.idle_test.test_filelist', verbosity=2)
+
+#    _test()
diff --git a/rootfs/usr/lib/python3.8/idlelib/format.py b/rootfs/usr/lib/python3.8/idlelib/format.py
new file mode 100644
index 0000000..4b57a18
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/format.py
@@ -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)
diff --git a/rootfs/usr/lib/python3.8/idlelib/grep.py b/rootfs/usr/lib/python3.8/idlelib/grep.py
new file mode 100644
index 0000000..1251359
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/grep.py
@@ -0,0 +1,221 @@
+"""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, END
+    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()
+
+    def show_grep_dialog():
+        text.tag_add(SEL, "1.0", END)
+        grep(text, flist=flist)
+        text.tag_remove(SEL, "1.0", 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)
diff --git a/rootfs/usr/lib/python3.8/idlelib/help.html b/rootfs/usr/lib/python3.8/idlelib/help.html
new file mode 100644
index 0000000..e80384b
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/help.html
@@ -0,0 +1,1014 @@
+
+<!DOCTYPE html>
+
+<html>
+  <head>
+    <meta charset="utf-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>IDLE &#8212; Python 3.10.0a6 documentation</title>
+    <link rel="stylesheet" href="../_static/pydoctheme.css" type="text/css" />
+    <link rel="stylesheet" href="../_static/pygments.css" type="text/css" />
+
+    <script id="documentation_options" data-url_root="../" src="../_static/documentation_options.js"></script>
+    <script src="../_static/jquery.js"></script>
+    <script src="../_static/underscore.js"></script>
+    <script src="../_static/doctools.js"></script>
+    <script src="../_static/language_data.js"></script>
+
+    <script src="../_static/sidebar.js"></script>
+
+    <link rel="search" type="application/opensearchdescription+xml"
+          title="Search within Python 3.10.0a6 documentation"
+          href="../_static/opensearch.xml"/>
+    <link rel="author" title="About these documents" href="../about.html" />
+    <link rel="index" title="Index" href="../genindex.html" />
+    <link rel="search" title="Search" href="../search.html" />
+    <link rel="copyright" title="Copyright" href="../copyright.html" />
+    <link rel="next" title="Other Graphical User Interface Packages" href="othergui.html" />
+    <link rel="prev" title="tkinter.tix — Extension widgets for Tk" href="tkinter.tix.html" />
+    <link rel="canonical" href="https://docs.python.org/3/library/idle.html" />
+
+
+
+
+
+    <style>
+      @media only screen {
+        table.full-width-table {
+            width: 100%;
+        }
+      }
+    </style>
+
+    <link rel="shortcut icon" type="image/png" href="../_static/py.png" />
+
+    <script type="text/javascript" src="../_static/copybutton.js"></script>
+
+
+
+
+  </head><body>
+
+    <div class="related" role="navigation" aria-label="related navigation">
+      <h3>Navigation</h3>
+      <ul>
+        <li class="right" style="margin-right: 10px">
+          <a href="../genindex.html" title="General Index"
+             accesskey="I">index</a></li>
+        <li class="right" >
+          <a href="../py-modindex.html" title="Python Module Index"
+             >modules</a> |</li>
+        <li class="right" >
+          <a href="othergui.html" title="Other Graphical User Interface Packages"
+             accesskey="N">next</a> |</li>
+        <li class="right" >
+          <a href="tkinter.tix.html" title="tkinter.tix — Extension widgets for Tk"
+             accesskey="P">previous</a> |</li>
+
+    <li><img src="../_static/py.png" alt=""
+             style="vertical-align: middle; margin-top: -1px"/></li>
+    <li><a href="https://www.python.org/">Python</a> &#187;</li>
+
+
+    <li id="cpython-language-and-version">
+      <a href="../index.html">3.10.0a6 Documentation</a> &#187;
+    </li>
+
+          <li class="nav-item nav-item-1"><a href="index.html" >The Python Standard Library</a> &#187;</li>
+          <li class="nav-item nav-item-2"><a href="tk.html" accesskey="U">Graphical User Interfaces with Tk</a> &#187;</li>
+        <li class="nav-item nav-item-this"><a href="">IDLE</a></li>
+    <li class="right">
+
+
+    <div class="inline-search" style="display: none" role="search">
+        <form class="inline-search" action="../search.html" method="get">
+          <input placeholder="Quick search" type="text" name="q" />
+          <input type="submit" value="Go" />
+          <input type="hidden" name="check_keywords" value="yes" />
+          <input type="hidden" name="area" value="default" />
+        </form>
+    </div>
+    <script type="text/javascript">$('.inline-search').show(0);</script>
+         |
+    </li>
+
+      </ul>
+    </div>
+
+    <div class="document">
+      <div class="documentwrapper">
+        <div class="bodywrapper">
+          <div class="body" role="main">
+
+  <div class="section" id="idle">
+<span id="id1"></span><h1>IDLE<a class="headerlink" href="#idle" title="Permalink to this headline">¶</a></h1>
+<p><strong>Source code:</strong> <a class="reference external" href="https://github.com/python/cpython/tree/master/Lib/idlelib/">Lib/idlelib/</a></p>
+<hr class="docutils" id="index-0" />
+<p>IDLE is Python’s Integrated Development and Learning Environment.</p>
+<p>IDLE has the following features:</p>
+<ul class="simple">
+<li><p>coded in 100% pure Python, using the <a class="reference internal" href="tkinter.html#module-tkinter" title="tkinter: Interface to Tcl/Tk for graphical user interfaces"><code class="xref py py-mod docutils literal notranslate"><span class="pre">tkinter</span></code></a> GUI toolkit</p></li>
+<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>
+<div class="section" id="menus">
+<h2>Menus<a class="headerlink" href="#menus" title="Permalink to this headline">¶</a></h2>
+<p>IDLE has two main window types, the Shell window and the Editor window.  It is
+possible to have multiple editor windows simultaneously.  On Windows and
+Linux, each has its own top menu.  Each menu documented below indicates
+which window type it is associated with.</p>
+<p>Output windows, such as used for Edit =&gt; Find in Files, are a subtype of editor
+window.  They currently have the same top menu but a different
+default title and context menu.</p>
+<p>On macOS, there is one application menu.  It dynamically changes according
+to the window currently selected.  It has an IDLE menu, and some entries
+described below are moved around to conform to Apple guidelines.</p>
+<div class="section" id="file-menu-shell-and-editor">
+<h3>File menu (Shell and Editor)<a class="headerlink" href="#file-menu-shell-and-editor" title="Permalink to this headline">¶</a></h3>
+<dl class="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>Recent Files</dt><dd><p>Open a list of recent files.  Click one to open it.</p>
+</dd>
+<dt>Open Module…</dt><dd><p>Open an existing module (searches sys.path).</p>
+</dd>
+</dl>
+<dl class="simple" id="index-1">
+<dt>Class 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.</p>
+</dd>
+<dt>Save Copy As…</dt><dd><p>Save the current window to different file without changing the associated
+file.</p>
+</dd>
+<dt>Print Window</dt><dd><p>Print the current window to the default printer.</p>
+</dd>
+<dt>Close</dt><dd><p>Close the current window (ask to save if unsaved).</p>
+</dd>
+<dt>Exit</dt><dd><p>Close all windows and quit IDLE (ask to save unsaved windows).</p>
+</dd>
+</dl>
+</div>
+<div class="section" id="edit-menu-shell-and-editor">
+<h3>Edit menu (Shell and Editor)<a class="headerlink" href="#edit-menu-shell-and-editor" title="Permalink to this headline">¶</a></h3>
+<dl class="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>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>Select All</dt><dd><p>Select the entire contents of the current window.</p>
+</dd>
+<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>
+</div>
+<div class="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="Permalink to this headline">¶</a></h3>
+<dl class="simple">
+<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>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>Strip trailing whitespace</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>
+</div>
+<div class="section" id="run-menu-editor-window-only">
+<span id="index-2"></span><h3>Run menu (Editor window only)<a class="headerlink" href="#run-menu-editor-window-only" title="Permalink to this headline">¶</a></h3>
+<dl class="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>
+</div>
+<div class="section" id="shell-menu-shell-window-only">
+<h3>Shell menu (Shell window only)<a class="headerlink" href="#shell-menu-shell-window-only" title="Permalink to this headline">¶</a></h3>
+<dl class="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>
+</div>
+<div class="section" id="debug-menu-shell-window-only">
+<h3>Debug menu (Shell window only)<a class="headerlink" href="#debug-menu-shell-window-only" title="Permalink to this headline">¶</a></h3>
+<dl class="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>
+</div>
+<div class="section" id="options-menu-shell-and-editor">
+<h3>Options menu (Shell and Editor)<a class="headerlink" href="#options-menu-shell-and-editor" title="Permalink to this headline">¶</a></h3>
+<dl class="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>
+</div>
+<div class="section" id="window-menu-shell-and-editor">
+<h3>Window menu (Shell and Editor)<a class="headerlink" href="#window-menu-shell-and-editor" title="Permalink to this headline">¶</a></h3>
+<p>Lists the names of all open windows; select one to bring it to the foreground
+(deiconifying it if necessary).</p>
+</div>
+<div class="section" id="help-menu-shell-and-editor">
+<h3>Help menu (Shell and Editor)<a class="headerlink" href="#help-menu-shell-and-editor" title="Permalink to this headline">¶</a></h3>
+<dl class="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>
+</div>
+<div class="section" id="context-menus">
+<span id="index-4"></span><h3>Context Menus<a class="headerlink" href="#context-menus" title="Permalink to this headline">¶</a></h3>
+<p>Open a context menu by right-clicking in a window (Control-click on macOS).
+Context menus have the standard clipboard functions also on the Edit menu.</p>
+<dl class="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>
+</div>
+</div>
+<div class="section" id="editing-and-navigation">
+<span id="id2"></span><h2>Editing and navigation<a class="headerlink" href="#editing-and-navigation" title="Permalink to this headline">¶</a></h2>
+<div class="section" id="editor-windows">
+<h3>Editor windows<a class="headerlink" href="#editor-windows" title="Permalink to this headline">¶</a></h3>
+<p>IDLE may open editor windows when it starts, depending on settings
+and how you start IDLE.  Thereafter, use the File menu.  There can be only
+one open editor window for a given file.</p>
+<p>The title bar contains the name of the file, the full path, and the version
+of Python and IDLE running the window.  The status bar contains the line
+number (‘Ln’) and column number (‘Col’).  Line numbers start with 1;
+column numbers with 0.</p>
+<p>IDLE assumes that files with a known .py* extension contain Python code
+and that other files do not.  Run Python code with the Run menu.</p>
+</div>
+<div class="section" id="key-bindings">
+<h3>Key bindings<a class="headerlink" href="#key-bindings" title="Permalink to this headline">¶</a></h3>
+<p>In this section, ‘C’ refers to the <kbd class="kbd docutils literal notranslate">Control</kbd> key on Windows and Unix and
+the <kbd class="kbd docutils literal notranslate">Command</kbd> key on macOS.</p>
+<ul>
+<li><p><kbd class="kbd docutils literal notranslate">Backspace</kbd> deletes to the left; <kbd class="kbd docutils literal notranslate">Del</kbd> deletes to the right</p></li>
+<li><p><kbd class="kbd docutils literal notranslate"><kbd class="kbd docutils literal notranslate">C</kbd>-<kbd class="kbd docutils literal notranslate">Backspace</kbd></kbd> delete word left; <kbd class="kbd docutils literal notranslate"><kbd class="kbd docutils literal notranslate">C</kbd>-<kbd class="kbd docutils literal notranslate">Del</kbd></kbd> delete word to the right</p></li>
+<li><p>Arrow keys and <kbd class="kbd docutils literal notranslate"><kbd class="kbd docutils literal notranslate">Page</kbd> <kbd class="kbd docutils literal notranslate">Up</kbd></kbd>/<kbd class="kbd docutils literal notranslate"><kbd class="kbd docutils literal notranslate">Page</kbd> <kbd class="kbd docutils literal notranslate">Down</kbd></kbd> to move around</p></li>
+<li><p><kbd class="kbd docutils literal notranslate"><kbd class="kbd docutils literal notranslate">C</kbd>-<kbd class="kbd docutils literal notranslate">LeftArrow</kbd></kbd> and <kbd class="kbd docutils literal notranslate"><kbd class="kbd docutils literal notranslate">C</kbd>-<kbd class="kbd docutils literal notranslate">RightArrow</kbd></kbd> moves by words</p></li>
+<li><p><kbd class="kbd docutils literal notranslate">Home</kbd>/<kbd class="kbd docutils literal notranslate">End</kbd> go to begin/end of line</p></li>
+<li><p><kbd class="kbd docutils literal notranslate"><kbd class="kbd docutils literal notranslate">C</kbd>-<kbd class="kbd docutils literal notranslate">Home</kbd></kbd>/<kbd class="kbd docutils literal notranslate"><kbd class="kbd docutils literal notranslate">C</kbd>-<kbd class="kbd docutils literal notranslate">End</kbd></kbd> go to begin/end of file</p></li>
+<li><p>Some useful Emacs bindings are inherited from Tcl/Tk:</p>
+<blockquote>
+<div><ul class="simple">
+<li><p><kbd class="kbd docutils literal notranslate"><kbd class="kbd docutils literal notranslate">C</kbd>-<kbd class="kbd docutils literal notranslate">a</kbd></kbd> beginning of line</p></li>
+<li><p><kbd class="kbd docutils literal notranslate"><kbd class="kbd docutils literal notranslate">C</kbd>-<kbd class="kbd docutils literal notranslate">e</kbd></kbd> end of line</p></li>
+<li><p><kbd class="kbd docutils literal notranslate"><kbd class="kbd docutils literal notranslate">C</kbd>-<kbd class="kbd docutils literal notranslate">k</kbd></kbd> kill line (but doesn’t put it in clipboard)</p></li>
+<li><p><kbd class="kbd docutils literal notranslate"><kbd class="kbd docutils literal notranslate">C</kbd>-<kbd class="kbd docutils literal notranslate">l</kbd></kbd> center window around the insertion point</p></li>
+<li><p><kbd class="kbd docutils literal notranslate"><kbd class="kbd docutils literal notranslate">C</kbd>-<kbd class="kbd docutils literal notranslate">b</kbd></kbd> go backward one character without deleting (usually you can
+also use the cursor key for this)</p></li>
+<li><p><kbd class="kbd docutils literal notranslate"><kbd class="kbd docutils literal notranslate">C</kbd>-<kbd class="kbd docutils literal notranslate">f</kbd></kbd> go forward one character without deleting (usually you can
+also use the cursor key for this)</p></li>
+<li><p><kbd class="kbd docutils literal notranslate"><kbd class="kbd docutils literal notranslate">C</kbd>-<kbd class="kbd docutils literal notranslate">p</kbd></kbd> go up one line (usually you can also use the cursor key for
+this)</p></li>
+<li><p><kbd class="kbd docutils literal notranslate"><kbd class="kbd docutils literal notranslate">C</kbd>-<kbd class="kbd docutils literal notranslate">d</kbd></kbd> delete next character</p></li>
+</ul>
+</div></blockquote>
+</li>
+</ul>
+<p>Standard keybindings (like <kbd class="kbd 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 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>
+</div>
+<div class="section" id="automatic-indentation">
+<h3>Automatic indentation<a class="headerlink" href="#automatic-indentation" title="Permalink to this headline">¶</a></h3>
+<p>After a block-opening statement, the next line is indented by 4 spaces (in the
+Python Shell window by one tab).  After certain keywords (break, return etc.)
+the next line is dedented.  In leading indentation, <kbd class="kbd docutils literal notranslate">Backspace</kbd> deletes up
+to 4 spaces if they are there. <kbd class="kbd docutils literal notranslate">Tab</kbd> inserts spaces (in the Python
+Shell window one tab), number depends on Indent width. Currently, tabs
+are restricted to four spaces due to Tcl/Tk limitations.</p>
+<p>See also the indent/dedent region commands on the
+<a class="reference internal" href="#format-menu"><span class="std std-ref">Format menu</span></a>.</p>
+</div>
+<div class="section" id="completions">
+<span id="id3"></span><h3>Completions<a class="headerlink" href="#completions" title="Permalink to this headline">¶</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 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 intially 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>
+</div>
+<div class="section" id="calltips">
+<span id="id4"></span><h3>Calltips<a class="headerlink" href="#calltips" title="Permalink to this headline">¶</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>
+</div>
+<div class="section" id="code-context">
+<span id="id5"></span><h3>Code Context<a class="headerlink" href="#code-context" title="Permalink to this headline">¶</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>
+</div>
+<div class="section" id="python-shell-window">
+<h3>Python Shell window<a class="headerlink" href="#python-shell-window" title="Permalink to this headline">¶</a></h3>
+<p>With IDLE’s Shell, one enters, edits, and recalls complete statements.
+Most consoles and terminals only work with a single physical line at a time.</p>
+<p>When one pastes code into Shell, it is not compiled and possibly executed
+until one hits <kbd class="kbd docutils literal notranslate">Return</kbd>.  One may edit pasted code first.
+If one pastes more that one statement into Shell, the result will be a
+<a class="reference internal" href="exceptions.html#SyntaxError" title="SyntaxError"><code class="xref py py-exc docutils literal notranslate"><span class="pre">SyntaxError</span></code></a> when multiple statements are compiled as if they were one.</p>
+<p>The editing features described in previous subsections work when entering
+code interactively.  IDLE’s Shell window also responds to the following keys.</p>
+<ul>
+<li><p><kbd class="kbd docutils literal notranslate"><kbd class="kbd docutils literal notranslate">C</kbd>-<kbd class="kbd docutils literal notranslate">c</kbd></kbd> interrupts executing command</p></li>
+<li><p><kbd class="kbd docutils literal notranslate"><kbd class="kbd docutils literal notranslate">C</kbd>-<kbd class="kbd docutils literal notranslate">d</kbd></kbd> sends end-of-file; closes window if typed at a <code class="docutils literal notranslate"><span class="pre">&gt;&gt;&gt;</span></code> prompt</p></li>
+<li><p><kbd class="kbd docutils literal notranslate"><kbd class="kbd docutils literal notranslate">Alt</kbd>-<kbd class="kbd docutils literal notranslate">/</kbd></kbd> (Expand word) is also useful to reduce typing</p>
+<p>Command history</p>
+<ul class="simple">
+<li><p><kbd class="kbd docutils literal notranslate"><kbd class="kbd docutils literal notranslate">Alt</kbd>-<kbd class="kbd docutils literal notranslate">p</kbd></kbd> retrieves previous command matching what you have typed. On
+macOS use <kbd class="kbd docutils literal notranslate"><kbd class="kbd docutils literal notranslate">C</kbd>-<kbd class="kbd docutils literal notranslate">p</kbd></kbd>.</p></li>
+<li><p><kbd class="kbd docutils literal notranslate"><kbd class="kbd docutils literal notranslate">Alt</kbd>-<kbd class="kbd docutils literal notranslate">n</kbd></kbd> retrieves next. On macOS use <kbd class="kbd docutils literal notranslate"><kbd class="kbd docutils literal notranslate">C</kbd>-<kbd class="kbd docutils literal notranslate">n</kbd></kbd>.</p></li>
+<li><p><kbd class="kbd docutils literal notranslate">Return</kbd> while on any previous command retrieves that command</p></li>
+</ul>
+</li>
+</ul>
+</div>
+<div class="section" id="text-colors">
+<h3>Text colors<a class="headerlink" href="#text-colors" title="Permalink to this headline">¶</a></h3>
+<p>Idle defaults to black on white text, but colors text with special meanings.
+For the shell, these are shell output, shell error, user output, and
+user error.  For Python code, at the shell prompt or in an editor, these are
+keywords, builtin class and function names, names following <code class="docutils literal notranslate"><span class="pre">class</span></code> and
+<code class="docutils literal notranslate"><span class="pre">def</span></code>, strings, and comments. For any text window, these are the cursor (when
+present), found text (when possible), and selected text.</p>
+<p>Text coloring is done in the background, so uncolorized text is occasionally
+visible.  To change the color scheme, use the Configure IDLE dialog
+Highlighting tab.  The marking of debugger breakpoint lines in the editor and
+text in popups and dialogs is not user-configurable.</p>
+</div>
+</div>
+<div class="section" id="startup-and-code-execution">
+<h2>Startup and code execution<a class="headerlink" href="#startup-and-code-execution" title="Permalink to this headline">¶</a></h2>
+<p>Upon startup with the <code class="docutils literal notranslate"><span class="pre">-s</span></code> option, IDLE will execute the file referenced by
+the environment variables <span class="target" id="index-5"></span><code class="xref std std-envvar docutils literal notranslate"><span class="pre">IDLESTARTUP</span></code> or <span class="target" id="index-6"></span><a class="reference internal" href="../using/cmdline.html#envvar-PYTHONSTARTUP"><code class="xref std std-envvar docutils literal notranslate"><span class="pre">PYTHONSTARTUP</span></code></a>.
+IDLE first checks for <code class="docutils literal notranslate"><span class="pre">IDLESTARTUP</span></code>; if <code class="docutils literal notranslate"><span class="pre">IDLESTARTUP</span></code> is present the file
+referenced is run.  If <code class="docutils literal notranslate"><span class="pre">IDLESTARTUP</span></code> is not present, IDLE checks for
+<code class="docutils literal notranslate"><span class="pre">PYTHONSTARTUP</span></code>.  Files referenced by these environment variables are
+convenient places to store functions that are used frequently from the IDLE
+shell, or for executing import statements to import common modules.</p>
+<p>In addition, <code class="docutils literal notranslate"><span class="pre">Tk</span></code> also loads a startup file if it is present.  Note that the
+Tk file is loaded unconditionally.  This additional file is <code class="docutils literal notranslate"><span class="pre">.Idle.py</span></code> and is
+looked for in the user’s home directory.  Statements in this file will be
+executed in the Tk namespace, so this file is not useful for importing
+functions to be used from IDLE’s Python shell.</p>
+<div class="section" id="command-line-usage">
+<h3>Command line usage<a class="headerlink" href="#command-line-usage" title="Permalink to this headline">¶</a></h3>
+<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>idle.py [-c command] [-d] [-e] [-h] [-i] [-r file] [-s] [-t title] [-] [arg] ...
+
+-c command  run command in the shell window
+-d          enable debugger and open shell window
+-e          open editor window
+-h          print help message with legal combinations and exit
+-i          open shell window
+-r file     run file in shell window
+-s          run $IDLESTARTUP or $PYTHONSTARTUP first, in shell window
+-t title    set title of shell window
+-           run stdin in shell (- must be last option before args)
+</pre></div>
+</div>
+<p>If there are arguments:</p>
+<ul class="simple">
+<li><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>
+</div>
+<div class="section" id="startup-failure">
+<h3>Startup failure<a class="headerlink" href="#startup-failure" title="Permalink to this headline">¶</a></h3>
+<p>IDLE uses a socket to communicate between the IDLE GUI process and the user
+code execution process.  A connection must be established whenever the Shell
+starts or restarts.  (The latter is indicated by a divider line that says
+‘RESTART’). If the user process fails to connect to the GUI process, it
+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">&lt;same</span> <span class="pre">args&gt;</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>
+</div>
+<div class="section" id="running-user-code">
+<h3>Running user code<a class="headerlink" href="#running-user-code" title="Permalink to this headline">¶</a></h3>
+<p>With rare exceptions, the result of executing Python code with IDLE is
+intended to be the same as executing the same code by the default method,
+directly with Python in a text-mode system console or terminal window.
+However, the different interface and operation occasionally affect
+visible results.  For instance, <code class="docutils literal notranslate"><span class="pre">sys.modules</span></code> starts with more entries,
+and <code class="docutils literal notranslate"><span class="pre">threading.activeCount()</span></code> returns 2 instead of 1.</p>
+<p>By default, IDLE runs user code in a separate OS process rather than in
+the user interface process that runs the shell and editor.  In the execution
+process, it replaces <code class="docutils literal notranslate"><span class="pre">sys.stdin</span></code>, <code class="docutils literal notranslate"><span class="pre">sys.stdout</span></code>, and <code class="docutils literal notranslate"><span class="pre">sys.stderr</span></code>
+with objects that get input from and send output to the Shell window.
+The original values stored in <code class="docutils literal notranslate"><span class="pre">sys.__stdin__</span></code>, <code class="docutils literal notranslate"><span class="pre">sys.__stdout__</span></code>, and
+<code class="docutils literal notranslate"><span class="pre">sys.__stderr__</span></code> are not touched, but may be <code class="docutils literal notranslate"><span class="pre">None</span></code>.</p>
+<p>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.  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>
+</div>
+<div class="section" id="user-output-in-shell">
+<h3>User output in Shell<a class="headerlink" href="#user-output-in-shell" title="Permalink to this headline">¶</a></h3>
+<p>When a program outputs text, the result is determined by the
+corresponding output device.  When IDLE executes user code, <code class="docutils literal notranslate"><span class="pre">sys.stdout</span></code>
+and <code class="docutils literal notranslate"><span class="pre">sys.stderr</span></code> are connected to the display area of 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">&gt;&gt;&gt; </span><span class="n">s</span> <span class="o">=</span> <span class="s1">&#39;a</span><span class="se">\t</span><span class="s1">b</span><span class="se">\a</span><span class="s1">&lt;</span><span class="se">\x02</span><span class="s1">&gt;&lt;</span><span class="se">\r</span><span class="s1">&gt;</span><span class="se">\b</span><span class="s1">c</span><span class="se">\n</span><span class="s1">d&#39;</span>  <span class="c1"># Enter 22 chars.</span>
+<span class="gp">&gt;&gt;&gt; </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">&gt;&gt;&gt; </span><span class="n">s</span>  <span class="c1"># Display repr(s)</span>
+<span class="go">&#39;a\tb\x07&lt;\x02&gt;&lt;\r&gt;\x08c\nd&#39;</span>
+<span class="gp">&gt;&gt;&gt; </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">&#39;&#39;</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>
+</div>
+<div class="section" id="developing-tkinter-applications">
+<h3>Developing tkinter applications<a class="headerlink" href="#developing-tkinter-applications" title="Permalink to this headline">¶</a></h3>
+<p>IDLE is intentionally different from standard Python in order to
+facilitate development of tkinter programs.  Enter <code class="docutils literal notranslate"><span class="pre">import</span> <span class="pre">tkinter</span> <span class="pre">as</span> <span class="pre">tk;</span>
+<span class="pre">root</span> <span class="pre">=</span> <span class="pre">tk.Tk()</span></code> in standard Python and nothing appears.  Enter the same
+in IDLE and a tk window appears.  In standard Python, one must also enter
+<code class="docutils literal notranslate"><span class="pre">root.update()</span></code> to see the window.  IDLE does the equivalent in the
+background, about 20 times a second, which is about every 50 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">&gt;&gt;&gt;</span></code> shell prompt does not
+appear until <code class="docutils literal notranslate"><span class="pre">mainloop()</span></code> returns, at which time there is nothing left
+to interact with.</p>
+<p>When running a tkinter program from an IDLE editor, one can comment out
+the mainloop call.  One then gets a shell prompt immediately and can
+interact with the live application.  One just has to remember to
+re-enable the mainloop call when running in standard Python.</p>
+</div>
+<div class="section" id="running-without-a-subprocess">
+<h3>Running without a subprocess<a class="headerlink" href="#running-without-a-subprocess" title="Permalink to this headline">¶</a></h3>
+<p>By default, IDLE executes user code in a separate subprocess via a socket,
+which uses the internal loopback interface.  This connection is not
+externally visible and no data is sent to or received from the Internet.
+If firewall software complains anyway, you can ignore it.</p>
+<p>If the attempt to make the socket connection fails, Idle will notify you.
+Such failures are sometimes transient, but if persistent, the problem
+may be either a firewall blocking the connection or misconfiguration of
+a particular system.  Until the problem is fixed, one can run Idle with
+the -n command line switch.</p>
+<p>If IDLE is started with the -n command line switch it will run in a
+single process and will not create the subprocess which runs the RPC
+Python execution server.  This can be useful if Python cannot create
+the subprocess or the RPC socket interface on your platform.  However,
+in this mode user code is not isolated from IDLE itself.  Also, the
+environment is not restarted when Run/Run Module (F5) is selected.  If
+your code has been modified, you must reload() the affected modules and
+re-import any specific items (e.g. from foo import baz) if the changes
+are to take effect.  For these reasons, it is preferable to run IDLE
+with the default subprocess if at all possible.</p>
+<div class="deprecated">
+<p><span class="versionmodified deprecated">Deprecated since version 3.4.</span></p>
+</div>
+</div>
+</div>
+<div class="section" id="help-and-preferences">
+<h2>Help and preferences<a class="headerlink" href="#help-and-preferences" title="Permalink to this headline">¶</a></h2>
+<div class="section" id="help-sources">
+<span id="id6"></span><h3>Help sources<a class="headerlink" href="#help-sources" title="Permalink to this headline">¶</a></h3>
+<p>Help menu entry “IDLE Help” displays a formatted html version of the
+IDLE chapter of the Library Reference.  The result, in a read-only
+tkinter text window, is close to what one sees in a web browser.
+Navigate through the text with a mousewheel,
+the scrollbar, or up and down arrow keys held down.
+Or click the TOC (Table of Contents) button and select a section
+header in the opened box.</p>
+<p>Help menu entry “Python Docs” opens the extensive sources of help,
+including tutorials, available at <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>
+</div>
+<div class="section" id="setting-preferences">
+<span id="preferences"></span><h3>Setting preferences<a class="headerlink" href="#setting-preferences" title="Permalink to this headline">¶</a></h3>
+<p>The font preferences, highlighting, keys, and general preferences can be
+changed via Configure IDLE on the Option menu.
+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>
+</div>
+<div class="section" id="idle-on-macos">
+<h3>IDLE on macOS<a class="headerlink" href="#idle-on-macos" title="Permalink to this headline">¶</a></h3>
+<p>Under System Preferences: Dock, one can set “Prefer tabs when opening
+documents” to “Always”.  This setting is not compatible with the tk/tkinter
+GUI framework used by IDLE, and it breaks a few IDLE features.</p>
+</div>
+<div class="section" id="extensions">
+<h3>Extensions<a class="headerlink" href="#extensions" title="Permalink to this headline">¶</a></h3>
+<p>IDLE contains an extension facility.  Preferences for extensions can be
+changed with the Extensions tab of the preferences dialog. See the
+beginning of config-extensions.def in the idlelib directory for further
+information.  The only current default extension is zzdummy, an example
+also used for testing.</p>
+</div>
+</div>
+</div>
+
+
+            <div class="clearer"></div>
+          </div>
+        </div>
+      </div>
+      <div class="sphinxsidebar" role="navigation" aria-label="main navigation">
+        <div class="sphinxsidebarwrapper">
+  <h3><a href="../contents.html">Table of Contents</a></h3>
+  <ul>
+<li><a class="reference internal" href="#">IDLE</a><ul>
+<li><a class="reference internal" href="#menus">Menus</a><ul>
+<li><a class="reference internal" href="#file-menu-shell-and-editor">File menu (Shell and Editor)</a></li>
+<li><a class="reference internal" href="#edit-menu-shell-and-editor">Edit menu (Shell and Editor)</a></li>
+<li><a class="reference internal" href="#format-menu-editor-window-only">Format menu (Editor window only)</a></li>
+<li><a class="reference internal" href="#run-menu-editor-window-only">Run menu (Editor window only)</a></li>
+<li><a class="reference internal" href="#shell-menu-shell-window-only">Shell menu (Shell window only)</a></li>
+<li><a class="reference internal" href="#debug-menu-shell-window-only">Debug menu (Shell window only)</a></li>
+<li><a class="reference internal" href="#options-menu-shell-and-editor">Options menu (Shell and Editor)</a></li>
+<li><a class="reference internal" href="#window-menu-shell-and-editor">Window menu (Shell and Editor)</a></li>
+<li><a class="reference internal" href="#help-menu-shell-and-editor">Help menu (Shell and Editor)</a></li>
+<li><a class="reference internal" href="#context-menus">Context Menus</a></li>
+</ul>
+</li>
+<li><a class="reference internal" href="#editing-and-navigation">Editing and navigation</a><ul>
+<li><a class="reference internal" href="#editor-windows">Editor windows</a></li>
+<li><a class="reference internal" href="#key-bindings">Key bindings</a></li>
+<li><a class="reference internal" href="#automatic-indentation">Automatic indentation</a></li>
+<li><a class="reference internal" href="#completions">Completions</a></li>
+<li><a class="reference internal" href="#calltips">Calltips</a></li>
+<li><a class="reference internal" href="#code-context">Code Context</a></li>
+<li><a class="reference internal" href="#python-shell-window">Python Shell window</a></li>
+<li><a class="reference internal" href="#text-colors">Text colors</a></li>
+</ul>
+</li>
+<li><a class="reference internal" href="#startup-and-code-execution">Startup and code execution</a><ul>
+<li><a class="reference internal" href="#command-line-usage">Command line usage</a></li>
+<li><a class="reference internal" href="#startup-failure">Startup failure</a></li>
+<li><a class="reference internal" href="#running-user-code">Running user code</a></li>
+<li><a class="reference internal" href="#user-output-in-shell">User output in Shell</a></li>
+<li><a class="reference internal" href="#developing-tkinter-applications">Developing tkinter applications</a></li>
+<li><a class="reference internal" href="#running-without-a-subprocess">Running without a subprocess</a></li>
+</ul>
+</li>
+<li><a class="reference internal" href="#help-and-preferences">Help and preferences</a><ul>
+<li><a class="reference internal" href="#help-sources">Help sources</a></li>
+<li><a class="reference internal" href="#setting-preferences">Setting preferences</a></li>
+<li><a class="reference internal" href="#idle-on-macos">IDLE on macOS</a></li>
+<li><a class="reference internal" href="#extensions">Extensions</a></li>
+</ul>
+</li>
+</ul>
+</li>
+</ul>
+
+  <h4>Previous topic</h4>
+  <p class="topless"><a href="tkinter.tix.html"
+                        title="previous chapter"><code class="xref py py-mod docutils literal notranslate"><span class="pre">tkinter.tix</span></code> — Extension widgets for Tk</a></p>
+  <h4>Next topic</h4>
+  <p class="topless"><a href="othergui.html"
+                        title="next chapter">Other Graphical User Interface Packages</a></p>
+  <div role="note" aria-label="source link">
+    <h3>This Page</h3>
+    <ul class="this-page-menu">
+      <li><a href="../bugs.html">Report a Bug</a></li>
+      <li>
+        <a href="https://github.com/python/cpython/blob/master/Doc/library/idle.rst"
+            rel="nofollow">Show Source
+        </a>
+      </li>
+    </ul>
+  </div>
+        </div>
+      </div>
+      <div class="clearer"></div>
+    </div>
+    <div class="related" role="navigation" aria-label="related navigation">
+      <h3>Navigation</h3>
+      <ul>
+        <li class="right" style="margin-right: 10px">
+          <a href="../genindex.html" title="General Index"
+             >index</a></li>
+        <li class="right" >
+          <a href="../py-modindex.html" title="Python Module Index"
+             >modules</a> |</li>
+        <li class="right" >
+          <a href="othergui.html" title="Other Graphical User Interface Packages"
+             >next</a> |</li>
+        <li class="right" >
+          <a href="tkinter.tix.html" title="tkinter.tix — Extension widgets for Tk"
+             >previous</a> |</li>
+
+    <li><img src="../_static/py.png" alt=""
+             style="vertical-align: middle; margin-top: -1px"/></li>
+    <li><a href="https://www.python.org/">Python</a> &#187;</li>
+
+
+    <li id="cpython-language-and-version">
+      <a href="../index.html">3.10.0a6 Documentation</a> &#187;
+    </li>
+
+          <li class="nav-item nav-item-1"><a href="index.html" >The Python Standard Library</a> &#187;</li>
+          <li class="nav-item nav-item-2"><a href="tk.html" >Graphical User Interfaces with Tk</a> &#187;</li>
+        <li class="nav-item nav-item-this"><a href="">IDLE</a></li>
+    <li class="right">
+
+
+    <div class="inline-search" style="display: none" role="search">
+        <form class="inline-search" action="../search.html" method="get">
+          <input placeholder="Quick search" type="text" name="q" />
+          <input type="submit" value="Go" />
+          <input type="hidden" name="check_keywords" value="yes" />
+          <input type="hidden" name="area" value="default" />
+        </form>
+    </div>
+    <script type="text/javascript">$('.inline-search').show(0);</script>
+         |
+    </li>
+
+      </ul>
+    </div>
+    <div class="footer">
+    &copy; <a href="../copyright.html">Copyright</a> 2001-2021, Python Software Foundation.
+    <br />
+
+    The Python Software Foundation is a non-profit corporation.
+<a href="https://www.python.org/psf/donations/">Please donate.</a>
+<br />
+    <br />
+
+    Last updated on Mar 29, 2021.
+    <a href="https://docs.python.org/3/bugs.html">Found a bug</a>?
+    <br />
+
+    Created using <a href="https://www.sphinx-doc.org/">Sphinx</a> 3.2.1.
+    </div>
+
+  </body>
+</html>
diff --git a/rootfs/usr/lib/python3.8/idlelib/help.py b/rootfs/usr/lib/python3.8/idlelib/help.py
new file mode 100644
index 0000000..f420d40
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/help.py
@@ -0,0 +1,294 @@
+""" 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 idle.html to help.html, rstripping each line.
+
+show_idlehelp - Create HelpWindow.  Called in EditorWindow.help_dialog.
+"""
+from html.parser import HTMLParser
+from os.path import abspath, dirname, isfile, join
+from platform import python_version
+
+from tkinter import Toplevel, Text, Menu
+from tkinter.ttk import Frame, Menubutton, Scrollbar, Style
+from tkinter import font as tkfont
+
+from idlelib.config import idleConf
+
+## About IDLE ##
+
+
+## IDLE Help ##
+
+class HelpParser(HTMLParser):
+    """Render help.html into a text widget.
+
+    The overridden handle_xyz methods handle a subset of html tags.
+    The supplied text should have the needed tag configurations.
+    The behavior for unsupported tags, such as table, is undefined.
+    If the tags generated by Sphinx change, this class, especially
+    the handle_starttag and handle_endtags methods, might have to also.
+    """
+    def __init__(self, text):
+        HTMLParser.__init__(self, convert_charrefs=True)
+        self.text = text         # Text widget we're rendering into.
+        self.tags = ''           # Current block level text tags to apply.
+        self.chartags = ''       # Current character level text tags.
+        self.show = False        # Exclude html page navigation.
+        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 == 'div' and class_ == 'section':
+            self.show = True    # Start main content.
+        elif tag == 'div' and class_ == 'sphinxsidebar':
+            self.show = False   # End main content.
+        elif 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* ' if self.simplelist else '\n\n* '
+        elif tag == 'dt':
+            s = '\n\n' if not self.nested_dl else '\n'  # Avoid extra line.
+            self.nested_dl = False
+        elif tag == 'dd':
+            self.indent()
+            s = '\n'
+        elif tag == 'pre':
+            self.pre = True
+            if self.show:
+                self.text.insert('end', '\n\n')
+            self.tags = 'preblock'
+        elif tag == 'a' and class_ == 'headerlink':
+            self.hdrlink = True
+        elif tag == 'h1':
+            self.tags = tag
+        elif tag in ['h2', 'h3']:
+            if self.show:
+                self.header = ''
+                self.text.insert('end', '\n\n')
+            self.tags = tag
+        if self.show:
+            self.text.insert('end', s, (self.tags, self.chartags))
+        self.prevtag = (True, tag)
+
+    def handle_endtag(self, tag):
+        "Handle endtags in help.html."
+        if tag in ['h1', 'h2', 'h3']:
+            assert self.level == 0
+            if self.show:
+                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 self.show and not self.hdrlink:
+            d = data if self.pre else data.replace('\n', ' ')
+            if self.tags == 'h1':
+                try:
+                    self.hprefix = d[0:d.index(' ')]
+                except ValueError:
+                    self.hprefix = ''
+            if self.tags in ['h1', 'h2', 'h3']:
+                if (self.hprefix != '' and
+                    d[0:len(self.hprefix)] == self.hprefix):
+                    d = d[len(self.hprefix):]
+                self.header += d.strip()
+            self.text.insert('end', d, (self.tags, self.chartags))
+
+
+class HelpText(Text):
+    "Display help.html."
+    def __init__(self, parent, filename):
+        "Configure tags and feed file to parser."
+        uwide = idleConf.GetOption('main', 'EditorWindow', 'width', type='int')
+        uhigh = idleConf.GetOption('main', 'EditorWindow', 'height', type='int')
+        uhigh = 3 * uhigh // 4  # Lines average 4/3 of editor line height.
+        Text.__init__(self, parent, wrap='word', highlightthickness=0,
+                      padx=5, borderwidth=0, width=uwide, height=uhigh)
+
+        normalfont = self.findfont(['TkDefaultFont', 'arial', 'helvetica'])
+        fixedfont = self.findfont(['TkFixedFont', 'monaco', 'courier'])
+        self['font'] = (normalfont, 12)
+        self.tag_configure('em', font=(normalfont, 12, 'italic'))
+        self.tag_configure('h1', font=(normalfont, 20, 'bold'))
+        self.tag_configure('h2', font=(normalfont, 18, 'bold'))
+        self.tag_configure('h3', font=(normalfont, 15, 'bold'))
+        self.tag_configure('pre', font=(fixedfont, 12), background='#f6f6ff')
+        self.tag_configure('preblock', font=(fixedfont, 10), lmargin1=25,
+                borderwidth=1, relief='solid', background='#eeffcc')
+        self.tag_configure('l1', lmargin1=25, lmargin2=25)
+        self.tag_configure('l2', lmargin1=50, lmargin2=50)
+        self.tag_configure('l3', lmargin1=75, lmargin2=75)
+        self.tag_configure('l4', lmargin1=100, lmargin2=100)
+
+        self.parser = HelpParser(self)
+        with open(filename, encoding='utf-8') as f:
+            contents = f.read()
+        self.parser.feed(contents)
+        self['state'] = 'disabled'
+
+    def findfont(self, names):
+        "Return name of first font family derived from names."
+        for name in names:
+            if name.lower() in (x.lower() for x in tkfont.names(root=self)):
+                font = tkfont.Font(name=name, exists=True, root=self)
+                return font.actual()['family']
+            elif name.lower() in (x.lower()
+                                  for x in tkfont.families(root=self)):
+                return name
+
+
+class HelpFrame(Frame):
+    "Display html text, scrollbar, and toc."
+    def __init__(self, parent, filename):
+        Frame.__init__(self, parent)
+        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)
+        HelpFrame(self, filename).grid(column=0, row=0, sticky='nsew')
+        self.grid_columnconfigure(0, weight=1)
+        self.grid_rowconfigure(0, weight=1)
+
+
+def copy_strip():
+    """Copy idle.html to idlelib/help.html, stripping trailing whitespace.
+
+    Files with trailing whitespace cannot be pushed to the git cpython
+    repository.  For 3.x (on Windows), help.html is generated, after
+    editing idle.rst on the master branch, with
+      sphinx-build -bhtml . build/html
+      python_d.exe -c "from idlelib.help import copy_strip; copy_strip()"
+    Check build/html/library/idle.html, the help.html diff, and the text
+    displayed by Help => IDLE Help.  Add a blurb and create a PR.
+
+    It can be worthwhile to occasionally generate help.html without
+    touching idle.rst.  Changes to the master version and to the doc
+    build system may result in changes that should not changed
+    the displayed text, but might break HelpParser.
+
+    As long as master and maintenance versions of idle.rst remain the
+    same, help.html can be backported.  The internal Python version
+    number is not displayed.  If maintenance idle.rst diverges from
+    the master version, then instead of backporting help.html from
+    master, repeat the 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, 'rb') as inn,\
+         open(dst, 'wb') as out:
+        for line in inn:
+            out.write(line.rstrip() + b'\n')
+    print(f'{src} copied to {dst}')
+
+def show_idlehelp(parent):
+    "Create HelpWindow; called from Idle Help event handler."
+    filename = join(abspath(dirname(__file__)), 'help.html')
+    if not isfile(filename):
+        # Try copy_strip, present message.
+        return
+    HelpWindow(parent, filename, 'IDLE Help (%s)' % python_version())
+
+if __name__ == '__main__':
+    from unittest import main
+    main('idlelib.idle_test.test_help', verbosity=2, exit=False)
+
+    from idlelib.idle_test.htest import run
+    run(show_idlehelp)
diff --git a/rootfs/usr/lib/python3.8/idlelib/help_about.py b/rootfs/usr/lib/python3.8/idlelib/help_about.py
new file mode 100644
index 0000000..64b13ac
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/help_about.py
@@ -0,0 +1,207 @@
+"""About Dialog for IDLE
+
+"""
+import os
+import sys
+from platform import python_version, architecture
+
+from tkinter import Toplevel, Frame, Label, Button, PhotoImage
+from tkinter import SUNKEN, TOP, BOTTOM, LEFT, X, BOTH, W, EW, NSEW, E
+
+from idlelib import textview
+
+
+def build_bits():
+    "Return bits for platform."
+    if sys.platform == 'darwin':
+        return '64' if sys.maxsize > 2**32 else '32'
+    else:
+        return architecture()[0][:2]
+
+
+class AboutDialog(Toplevel):
+    """Modal about dialog for idle
+
+    """
+    def __init__(self, parent, title=None, *, _htest=False, _utest=False):
+        """Create popup, do not return until tk widget destroyed.
+
+        parent - parent of this dialog
+        title - string which is title of popup dialog
+        _htest - bool, change box location when running htest
+        _utest - bool, don't wait_window when running unittest
+        """
+        Toplevel.__init__(self, parent)
+        self.configure(borderwidth=5)
+        # place dialog below parent if running htest
+        self.geometry("+%d+%d" % (
+                        parent.winfo_rootx()+30,
+                        parent.winfo_rooty()+(30 if not _htest else 100)))
+        self.bg = "#bbbbbb"
+        self.fg = "#000000"
+        self.create_widgets()
+        self.resizable(height=False, width=False)
+        self.title(title or
+                   f'About IDLE {python_version()} ({build_bits()} bit)')
+        self.transient(parent)
+        self.grab_set()
+        self.protocol("WM_DELETE_WINDOW", self.ok)
+        self.parent = parent
+        self.button_ok.focus_set()
+        self.bind('<Return>', self.ok)  # dismiss dialog
+        self.bind('<Escape>', self.ok)  # dismiss dialog
+        self._current_textview = None
+        self._utest = _utest
+
+        if not _utest:
+            self.deiconify()
+            self.wait_window()
+
+    def create_widgets(self):
+        frame = Frame(self, borderwidth=2, relief=SUNKEN)
+        frame_buttons = Frame(self)
+        frame_buttons.pack(side=BOTTOM, fill=X)
+        frame.pack(side=TOP, expand=True, fill=BOTH)
+        self.button_ok = Button(frame_buttons, text='Close',
+                                command=self.ok)
+        self.button_ok.pack(padx=5, pady=5)
+
+        frame_background = Frame(frame, bg=self.bg)
+        frame_background.pack(expand=True, fill=BOTH)
+
+        header = Label(frame_background, text='IDLE', fg=self.fg,
+                       bg=self.bg, font=('courier', 24, 'bold'))
+        header.grid(row=0, column=0, sticky=E, padx=10, pady=10)
+
+        tk_patchlevel = self.tk.call('info', 'patchlevel')
+        ext = '.png' if tk_patchlevel >= '8.6' else '.gif'
+        icon = os.path.join(os.path.abspath(os.path.dirname(__file__)),
+                            'Icons', f'idle_48{ext}')
+        self.icon_image = PhotoImage(master=self._root(), file=icon)
+        logo = Label(frame_background, image=self.icon_image, bg=self.bg)
+        logo.grid(row=0, column=0, sticky=W, rowspan=2, padx=10, pady=10)
+
+        byline_text = "Python's Integrated Development\nand Learning Environment" + 5*'\n'
+        byline = Label(frame_background, text=byline_text, justify=LEFT,
+                       fg=self.fg, bg=self.bg)
+        byline.grid(row=2, column=0, sticky=W, columnspan=3, padx=10, pady=5)
+        email = Label(frame_background, text='email:  idle-dev@python.org',
+                      justify=LEFT, fg=self.fg, bg=self.bg)
+        email.grid(row=6, column=0, columnspan=2, sticky=W, padx=10, pady=0)
+        docs = Label(frame_background, text='https://docs.python.org/' +
+                     python_version()[:3] + '/library/idle.html',
+                     justify=LEFT, fg=self.fg, bg=self.bg)
+        docs.grid(row=7, column=0, columnspan=2, sticky=W, padx=10, pady=0)
+
+        Frame(frame_background, borderwidth=1, relief=SUNKEN,
+              height=2, bg=self.bg).grid(row=8, column=0, sticky=EW,
+                                         columnspan=3, padx=5, pady=5)
+
+        pyver = Label(frame_background,
+                      text='Python version:  ' + python_version(),
+                      fg=self.fg, bg=self.bg)
+        pyver.grid(row=9, column=0, sticky=W, padx=10, pady=0)
+        tkver = Label(frame_background, text='Tk version:  ' + tk_patchlevel,
+                      fg=self.fg, bg=self.bg)
+        tkver.grid(row=9, column=1, sticky=W, padx=2, pady=0)
+        py_buttons = Frame(frame_background, bg=self.bg)
+        py_buttons.grid(row=10, column=0, columnspan=2, sticky=NSEW)
+        self.py_license = Button(py_buttons, text='License', width=8,
+                                 highlightbackground=self.bg,
+                                 command=self.show_py_license)
+        self.py_license.pack(side=LEFT, padx=10, pady=10)
+        self.py_copyright = Button(py_buttons, text='Copyright', width=8,
+                                   highlightbackground=self.bg,
+                                   command=self.show_py_copyright)
+        self.py_copyright.pack(side=LEFT, padx=10, pady=10)
+        self.py_credits = Button(py_buttons, text='Credits', width=8,
+                                 highlightbackground=self.bg,
+                                 command=self.show_py_credits)
+        self.py_credits.pack(side=LEFT, padx=10, pady=10)
+
+        Frame(frame_background, borderwidth=1, relief=SUNKEN,
+              height=2, bg=self.bg).grid(row=11, column=0, sticky=EW,
+                                         columnspan=3, padx=5, pady=5)
+
+        idlever = Label(frame_background,
+                        text='IDLE version:   ' + python_version(),
+                        fg=self.fg, bg=self.bg)
+        idlever.grid(row=12, column=0, sticky=W, padx=10, pady=0)
+        idle_buttons = Frame(frame_background, bg=self.bg)
+        idle_buttons.grid(row=13, column=0, columnspan=3, sticky=NSEW)
+        self.readme = Button(idle_buttons, text='README', width=8,
+                             highlightbackground=self.bg,
+                             command=self.show_readme)
+        self.readme.pack(side=LEFT, padx=10, pady=10)
+        self.idle_news = Button(idle_buttons, text='NEWS', width=8,
+                                highlightbackground=self.bg,
+                                command=self.show_idle_news)
+        self.idle_news.pack(side=LEFT, padx=10, pady=10)
+        self.idle_credits = Button(idle_buttons, text='Credits', width=8,
+                                   highlightbackground=self.bg,
+                                   command=self.show_idle_credits)
+        self.idle_credits.pack(side=LEFT, padx=10, pady=10)
+
+    # License, copyright, and credits are of type _sitebuiltins._Printer
+    def show_py_license(self):
+        "Handle License button event."
+        self.display_printer_text('About - License', license)
+
+    def show_py_copyright(self):
+        "Handle Copyright button event."
+        self.display_printer_text('About - Copyright', copyright)
+
+    def show_py_credits(self):
+        "Handle Python Credits button event."
+        self.display_printer_text('About - Python Credits', credits)
+
+    # Encode CREDITS.txt to utf-8 for proper version of Loewis.
+    # Specify others as ascii until need utf-8, so catch errors.
+    def show_idle_credits(self):
+        "Handle Idle Credits button event."
+        self.display_file_text('About - Credits', 'CREDITS.txt', 'utf-8')
+
+    def show_readme(self):
+        "Handle Readme button event."
+        self.display_file_text('About - Readme', 'README.txt', 'ascii')
+
+    def show_idle_news(self):
+        "Handle News button event."
+        self.display_file_text('About - NEWS', 'NEWS.txt', 'utf-8')
+
+    def display_printer_text(self, title, printer):
+        """Create textview for built-in constants.
+
+        Built-in constants have type _sitebuiltins._Printer.  The
+        text is extracted from the built-in and then sent to a text
+        viewer with self as the parent and title as the title of
+        the popup.
+        """
+        printer._Printer__setup()
+        text = '\n'.join(printer._Printer__lines)
+        self._current_textview = textview.view_text(
+            self, title, text, _utest=self._utest)
+
+    def display_file_text(self, title, filename, encoding=None):
+        """Create textview for filename.
+
+        The filename needs to be in the current directory.  The path
+        is sent to a text viewer with self as the parent, title as
+        the title of the popup, and the file encoding.
+        """
+        fn = os.path.join(os.path.abspath(os.path.dirname(__file__)), filename)
+        self._current_textview = textview.view_file(
+            self, title, fn, encoding, _utest=self._utest)
+
+    def ok(self, event=None):
+        "Dismiss help_about dialog."
+        self.grab_release()
+        self.destroy()
+
+
+if __name__ == '__main__':
+    from unittest import main
+    main('idlelib.idle_test.test_help_about', verbosity=2, exit=False)
+
+    from idlelib.idle_test.htest import run
+    run(AboutDialog)
diff --git a/rootfs/usr/lib/python3.8/idlelib/history.py b/rootfs/usr/lib/python3.8/idlelib/history.py
new file mode 100644
index 0000000..ad44a96
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/history.py
@@ -0,0 +1,106 @@
+"Implement Idle Shell history mechanism with History class"
+
+from idlelib.config import idleConf
+
+
+class History:
+    ''' Implement Idle Shell history mechanism.
+
+    store - Store source statement (called from pyshell.resetoutput).
+    fetch - Fetch stored statement matching prefix already entered.
+    history_next - Bound to <<history-next>> event (default Alt-N).
+    history_prev - Bound to <<history-prev>> event (default Alt-P).
+    '''
+    def __init__(self, text):
+        '''Initialize data attributes and bind event methods.
+
+        .text - Idle wrapper of tk Text widget, with .bell().
+        .history - source statements, possibly with multiple lines.
+        .prefix - source already entered at prompt; filters history list.
+        .pointer - index into history.
+        .cyclic - wrap around history list (or not).
+        '''
+        self.text = text
+        self.history = []
+        self.prefix = None
+        self.pointer = None
+        self.cyclic = idleConf.GetOption("main", "History", "cyclic", 1, "bool")
+        text.bind("<<history-previous>>", self.history_prev)
+        text.bind("<<history-next>>", self.history_next)
+
+    def history_next(self, event):
+        "Fetch later statement; start with ealiest if cyclic."
+        self.fetch(reverse=False)
+        return "break"
+
+    def history_prev(self, event):
+        "Fetch earlier statement; start with most recent."
+        self.fetch(reverse=True)
+        return "break"
+
+    def fetch(self, reverse):
+        '''Fetch 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 1:
+            pointer += -1 if reverse else 1
+            if pointer < 0 or pointer >= nhist:
+                self.text.bell()
+                if not self.cyclic and pointer < 0:  # abort history_prev
+                    return
+                else:
+                    if self.text.get("iomark", "end-1c") != prefix:
+                        self.text.delete("iomark", "end-1c")
+                        self.text.insert("iomark", prefix)
+                    pointer = prefix = None
+                break
+            item = self.history[pointer]
+            if item[:nprefix] == prefix and len(item) > nprefix:
+                self.text.delete("iomark", "end-1c")
+                self.text.insert("iomark", item)
+                break
+        self.text.see("insert")
+        self.text.tag_remove("sel", "1.0", "end")
+        self.pointer = pointer
+        self.prefix = prefix
+
+    def store(self, source):
+        "Store Shell input statement into history list."
+        source = source.strip()
+        if len(source) > 2:
+            # avoid duplicates
+            try:
+                self.history.remove(source)
+            except ValueError:
+                pass
+            self.history.append(source)
+        self.pointer = None
+        self.prefix = None
+
+
+if __name__ == "__main__":
+    from unittest import main
+    main('idlelib.idle_test.test_history', verbosity=2, exit=False)
diff --git a/rootfs/usr/lib/python3.8/idlelib/hyperparser.py b/rootfs/usr/lib/python3.8/idlelib/hyperparser.py
new file mode 100644
index 0000000..77baca7
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/hyperparser.py
@@ -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 1:
+            # Eat whitespaces, comments, and if postdot_phase is False - a dot
+            while 1:
+                if pos>brck_limit and rawtext[pos-1] in self._whitespace_chars:
+                    # Eat a whitespace
+                    pos -= 1
+                elif (not postdot_phase and
+                      pos > brck_limit and rawtext[pos-1] == '.'):
+                    # Eat a dot
+                    pos -= 1
+                    postdot_phase = True
+                # The next line will fail if we are *inside* a comment,
+                # but we shouldn't be.
+                elif (pos == brck_limit and brck_index > 0 and
+                      rawtext[bracketing[brck_index-1][0]] == '#'):
+                    # Eat a comment
+                    brck_index -= 2
+                    brck_limit = bracketing[brck_index][0]
+                    pos = bracketing[brck_index+1][0]
+                else:
+                    # If we didn't eat anything, quit.
+                    break
+
+            if not postdot_phase:
+                # We didn't find a dot, so the expression end at the
+                # last identifier pos.
+                break
+
+            ret = self._eat_identifier(rawtext, brck_limit, pos)
+            if ret:
+                # There is an identifier to eat
+                pos = pos - ret
+                last_identifier_pos = pos
+                # Now, to continue the search, we must find a dot.
+                postdot_phase = False
+                # (the loop continues now)
+
+            elif pos == brck_limit:
+                # We are at a bracketing limit. If it is a closing
+                # bracket, eat the bracket, otherwise, stop the search.
+                level = bracketing[brck_index][1]
+                while brck_index > 0 and bracketing[brck_index-1][1] > level:
+                    brck_index -= 1
+                if bracketing[brck_index][0] == brck_limit:
+                    # We were not at the end of a closing bracket
+                    break
+                pos = bracketing[brck_index][0]
+                brck_index -= 1
+                brck_limit = bracketing[brck_index][0]
+                last_identifier_pos = pos
+                if rawtext[pos] in "([":
+                    # [] and () may be used after an identifier, so we
+                    # continue. postdot_phase is True, so we don't allow a dot.
+                    pass
+                else:
+                    # We can't continue after other types of brackets
+                    if rawtext[pos] in "'\"":
+                        # Scan a string prefix
+                        while pos > 0 and rawtext[pos - 1] in "rRbBuU":
+                            pos -= 1
+                        last_identifier_pos = pos
+                    break
+
+            else:
+                # We've found an operator or something.
+                break
+
+        return rawtext[last_identifier_pos:self.indexinrawtext]
+
+
+if __name__ == '__main__':
+    from unittest import main
+    main('idlelib.idle_test.test_hyperparser', verbosity=2)
diff --git a/rootfs/usr/lib/python3.8/idlelib/idle.bat b/rootfs/usr/lib/python3.8/idlelib/idle.bat
new file mode 100755
index 0000000..3d619a3
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/idle.bat
@@ -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

diff --git a/rootfs/usr/lib/python3.8/idlelib/idle.py b/rootfs/usr/lib/python3.8/idlelib/idle.py
new file mode 100644
index 0000000..485d5a7
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/idle.py
@@ -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()
diff --git a/rootfs/usr/lib/python3.8/idlelib/idle.pyw b/rootfs/usr/lib/python3.8/idlelib/idle.pyw
new file mode 100644
index 0000000..e73c049
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/idle.pyw
@@ -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()
diff --git a/rootfs/usr/lib/python3.8/idlelib/iomenu.py b/rootfs/usr/lib/python3.8/idlelib/iomenu.py
new file mode 100644
index 0000000..5ebf708
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/iomenu.py
@@ -0,0 +1,434 @@
+import io
+import os
+import shlex
+import sys
+import tempfile
+import tokenize
+
+from tkinter import filedialog
+from tkinter import messagebox
+from tkinter.simpledialog import askstring
+
+import idlelib
+from idlelib.config import idleConf
+
+encoding = 'utf-8'
+if sys.platform == 'win32':
+    errors = 'surrogatepass'
+else:
+    errors = 'surrogateescape'
+
+
+
+class IOBinding:
+# One instance per editor Window so methods know which to save, close.
+# Open returns focus to self.editwin if aborted.
+# EditorWindow.open_module, others, belong here.
+
+    def __init__(self, editwin):
+        self.editwin = editwin
+        self.text = editwin.text
+        self.__id_open = self.text.bind("<<open-window-from-file>>", self.open)
+        self.__id_save = self.text.bind("<<save-window>>", self.save)
+        self.__id_saveas = self.text.bind("<<save-window-as-file>>",
+                                          self.save_as)
+        self.__id_savecopy = self.text.bind("<<save-copy-of-window-as-file>>",
+                                            self.save_a_copy)
+        self.fileencoding = 'utf-8'
+        self.__id_print = self.text.bind("<<print-window>>", self.print_window)
+
+    def close(self):
+        # Undo command bindings
+        self.text.unbind("<<open-window-from-file>>", self.__id_open)
+        self.text.unbind("<<save-window>>", self.__id_save)
+        self.text.unbind("<<save-window-as-file>>",self.__id_saveas)
+        self.text.unbind("<<save-copy-of-window-as-file>>", self.__id_savecopy)
+        self.text.unbind("<<print-window>>", self.__id_print)
+        # Break cycles
+        self.editwin = None
+        self.text = None
+        self.filename_change_hook = None
+
+    def get_saved(self):
+        return self.editwin.get_saved()
+
+    def set_saved(self, flag):
+        self.editwin.set_saved(flag)
+
+    def reset_undo(self):
+        self.editwin.reset_undo()
+
+    filename_change_hook = None
+
+    def set_filename_change_hook(self, hook):
+        self.filename_change_hook = hook
+
+    filename = None
+    dirname = None
+
+    def set_filename(self, filename):
+        if filename and os.path.isdir(filename):
+            self.filename = None
+            self.dirname = filename
+        else:
+            self.filename = filename
+            self.dirname = None
+            self.set_saved(1)
+            if self.filename_change_hook:
+                self.filename_change_hook()
+
+    def open(self, event=None, editFile=None):
+        flist = self.editwin.flist
+        # Save in case parent window is closed (ie, during askopenfile()).
+        if flist:
+            if not editFile:
+                filename = self.askopenfile()
+            else:
+                filename=editFile
+            if filename:
+                # If editFile is valid and already open, flist.open will
+                # shift focus to its existing window.
+                # If the current window exists and is a fresh unnamed,
+                # unmodified editor window (not an interpreter shell),
+                # pass self.loadfile to flist.open so it will load the file
+                # in the current window (if the file is not already open)
+                # instead of a new window.
+                if (self.editwin and
+                        not getattr(self.editwin, 'interp', None) and
+                        not self.filename and
+                        self.get_saved()):
+                    flist.open(filename, self.loadfile)
+                else:
+                    flist.open(filename)
+            else:
+                if self.text:
+                    self.text.focus_set()
+            return "break"
+
+        # Code for use outside IDLE:
+        if self.get_saved():
+            reply = self.maybesave()
+            if reply == "cancel":
+                self.text.focus_set()
+                return "break"
+        if not editFile:
+            filename = self.askopenfile()
+        else:
+            filename=editFile
+        if filename:
+            self.loadfile(filename)
+        else:
+            self.text.focus_set()
+        return "break"
+
+    eol_convention = os.linesep  # default
+
+    def loadfile(self, filename):
+        try:
+            try:
+                with tokenize.open(filename) as f:
+                    chars = f.read()
+                    fileencoding = f.encoding
+                    eol_convention = f.newlines
+                    converted = False
+            except (UnicodeDecodeError, SyntaxError):
+                # Wait for the editor window to appear
+                self.editwin.text.update()
+                enc = askstring(
+                    "Specify file encoding",
+                    "The file's encoding is invalid for Python 3.x.\n"
+                    "IDLE will convert it to UTF-8.\n"
+                    "What is the current encoding of the file?",
+                    initialvalue='utf-8',
+                    parent=self.editwin.text)
+                with open(filename, encoding=enc) as f:
+                    chars = f.read()
+                    fileencoding = f.encoding
+                    eol_convention = f.newlines
+                    converted = True
+        except OSError as err:
+            messagebox.showerror("I/O Error", str(err), parent=self.text)
+            return False
+        except UnicodeDecodeError:
+            messagebox.showerror("Decoding Error",
+                                   "File %s\nFailed to Decode" % filename,
+                                   parent=self.text)
+            return False
+
+        if not isinstance(eol_convention, str):
+            # If the file does not contain line separators, it is None.
+            # If the file contains mixed line separators, it is a tuple.
+            if eol_convention is not None:
+                messagebox.showwarning("Mixed Newlines",
+                                         "Mixed newlines detected.\n"
+                                         "The file will be changed on save.",
+                                         parent=self.text)
+                converted = True
+            eol_convention = os.linesep  # default
+
+        self.text.delete("1.0", "end")
+        self.set_filename(None)
+        self.fileencoding = fileencoding
+        self.eol_convention = eol_convention
+        self.text.insert("1.0", chars)
+        self.reset_undo()
+        self.set_filename(filename)
+        if converted:
+            # We need to save the conversion results first
+            # before being able to execute the code
+            self.set_saved(False)
+        self.text.mark_set("insert", "1.0")
+        self.text.yview("insert")
+        self.updaterecentfileslist(filename)
+        return True
+
+    def maybesave(self):
+        if self.get_saved():
+            return "yes"
+        message = "Do you want to save %s before closing?" % (
+            self.filename or "this untitled document")
+        confirm = messagebox.askyesnocancel(
+                  title="Save On Close",
+                  message=message,
+                  default=messagebox.YES,
+                  parent=self.text)
+        if confirm:
+            reply = "yes"
+            self.save(None)
+            if not self.get_saved():
+                reply = "cancel"
+        elif confirm is None:
+            reply = "cancel"
+        else:
+            reply = "no"
+        self.text.focus_set()
+        return reply
+
+    def save(self, event):
+        if not self.filename:
+            self.save_as(event)
+        else:
+            if self.writefile(self.filename):
+                self.set_saved(True)
+                try:
+                    self.editwin.store_file_breaks()
+                except AttributeError:  # may be a PyShell
+                    pass
+        self.text.focus_set()
+        return "break"
+
+    def save_as(self, event):
+        filename = self.asksavefile()
+        if filename:
+            if self.writefile(filename):
+                self.set_filename(filename)
+                self.set_saved(1)
+                try:
+                    self.editwin.store_file_breaks()
+                except AttributeError:
+                    pass
+        self.text.focus_set()
+        self.updaterecentfileslist(filename)
+        return "break"
+
+    def save_a_copy(self, event):
+        filename = self.asksavefile()
+        if filename:
+            self.writefile(filename)
+        self.text.focus_set()
+        self.updaterecentfileslist(filename)
+        return "break"
+
+    def writefile(self, filename):
+        text = self.fixnewlines()
+        chars = self.encode(text)
+        try:
+            with open(filename, "wb") as f:
+                f.write(chars)
+                f.flush()
+                os.fsync(f.fileno())
+            return True
+        except OSError as msg:
+            messagebox.showerror("I/O Error", str(msg),
+                                   parent=self.text)
+            return False
+
+    def fixnewlines(self):
+        "Return text with final \n if needed and os eols."
+        if (self.text.get("end-2c") != '\n'
+            and not hasattr(self.editwin, "interp")):  # Not shell.
+            self.text.insert("end-1c", "\n")
+        text = self.text.get("1.0", "end-1c")
+        if self.eol_convention != "\n":
+            text = text.replace("\n", self.eol_convention)
+        return text
+
+    def encode(self, chars):
+        if isinstance(chars, bytes):
+            # This is either plain ASCII, or Tk was returning mixed-encoding
+            # text to us. Don't try to guess further.
+            return chars
+        # Preserve a BOM that might have been present on opening
+        if self.fileencoding == 'utf-8-sig':
+            return chars.encode('utf-8-sig')
+        # See whether there is anything non-ASCII in it.
+        # If not, no need to figure out the encoding.
+        try:
+            return chars.encode('ascii')
+        except UnicodeEncodeError:
+            pass
+        # Check if there is an encoding declared
+        try:
+            encoded = chars.encode('ascii', 'replace')
+            enc, _ = tokenize.detect_encoding(io.BytesIO(encoded).readline)
+            return chars.encode(enc)
+        except SyntaxError as err:
+            failed = str(err)
+        except UnicodeEncodeError:
+            failed = "Invalid encoding '%s'" % enc
+        messagebox.showerror(
+            "I/O Error",
+            "%s.\nSaving as UTF-8" % failed,
+            parent=self.text)
+        # Fallback: save as UTF-8, with BOM - ignoring the incorrect
+        # declared encoding
+        return chars.encode('utf-8-sig')
+
+    def print_window(self, event):
+        confirm = messagebox.askokcancel(
+                  title="Print",
+                  message="Print to Default Printer",
+                  default=messagebox.OK,
+                  parent=self.text)
+        if not confirm:
+            self.text.focus_set()
+            return "break"
+        tempfilename = None
+        saved = self.get_saved()
+        if saved:
+            filename = self.filename
+        # shell undo is reset after every prompt, looks saved, probably isn't
+        if not saved or filename is None:
+            (tfd, tempfilename) = tempfile.mkstemp(prefix='IDLE_tmp_')
+            filename = tempfilename
+            os.close(tfd)
+            if not self.writefile(tempfilename):
+                os.unlink(tempfilename)
+                return "break"
+        platform = os.name
+        printPlatform = True
+        if platform == 'posix': #posix platform
+            command = idleConf.GetOption('main','General',
+                                         'print-command-posix')
+            command = command + " 2>&1"
+        elif platform == 'nt': #win32 platform
+            command = idleConf.GetOption('main','General','print-command-win')
+        else: #no printing for this platform
+            printPlatform = False
+        if printPlatform:  #we can try to print for this platform
+            command = command % shlex.quote(filename)
+            pipe = os.popen(command, "r")
+            # things can get ugly on NT if there is no printer available.
+            output = pipe.read().strip()
+            status = pipe.close()
+            if status:
+                output = "Printing failed (exit status 0x%x)\n" % \
+                         status + output
+            if output:
+                output = "Printing command: %s\n" % repr(command) + output
+                messagebox.showerror("Print status", output, parent=self.text)
+        else:  #no printing for this platform
+            message = "Printing is not enabled for this platform: %s" % platform
+            messagebox.showinfo("Print status", message, parent=self.text)
+        if tempfilename:
+            os.unlink(tempfilename)
+        return "break"
+
+    opendialog = None
+    savedialog = None
+
+    filetypes = (
+        ("Python files", "*.py *.pyw", "TEXT"),
+        ("Text files", "*.txt", "TEXT"),
+        ("All files", "*"),
+        )
+
+    defaultextension = '.py' if sys.platform == 'darwin' else ''
+
+    def askopenfile(self):
+        dir, base = self.defaultfilename("open")
+        if not self.opendialog:
+            self.opendialog = filedialog.Open(parent=self.text,
+                                                filetypes=self.filetypes)
+        filename = self.opendialog.show(initialdir=dir, initialfile=base)
+        return filename
+
+    def defaultfilename(self, mode="open"):
+        if self.filename:
+            return os.path.split(self.filename)
+        elif self.dirname:
+            return self.dirname, ""
+        else:
+            try:
+                pwd = os.getcwd()
+            except OSError:
+                pwd = ""
+            return pwd, ""
+
+    def asksavefile(self):
+        dir, base = self.defaultfilename("save")
+        if not self.savedialog:
+            self.savedialog = filedialog.SaveAs(
+                    parent=self.text,
+                    filetypes=self.filetypes,
+                    defaultextension=self.defaultextension)
+        filename = self.savedialog.show(initialdir=dir, initialfile=base)
+        return filename
+
+    def updaterecentfileslist(self,filename):
+        "Update recent file list on all editor windows"
+        if self.editwin.flist:
+            self.editwin.update_recent_files_list(filename)
+
+def _io_binding(parent):  # htest #
+    from tkinter import Toplevel, Text
+
+    root = Toplevel(parent)
+    root.title("Test IOBinding")
+    x, y = map(int, parent.geometry().split('+')[1:])
+    root.geometry("+%d+%d" % (x, y + 175))
+    class MyEditWin:
+        def __init__(self, text):
+            self.text = text
+            self.flist = None
+            self.text.bind("<Control-o>", self.open)
+            self.text.bind('<Control-p>', self.print)
+            self.text.bind("<Control-s>", self.save)
+            self.text.bind("<Alt-s>", self.saveas)
+            self.text.bind('<Control-c>', self.savecopy)
+        def get_saved(self): return 0
+        def set_saved(self, flag): pass
+        def reset_undo(self): pass
+        def open(self, event):
+            self.text.event_generate("<<open-window-from-file>>")
+        def print(self, event):
+            self.text.event_generate("<<print-window>>")
+        def save(self, event):
+            self.text.event_generate("<<save-window>>")
+        def saveas(self, event):
+            self.text.event_generate("<<save-window-as-file>>")
+        def savecopy(self, event):
+            self.text.event_generate("<<save-copy-of-window-as-file>>")
+
+    text = Text(root)
+    text.pack()
+    text.focus_set()
+    editwin = MyEditWin(text)
+    IOBinding(editwin)
+
+if __name__ == "__main__":
+    from unittest import main
+    main('idlelib.idle_test.test_iomenu', verbosity=2, exit=False)
+
+    from idlelib.idle_test.htest import run
+    run(_io_binding)
diff --git a/rootfs/usr/lib/python3.8/idlelib/macosx.py b/rootfs/usr/lib/python3.8/idlelib/macosx.py
new file mode 100644
index 0000000..eeaab59
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/macosx.py
@@ -0,0 +1,287 @@
+"""
+A number of functions that enhance IDLE on macOS.
+"""
+from os.path import expanduser
+import plistlib
+from sys import platform  # Used in _init_tk_type, changed by test.
+
+import tkinter
+
+
+## Define functions that query the Mac graphics type.
+## _tk_type and its initializer are private to this section.
+
+_tk_type = None
+
+def _init_tk_type():
+    """
+    Initializes OS X Tk variant values for
+    isAquaTk(), isCarbonTk(), isCocoaTk(), and isXQuartz().
+    """
+    global _tk_type
+    if platform == 'darwin':
+        root = tkinter.Tk()
+        ws = root.tk.call('tk', 'windowingsystem')
+        if 'x11' in ws:
+            _tk_type = "xquartz"
+        elif 'aqua' not in ws:
+            _tk_type = "other"
+        elif 'AppKit' in root.tk.call('winfo', 'server', '.'):
+            _tk_type = "cocoa"
+        else:
+            _tk_type = "carbon"
+        root.destroy()
+    else:
+        _tk_type = "other"
+
+def isAquaTk():
+    """
+    Returns True if IDLE is using a native OS X Tk (Cocoa or Carbon).
+    """
+    if not _tk_type:
+        _init_tk_type()
+    return _tk_type == "cocoa" or _tk_type == "carbon"
+
+def isCarbonTk():
+    """
+    Returns True if IDLE is using a Carbon Aqua Tk (instead of the
+    newer Cocoa Aqua Tk).
+    """
+    if not _tk_type:
+        _init_tk_type()
+    return _tk_type == "carbon"
+
+def isCocoaTk():
+    """
+    Returns True if IDLE is using a Cocoa Aqua Tk.
+    """
+    if not _tk_type:
+        _init_tk_type()
+    return _tk_type == "cocoa"
+
+def isXQuartz():
+    """
+    Returns True if IDLE is using an OS X X11 Tk.
+    """
+    if not _tk_type:
+        _init_tk_type()
+    return _tk_type == "xquartz"
+
+
+def tkVersionWarning(root):
+    """
+    Returns a string warning message if the Tk version in use appears to
+    be one known to cause problems with IDLE.
+    1. Apple Cocoa-based Tk 8.5.7 shipped with Mac OS X 10.6 is unusable.
+    2. Apple Cocoa-based Tk 8.5.9 in OS X 10.7 and 10.8 is better but
+        can still crash unexpectedly.
+    """
+
+    if isCocoaTk():
+        patchlevel = root.tk.call('info', 'patchlevel')
+        if patchlevel not in ('8.5.7', '8.5.9'):
+            return False
+        return ("WARNING: The version of Tcl/Tk ({0}) in use may"
+                " be unstable.\n"
+                "Visit http://www.python.org/download/mac/tcltk/"
+                " for current information.".format(patchlevel))
+    else:
+        return False
+
+
+def readSystemPreferences():
+    """
+    Fetch the macOS system preferences.
+    """
+    if platform != 'darwin':
+        return None
+
+    plist_path = expanduser('~/Library/Preferences/.GlobalPreferences.plist')
+    try:
+        with open(plist_path, 'rb') as plist_file:
+            return plistlib.load(plist_file)
+    except OSError:
+        return None
+
+
+def preferTabsPreferenceWarning():
+    """
+    Warn if "Prefer tabs when opening documents" is set to "Always".
+    """
+    if platform != 'darwin':
+        return None
+
+    prefs = readSystemPreferences()
+    if prefs and prefs.get('AppleWindowTabbingMode') == 'always':
+        return (
+            'WARNING: The system preference "Prefer tabs when opening'
+            ' documents" is set to "Always". This will cause various problems'
+            ' with IDLE. For the best experience, change this setting when'
+            ' running IDLE (via System Preferences -> Dock).'
+        )
+    return None
+
+
+## Fix the menu and related functions.
+
+def addOpenEventSupport(root, flist):
+    """
+    This ensures that the application will respond to open AppleEvents, which
+    makes is feasible to use IDLE as the default application for python files.
+    """
+    def doOpenFile(*args):
+        for fn in args:
+            flist.open(fn)
+
+    # The command below is a hook in aquatk that is called whenever the app
+    # receives a file open event. The callback can have multiple arguments,
+    # one for every file that should be opened.
+    root.createcommand("::tk::mac::OpenDocument", doOpenFile)
+
+def hideTkConsole(root):
+    try:
+        root.tk.call('console', 'hide')
+    except tkinter.TclError:
+        # Some versions of the Tk framework don't have a console object
+        pass
+
+def overrideRootMenu(root, flist):
+    """
+    Replace the Tk root menu by something that is more appropriate for
+    IDLE with an Aqua Tk.
+    """
+    # The menu that is attached to the Tk root (".") is also used by AquaTk for
+    # all windows that don't specify a menu of their own. The default menubar
+    # contains a number of menus, none of which are appropriate for IDLE. The
+    # Most annoying of those is an 'About Tck/Tk...' menu in the application
+    # menu.
+    #
+    # This function replaces the default menubar by a mostly empty one, it
+    # should only contain the correct application menu and the window menu.
+    #
+    # Due to a (mis-)feature of TkAqua the user will also see an empty Help
+    # menu.
+    from tkinter import Menu
+    from idlelib import mainmenu
+    from idlelib import window
+
+    closeItem = mainmenu.menudefs[0][1][-2]
+
+    # Remove the last 3 items of the file menu: a separator, close window and
+    # quit. Close window will be reinserted just above the save item, where
+    # it should be according to the HIG. Quit is in the application menu.
+    del mainmenu.menudefs[0][1][-3:]
+    mainmenu.menudefs[0][1].insert(6, closeItem)
+
+    # Remove the 'About' entry from the help menu, it is in the application
+    # menu
+    del mainmenu.menudefs[-1][1][0:2]
+    # Remove the 'Configure Idle' entry from the options menu, it is in the
+    # application menu as 'Preferences'
+    del mainmenu.menudefs[-3][1][0:2]
+    menubar = Menu(root)
+    root.configure(menu=menubar)
+    menudict = {}
+
+    menudict['window'] = menu = Menu(menubar, name='window', tearoff=0)
+    menubar.add_cascade(label='Window', menu=menu, underline=0)
+
+    def postwindowsmenu(menu=menu):
+        end = menu.index('end')
+        if end is None:
+            end = -1
+
+        if end > 0:
+            menu.delete(0, end)
+        window.add_windows_to_menu(menu)
+    window.register_callback(postwindowsmenu)
+
+    def about_dialog(event=None):
+        "Handle Help 'About IDLE' event."
+        # Synchronize with editor.EditorWindow.about_dialog.
+        from idlelib import help_about
+        help_about.AboutDialog(root)
+
+    def config_dialog(event=None):
+        "Handle Options 'Configure IDLE' event."
+        # Synchronize with editor.EditorWindow.config_dialog.
+        from idlelib import configdialog
+
+        # Ensure that the root object has an instance_dict attribute,
+        # mirrors code in EditorWindow (although that sets the attribute
+        # on an EditorWindow instance that is then passed as the first
+        # argument to ConfigDialog)
+        root.instance_dict = flist.inversedict
+        configdialog.ConfigDialog(root, 'Settings')
+
+    def help_dialog(event=None):
+        "Handle Help 'IDLE Help' event."
+        # Synchronize with editor.EditorWindow.help_dialog.
+        from idlelib import help
+        help.show_idlehelp(root)
+
+    root.bind('<<about-idle>>', about_dialog)
+    root.bind('<<open-config-dialog>>', config_dialog)
+    root.createcommand('::tk::mac::ShowPreferences', config_dialog)
+    if flist:
+        root.bind('<<close-all-windows>>', flist.close_all_callback)
+
+        # The binding above doesn't reliably work on all versions of Tk
+        # on macOS. Adding command definition below does seem to do the
+        # right thing for now.
+        root.createcommand('exit', flist.close_all_callback)
+
+    if isCarbonTk():
+        # for Carbon AquaTk, replace the default Tk apple menu
+        menudict['application'] = menu = Menu(menubar, name='apple',
+                                              tearoff=0)
+        menubar.add_cascade(label='IDLE', menu=menu)
+        mainmenu.menudefs.insert(0,
+            ('application', [
+                ('About IDLE', '<<about-idle>>'),
+                    None,
+                ]))
+    if isCocoaTk():
+        # replace default About dialog with About IDLE one
+        root.createcommand('tkAboutDialog', about_dialog)
+        # replace default "Help" item in Help menu
+        root.createcommand('::tk::mac::ShowHelp', help_dialog)
+        # remove redundant "IDLE Help" from menu
+        del mainmenu.menudefs[-1][1][0]
+
+def fixb2context(root):
+    '''Removed bad AquaTk Button-2 (right) and Paste bindings.
+
+    They prevent context menu access and seem to be gone in AquaTk8.6.
+    See issue #24801.
+    '''
+    root.unbind_class('Text', '<B2>')
+    root.unbind_class('Text', '<B2-Motion>')
+    root.unbind_class('Text', '<<PasteSelection>>')
+
+def setupApp(root, flist):
+    """
+    Perform initial OS X customizations if needed.
+    Called from pyshell.main() after initial calls to Tk()
+
+    There are currently three major versions of Tk in use on OS X:
+        1. Aqua Cocoa Tk (native default since OS X 10.6)
+        2. Aqua Carbon Tk (original native, 32-bit only, deprecated)
+        3. X11 (supported by some third-party distributors, deprecated)
+    There are various differences among the three that affect IDLE
+    behavior, primarily with menus, mouse key events, and accelerators.
+    Some one-time customizations are performed here.
+    Others are dynamically tested throughout idlelib by calls to the
+    isAquaTk(), isCarbonTk(), isCocoaTk(), isXQuartz() functions which
+    are initialized here as well.
+    """
+    if isAquaTk():
+        hideTkConsole(root)
+        overrideRootMenu(root, flist)
+        addOpenEventSupport(root, flist)
+        fixb2context(root)
+
+
+if __name__ == '__main__':
+    from unittest import main
+    main('idlelib.idle_test.test_macosx', verbosity=2)
diff --git a/rootfs/usr/lib/python3.8/idlelib/mainmenu.py b/rootfs/usr/lib/python3.8/idlelib/mainmenu.py
new file mode 100644
index 0000000..74edce2
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/mainmenu.py
@@ -0,0 +1,125 @@
+"""Define the menu contents, hotkeys, and event bindings.
+
+There is additional configuration information in the EditorWindow class (and
+subclasses): the menus are created there based on the menu_specs (class)
+variable, and menus not created are silently skipped in the code here.  This
+makes it possible, for example, to define a Debug menu which is only present in
+the PythonShell window, and a Format menu which is only present in the Editor
+windows.
+
+"""
+from importlib.util import find_spec
+
+from idlelib.config import idleConf
+
+#   Warning: menudefs is altered in macosx.overrideRootMenu()
+#   after it is determined that an OS X Aqua Tk is in use,
+#   which cannot be done until after Tk() is first called.
+#   Do not alter the 'file', 'options', or 'help' cascades here
+#   without altering overrideRootMenu() as well.
+#       TODO: Make this more robust
+
+menudefs = [
+ # underscore prefixes character to underscore
+ ('file', [
+   ('_New File', '<<open-new-window>>'),
+   ('_Open...', '<<open-window-from-file>>'),
+   ('Open _Module...', '<<open-module>>'),
+   ('Module _Browser', '<<open-class-browser>>'),
+   ('_Path Browser', '<<open-path-browser>>'),
+   None,
+   ('_Save', '<<save-window>>'),
+   ('Save _As...', '<<save-window-as-file>>'),
+   ('Save Cop_y As...', '<<save-copy-of-window-as-file>>'),
+   None,
+   ('Prin_t Window', '<<print-window>>'),
+   None,
+   ('_Close', '<<close-window>>'),
+   ('E_xit', '<<close-all-windows>>'),
+   ]),
+
+ ('edit', [
+   ('_Undo', '<<undo>>'),
+   ('_Redo', '<<redo>>'),
+   None,
+   ('Cu_t', '<<cut>>'),
+   ('_Copy', '<<copy>>'),
+   ('_Paste', '<<paste>>'),
+   ('Select _All', '<<select-all>>'),
+   None,
+   ('_Find...', '<<find>>'),
+   ('Find A_gain', '<<find-again>>'),
+   ('Find _Selection', '<<find-selection>>'),
+   ('Find in Files...', '<<find-in-files>>'),
+   ('R_eplace...', '<<replace>>'),
+   ('Go to _Line', '<<goto-line>>'),
+   ('S_how Completions', '<<force-open-completions>>'),
+   ('E_xpand Word', '<<expand-word>>'),
+   ('Show C_all Tip', '<<force-open-calltip>>'),
+   ('Show Surrounding P_arens', '<<flash-paren>>'),
+   ]),
+
+ ('format', [
+   ('F_ormat Paragraph', '<<format-paragraph>>'),
+   ('_Indent Region', '<<indent-region>>'),
+   ('_Dedent Region', '<<dedent-region>>'),
+   ('Comment _Out Region', '<<comment-region>>'),
+   ('U_ncomment Region', '<<uncomment-region>>'),
+   ('Tabify Region', '<<tabify-region>>'),
+   ('Untabify Region', '<<untabify-region>>'),
+   ('Toggle Tabs', '<<toggle-tabs>>'),
+   ('New Indent Width', '<<change-indentwidth>>'),
+   ('S_trip Trailing Whitespace', '<<do-rstrip>>'),
+   ]),
+
+ ('run', [
+   ('R_un Module', '<<run-module>>'),
+   ('Run... _Customized', '<<run-custom>>'),
+   ('C_heck Module', '<<check-module>>'),
+   ('Python Shell', '<<open-python-shell>>'),
+   ]),
+
+ ('shell', [
+   ('_View Last Restart', '<<view-restart>>'),
+   ('_Restart Shell', '<<restart-shell>>'),
+   None,
+   ('_Previous History', '<<history-previous>>'),
+   ('_Next History', '<<history-next>>'),
+   None,
+   ('_Interrupt Execution', '<<interrupt-execution>>'),
+   ]),
+
+ ('debug', [
+   ('_Go to File/Line', '<<goto-file-line>>'),
+   ('!_Debugger', '<<toggle-debugger>>'),
+   ('_Stack Viewer', '<<open-stack-viewer>>'),
+   ('!_Auto-open Stack Viewer', '<<toggle-jit-stack-viewer>>'),
+   ]),
+
+ ('options', [
+   ('Configure _IDLE', '<<open-config-dialog>>'),
+   None,
+   ('Show _Code Context', '<<toggle-code-context>>'),
+   ('Show _Line Numbers', '<<toggle-line-numbers>>'),
+   ('_Zoom Height', '<<zoom-height>>'),
+   ]),
+
+ ('window', [
+   ]),
+
+ ('help', [
+   ('_About IDLE', '<<about-idle>>'),
+   None,
+   ('_IDLE Help', '<<help>>'),
+   ('Python _Docs', '<<python-docs>>'),
+   ]),
+]
+
+if find_spec('turtledemo'):
+    menudefs[-1][1].append(('Turtle Demo', '<<open-turtle-demo>>'))
+
+default_keydefs = idleConf.GetCurrentKeySet()
+
+if __name__ == '__main__':
+    from unittest import main
+    main('idlelib.idle_test.test_mainmenu', verbosity=2)
diff --git a/rootfs/usr/lib/python3.8/idlelib/multicall.py b/rootfs/usr/lib/python3.8/idlelib/multicall.py
new file mode 100644
index 0000000..dc02001
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/multicall.py
@@ -0,0 +1,448 @@
+"""
+MultiCall - a class which inherits its methods from a Tkinter widget (Text, for
+example), but enables multiple calls of functions per virtual event - all
+matching events will be called, not only the most specific one. This is done
+by wrapping the event functions - event_add, event_delete and event_info.
+MultiCall recognizes only a subset of legal event sequences. Sequences which
+are not recognized are treated by the original Tk handling mechanism. A
+more-specific event will be called before a less-specific event.
+
+The recognized sequences are complete one-event sequences (no emacs-style
+Ctrl-X Ctrl-C, no shortcuts like <3>), for all types of events.
+Key/Button Press/Release events can have modifiers.
+The recognized modifiers are Shift, Control, Option and Command for Mac, and
+Control, Alt, Shift, Meta/M for other platforms.
+
+For all events which were handled by MultiCall, a new member is added to the
+event instance passed to the binded functions - mc_type. This is one of the
+event type constants defined in this module (such as MC_KEYPRESS).
+For Key/Button events (which are handled by MultiCall and may receive
+modifiers), another member is added - mc_state. This member gives the state
+of the recognized modifiers, as a combination of the modifier constants
+also defined in this module (for example, MC_SHIFT).
+Using these members is absolutely portable.
+
+The order by which events are called is defined by these rules:
+1. A more-specific event will be called before a less-specific event.
+2. A recently-binded event will be called before a previously-binded event,
+   unless this conflicts with the first rule.
+Each function will be called at most once for each event.
+"""
+import re
+import sys
+
+import tkinter
+
+# the event type constants, which define the meaning of mc_type
+MC_KEYPRESS=0; MC_KEYRELEASE=1; MC_BUTTONPRESS=2; MC_BUTTONRELEASE=3;
+MC_ACTIVATE=4; MC_CIRCULATE=5; MC_COLORMAP=6; MC_CONFIGURE=7;
+MC_DEACTIVATE=8; MC_DESTROY=9; MC_ENTER=10; MC_EXPOSE=11; MC_FOCUSIN=12;
+MC_FOCUSOUT=13; MC_GRAVITY=14; MC_LEAVE=15; MC_MAP=16; MC_MOTION=17;
+MC_MOUSEWHEEL=18; MC_PROPERTY=19; MC_REPARENT=20; MC_UNMAP=21; MC_VISIBILITY=22;
+# the modifier state constants, which define the meaning of mc_state
+MC_SHIFT = 1<<0; MC_CONTROL = 1<<2; MC_ALT = 1<<3; MC_META = 1<<5
+MC_OPTION = 1<<6; MC_COMMAND = 1<<7
+
+# define the list of modifiers, to be used in complex event types.
+if sys.platform == "darwin":
+    _modifiers = (("Shift",), ("Control",), ("Option",), ("Command",))
+    _modifier_masks = (MC_SHIFT, MC_CONTROL, MC_OPTION, MC_COMMAND)
+else:
+    _modifiers = (("Control",), ("Alt",), ("Shift",), ("Meta", "M"))
+    _modifier_masks = (MC_CONTROL, MC_ALT, MC_SHIFT, MC_META)
+
+# a dictionary to map a modifier name into its number
+_modifier_names = dict([(name, number)
+                         for number in range(len(_modifiers))
+                         for name in _modifiers[number]])
+
+# In 3.4, if no shell window is ever open, the underlying Tk widget is
+# destroyed before .__del__ methods here are called.  The following
+# is used to selectively ignore shutdown exceptions to avoid
+# 'Exception ignored' messages.  See http://bugs.python.org/issue20167
+APPLICATION_GONE = "application has been destroyed"
+
+# A binder is a class which binds functions to one type of event. It has two
+# methods: bind and unbind, which get a function and a parsed sequence, as
+# returned by _parse_sequence(). There are two types of binders:
+# _SimpleBinder handles event types with no modifiers and no detail.
+# No Python functions are called when no events are binded.
+# _ComplexBinder handles event types with modifiers and a detail.
+# A Python function is called each time an event is generated.
+
+class _SimpleBinder:
+    def __init__(self, type, widget, widgetinst):
+        self.type = type
+        self.sequence = '<'+_types[type][0]+'>'
+        self.widget = widget
+        self.widgetinst = widgetinst
+        self.bindedfuncs = []
+        self.handlerid = None
+
+    def bind(self, triplet, func):
+        if not self.handlerid:
+            def handler(event, l = self.bindedfuncs, mc_type = self.type):
+                event.mc_type = mc_type
+                wascalled = {}
+                for i in range(len(l)-1, -1, -1):
+                    func = l[i]
+                    if func not in wascalled:
+                        wascalled[func] = True
+                        r = func(event)
+                        if r:
+                            return r
+            self.handlerid = self.widget.bind(self.widgetinst,
+                                              self.sequence, handler)
+        self.bindedfuncs.append(func)
+
+    def unbind(self, triplet, func):
+        self.bindedfuncs.remove(func)
+        if not self.bindedfuncs:
+            self.widget.unbind(self.widgetinst, self.sequence, self.handlerid)
+            self.handlerid = None
+
+    def __del__(self):
+        if self.handlerid:
+            try:
+                self.widget.unbind(self.widgetinst, self.sequence,
+                        self.handlerid)
+            except tkinter.TclError as e:
+                if not APPLICATION_GONE in e.args[0]:
+                    raise
+
+# An int in range(1 << len(_modifiers)) represents a combination of modifiers
+# (if the least significant bit is on, _modifiers[0] is on, and so on).
+# _state_subsets gives for each combination of modifiers, or *state*,
+# a list of the states which are a subset of it. This list is ordered by the
+# number of modifiers is the state - the most specific state comes first.
+_states = range(1 << len(_modifiers))
+_state_names = [''.join(m[0]+'-'
+                        for i, m in enumerate(_modifiers)
+                        if (1 << i) & s)
+                for s in _states]
+
+def expand_substates(states):
+    '''For each item of states return a list containing all combinations of
+    that item with individual bits reset, sorted by the number of set bits.
+    '''
+    def nbits(n):
+        "number of bits set in n base 2"
+        nb = 0
+        while n:
+            n, rem = divmod(n, 2)
+            nb += rem
+        return nb
+    statelist = []
+    for state in states:
+        substates = list(set(state & x for x in states))
+        substates.sort(key=nbits, reverse=True)
+        statelist.append(substates)
+    return statelist
+
+_state_subsets = expand_substates(_states)
+
+# _state_codes gives for each state, the portable code to be passed as mc_state
+_state_codes = []
+for s in _states:
+    r = 0
+    for i in range(len(_modifiers)):
+        if (1 << i) & s:
+            r |= _modifier_masks[i]
+    _state_codes.append(r)
+
+class _ComplexBinder:
+    # This class binds many functions, and only unbinds them when it is deleted.
+    # self.handlerids is the list of seqs and ids of binded handler functions.
+    # The binded functions sit in a dictionary of lists of lists, which maps
+    # a detail (or None) and a state into a list of functions.
+    # When a new detail is discovered, handlers for all the possible states
+    # are binded.
+
+    def __create_handler(self, lists, mc_type, mc_state):
+        def handler(event, lists = lists,
+                    mc_type = mc_type, mc_state = mc_state,
+                    ishandlerrunning = self.ishandlerrunning,
+                    doafterhandler = self.doafterhandler):
+            ishandlerrunning[:] = [True]
+            event.mc_type = mc_type
+            event.mc_state = mc_state
+            wascalled = {}
+            r = None
+            for l in lists:
+                for i in range(len(l)-1, -1, -1):
+                    func = l[i]
+                    if func not in wascalled:
+                        wascalled[func] = True
+                        r = l[i](event)
+                        if r:
+                            break
+                if r:
+                    break
+            ishandlerrunning[:] = []
+            # Call all functions in doafterhandler and remove them from list
+            for f in doafterhandler:
+                f()
+            doafterhandler[:] = []
+            if r:
+                return r
+        return handler
+
+    def __init__(self, type, widget, widgetinst):
+        self.type = type
+        self.typename = _types[type][0]
+        self.widget = widget
+        self.widgetinst = widgetinst
+        self.bindedfuncs = {None: [[] for s in _states]}
+        self.handlerids = []
+        # we don't want to change the lists of functions while a handler is
+        # running - it will mess up the loop and anyway, we usually want the
+        # change to happen from the next event. So we have a list of functions
+        # for the handler to run after it finishes calling the binded functions.
+        # It calls them only once.
+        # ishandlerrunning is a list. An empty one means no, otherwise - yes.
+        # this is done so that it would be mutable.
+        self.ishandlerrunning = []
+        self.doafterhandler = []
+        for s in _states:
+            lists = [self.bindedfuncs[None][i] for i in _state_subsets[s]]
+            handler = self.__create_handler(lists, type, _state_codes[s])
+            seq = '<'+_state_names[s]+self.typename+'>'
+            self.handlerids.append((seq, self.widget.bind(self.widgetinst,
+                                                          seq, handler)))
+
+    def bind(self, triplet, func):
+        if triplet[2] not in self.bindedfuncs:
+            self.bindedfuncs[triplet[2]] = [[] for s in _states]
+            for s in _states:
+                lists = [ self.bindedfuncs[detail][i]
+                          for detail in (triplet[2], None)
+                          for i in _state_subsets[s]       ]
+                handler = self.__create_handler(lists, self.type,
+                                                _state_codes[s])
+                seq = "<%s%s-%s>"% (_state_names[s], self.typename, triplet[2])
+                self.handlerids.append((seq, self.widget.bind(self.widgetinst,
+                                                              seq, handler)))
+        doit = lambda: self.bindedfuncs[triplet[2]][triplet[0]].append(func)
+        if not self.ishandlerrunning:
+            doit()
+        else:
+            self.doafterhandler.append(doit)
+
+    def unbind(self, triplet, func):
+        doit = lambda: self.bindedfuncs[triplet[2]][triplet[0]].remove(func)
+        if not self.ishandlerrunning:
+            doit()
+        else:
+            self.doafterhandler.append(doit)
+
+    def __del__(self):
+        for seq, id in self.handlerids:
+            try:
+                self.widget.unbind(self.widgetinst, seq, id)
+            except tkinter.TclError as e:
+                if not APPLICATION_GONE in e.args[0]:
+                    raise
+
+# define the list of event types to be handled by MultiEvent. the order is
+# compatible with the definition of event type constants.
+_types = (
+    ("KeyPress", "Key"), ("KeyRelease",), ("ButtonPress", "Button"),
+    ("ButtonRelease",), ("Activate",), ("Circulate",), ("Colormap",),
+    ("Configure",), ("Deactivate",), ("Destroy",), ("Enter",), ("Expose",),
+    ("FocusIn",), ("FocusOut",), ("Gravity",), ("Leave",), ("Map",),
+    ("Motion",), ("MouseWheel",), ("Property",), ("Reparent",), ("Unmap",),
+    ("Visibility",),
+)
+
+# which binder should be used for every event type?
+_binder_classes = (_ComplexBinder,) * 4 + (_SimpleBinder,) * (len(_types)-4)
+
+# A dictionary to map a type name into its number
+_type_names = dict([(name, number)
+                     for number in range(len(_types))
+                     for name in _types[number]])
+
+_keysym_re = re.compile(r"^\w+$")
+_button_re = re.compile(r"^[1-5]$")
+def _parse_sequence(sequence):
+    """Get a string which should describe an event sequence. If it is
+    successfully parsed as one, return a tuple containing the state (as an int),
+    the event type (as an index of _types), and the detail - None if none, or a
+    string if there is one. If the parsing is unsuccessful, return None.
+    """
+    if not sequence or sequence[0] != '<' or sequence[-1] != '>':
+        return None
+    words = sequence[1:-1].split('-')
+    modifiers = 0
+    while words and words[0] in _modifier_names:
+        modifiers |= 1 << _modifier_names[words[0]]
+        del words[0]
+    if words and words[0] in _type_names:
+        type = _type_names[words[0]]
+        del words[0]
+    else:
+        return None
+    if _binder_classes[type] is _SimpleBinder:
+        if modifiers or words:
+            return None
+        else:
+            detail = None
+    else:
+        # _ComplexBinder
+        if type in [_type_names[s] for s in ("KeyPress", "KeyRelease")]:
+            type_re = _keysym_re
+        else:
+            type_re = _button_re
+
+        if not words:
+            detail = None
+        elif len(words) == 1 and type_re.match(words[0]):
+            detail = words[0]
+        else:
+            return None
+
+    return modifiers, type, detail
+
+def _triplet_to_sequence(triplet):
+    if triplet[2]:
+        return '<'+_state_names[triplet[0]]+_types[triplet[1]][0]+'-'+ \
+               triplet[2]+'>'
+    else:
+        return '<'+_state_names[triplet[0]]+_types[triplet[1]][0]+'>'
+
+_multicall_dict = {}
+def MultiCallCreator(widget):
+    """Return a MultiCall class which inherits its methods from the
+    given widget class (for example, Tkinter.Text). This is used
+    instead of a templating mechanism.
+    """
+    if widget in _multicall_dict:
+        return _multicall_dict[widget]
+
+    class MultiCall (widget):
+        assert issubclass(widget, tkinter.Misc)
+
+        def __init__(self, *args, **kwargs):
+            widget.__init__(self, *args, **kwargs)
+            # a dictionary which maps a virtual event to a tuple with:
+            #  0. the function binded
+            #  1. a list of triplets - the sequences it is binded to
+            self.__eventinfo = {}
+            self.__binders = [_binder_classes[i](i, widget, self)
+                              for i in range(len(_types))]
+
+        def bind(self, sequence=None, func=None, add=None):
+            #print("bind(%s, %s, %s)" % (sequence, func, add),
+            #      file=sys.__stderr__)
+            if type(sequence) is str and len(sequence) > 2 and \
+               sequence[:2] == "<<" and sequence[-2:] == ">>":
+                if sequence in self.__eventinfo:
+                    ei = self.__eventinfo[sequence]
+                    if ei[0] is not None:
+                        for triplet in ei[1]:
+                            self.__binders[triplet[1]].unbind(triplet, ei[0])
+                    ei[0] = func
+                    if ei[0] is not None:
+                        for triplet in ei[1]:
+                            self.__binders[triplet[1]].bind(triplet, func)
+                else:
+                    self.__eventinfo[sequence] = [func, []]
+            return widget.bind(self, sequence, func, add)
+
+        def unbind(self, sequence, funcid=None):
+            if type(sequence) is str and len(sequence) > 2 and \
+               sequence[:2] == "<<" and sequence[-2:] == ">>" and \
+               sequence in self.__eventinfo:
+                func, triplets = self.__eventinfo[sequence]
+                if func is not None:
+                    for triplet in triplets:
+                        self.__binders[triplet[1]].unbind(triplet, func)
+                    self.__eventinfo[sequence][0] = None
+            return widget.unbind(self, sequence, funcid)
+
+        def event_add(self, virtual, *sequences):
+            #print("event_add(%s, %s)" % (repr(virtual), repr(sequences)),
+            #      file=sys.__stderr__)
+            if virtual not in self.__eventinfo:
+                self.__eventinfo[virtual] = [None, []]
+
+            func, triplets = self.__eventinfo[virtual]
+            for seq in sequences:
+                triplet = _parse_sequence(seq)
+                if triplet is None:
+                    #print("Tkinter event_add(%s)" % seq, file=sys.__stderr__)
+                    widget.event_add(self, virtual, seq)
+                else:
+                    if func is not None:
+                        self.__binders[triplet[1]].bind(triplet, func)
+                    triplets.append(triplet)
+
+        def event_delete(self, virtual, *sequences):
+            if virtual not in self.__eventinfo:
+                return
+            func, triplets = self.__eventinfo[virtual]
+            for seq in sequences:
+                triplet = _parse_sequence(seq)
+                if triplet is None:
+                    #print("Tkinter event_delete: %s" % seq, file=sys.__stderr__)
+                    widget.event_delete(self, virtual, seq)
+                else:
+                    if func is not None:
+                        self.__binders[triplet[1]].unbind(triplet, func)
+                    triplets.remove(triplet)
+
+        def event_info(self, virtual=None):
+            if virtual is None or virtual not in self.__eventinfo:
+                return widget.event_info(self, virtual)
+            else:
+                return tuple(map(_triplet_to_sequence,
+                                 self.__eventinfo[virtual][1])) + \
+                       widget.event_info(self, virtual)
+
+        def __del__(self):
+            for virtual in self.__eventinfo:
+                func, triplets = self.__eventinfo[virtual]
+                if func:
+                    for triplet in triplets:
+                        try:
+                            self.__binders[triplet[1]].unbind(triplet, func)
+                        except tkinter.TclError as e:
+                            if not APPLICATION_GONE in e.args[0]:
+                                raise
+
+    _multicall_dict[widget] = MultiCall
+    return MultiCall
+
+
+def _multi_call(parent):  # htest #
+    top = tkinter.Toplevel(parent)
+    top.title("Test MultiCall")
+    x, y = map(int, parent.geometry().split('+')[1:])
+    top.geometry("+%d+%d" % (x, y + 175))
+    text = MultiCallCreator(tkinter.Text)(top)
+    text.pack()
+    def bindseq(seq, n=[0]):
+        def handler(event):
+            print(seq)
+        text.bind("<<handler%d>>"%n[0], handler)
+        text.event_add("<<handler%d>>"%n[0], seq)
+        n[0] += 1
+    bindseq("<Key>")
+    bindseq("<Control-Key>")
+    bindseq("<Alt-Key-a>")
+    bindseq("<Control-Key-a>")
+    bindseq("<Alt-Control-Key-a>")
+    bindseq("<Key-b>")
+    bindseq("<Control-Button-1>")
+    bindseq("<Button-2>")
+    bindseq("<Alt-Button-1>")
+    bindseq("<FocusOut>")
+    bindseq("<Enter>")
+    bindseq("<Leave>")
+
+if __name__ == "__main__":
+    from unittest import main
+    main('idlelib.idle_test.test_mainmenu', verbosity=2, exit=False)
+
+    from idlelib.idle_test.htest import run
+    run(_multi_call)
diff --git a/rootfs/usr/lib/python3.8/idlelib/outwin.py b/rootfs/usr/lib/python3.8/idlelib/outwin.py
new file mode 100644
index 0000000..5ab08bb
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/outwin.py
@@ -0,0 +1,187 @@
+"""Editor window that can serve as an output file.
+"""
+
+import re
+
+from tkinter import messagebox
+
+from idlelib.editor import EditorWindow
+
+
+file_line_pats = [
+    # order of patterns matters
+    r'file "([^"]*)", line (\d+)',
+    r'([^\s]+)\((\d+)\)',
+    r'^(\s*\S.*?):\s*(\d+):',  # Win filename, maybe starting with spaces
+    r'([^\s]+):\s*(\d+):',     # filename or path, ltrim
+    r'^\s*(\S.*?):\s*(\d+):',  # Win abs path with embedded spaces, ltrim
+]
+
+file_line_progs = None
+
+
+def compile_progs():
+    "Compile the patterns for matching to file name and line number."
+    global file_line_progs
+    file_line_progs = [re.compile(pat, re.IGNORECASE)
+                       for pat in file_line_pats]
+
+
+def file_line_helper(line):
+    """Extract file name and line number from line of text.
+
+    Check if line of text contains one of the file/line patterns.
+    If it does and if the file and line are valid, return
+    a tuple of the file name and line number.  If it doesn't match
+    or if the file or line is invalid, return None.
+    """
+    if not file_line_progs:
+        compile_progs()
+    for prog in file_line_progs:
+        match = prog.search(line)
+        if match:
+            filename, lineno = match.group(1, 2)
+            try:
+                f = open(filename, "r")
+                f.close()
+                break
+            except OSError:
+                continue
+    else:
+        return None
+    try:
+        return filename, int(lineno)
+    except TypeError:
+        return None
+
+
+class OutputWindow(EditorWindow):
+    """An editor window that can serve as an output file.
+
+    Also the future base class for the Python shell window.
+    This class has no input facilities.
+
+    Adds binding to open a file at a line to the text widget.
+    """
+
+    # Our own right-button menu
+    rmenu_specs = [
+        ("Cut", "<<cut>>", "rmenu_check_cut"),
+        ("Copy", "<<copy>>", "rmenu_check_copy"),
+        ("Paste", "<<paste>>", "rmenu_check_paste"),
+        (None, None, None),
+        ("Go to file/line", "<<goto-file-line>>", None),
+    ]
+
+    allow_code_context = False
+
+    def __init__(self, *args):
+        EditorWindow.__init__(self, *args)
+        self.text.bind("<<goto-file-line>>", self.goto_file_line)
+
+    # Customize EditorWindow
+    def ispythonsource(self, filename):
+        "Python source is only part of output: do not colorize."
+        return False
+
+    def short_title(self):
+        "Customize EditorWindow title."
+        return "Output"
+
+    def maybesave(self):
+        "Customize EditorWindow to not display save file messagebox."
+        return 'yes' if self.get_saved() else 'no'
+
+    # Act as output file
+    def write(self, s, tags=(), mark="insert"):
+        """Write text to text widget.
+
+        The text is inserted at the given index with the provided
+        tags.  The text widget is then scrolled to make it visible
+        and updated to display it, giving the effect of seeing each
+        line as it is added.
+
+        Args:
+            s: Text to insert into text widget.
+            tags: Tuple of tag strings to apply on the insert.
+            mark: Index for the insert.
+
+        Return:
+            Length of text inserted.
+        """
+        assert isinstance(s, str)
+        self.text.insert(mark, s, tags)
+        self.text.see(mark)
+        self.text.update()
+        return len(s)
+
+    def writelines(self, lines):
+        "Write each item in lines iterable."
+        for line in lines:
+            self.write(line)
+
+    def flush(self):
+        "No flushing needed as write() directly writes to widget."
+        pass
+
+    def showerror(self, *args, **kwargs):
+        messagebox.showerror(*args, **kwargs)
+
+    def goto_file_line(self, event=None):
+        """Handle request to open file/line.
+
+        If the selected or previous line in the output window
+        contains a file name and line number, then open that file
+        name in a new window and position on the line number.
+
+        Otherwise, display an error messagebox.
+        """
+        line = self.text.get("insert linestart", "insert lineend")
+        result = file_line_helper(line)
+        if not result:
+            # Try the previous line.  This is handy e.g. in tracebacks,
+            # where you tend to right-click on the displayed source line
+            line = self.text.get("insert -1line linestart",
+                                 "insert -1line lineend")
+            result = file_line_helper(line)
+            if not result:
+                self.showerror(
+                    "No special line",
+                    "The line you point at doesn't look like "
+                    "a valid file name followed by a line number.",
+                    parent=self.text)
+                return
+        filename, lineno = result
+        self.flist.gotofileline(filename, lineno)
+
+
+# These classes are currently not used but might come in handy
+class OnDemandOutputWindow:
+
+    tagdefs = {
+        # XXX Should use IdlePrefs.ColorPrefs
+        "stdout":  {"foreground": "blue"},
+        "stderr":  {"foreground": "#007700"},
+    }
+
+    def __init__(self, flist):
+        self.flist = flist
+        self.owin = None
+
+    def write(self, s, tags, mark):
+        if not self.owin:
+            self.setup()
+        self.owin.write(s, tags, mark)
+
+    def setup(self):
+        self.owin = owin = OutputWindow(self.flist)
+        text = owin.text
+        for tag, cnf in self.tagdefs.items():
+            if cnf:
+                text.tag_configure(tag, **cnf)
+        text.tag_raise('sel')
+        self.write = self.owin.write
+
+if __name__ == '__main__':
+    from unittest import main
+    main('idlelib.idle_test.test_outwin', verbosity=2, exit=False)
diff --git a/rootfs/usr/lib/python3.8/idlelib/parenmatch.py b/rootfs/usr/lib/python3.8/idlelib/parenmatch.py
new file mode 100644
index 0000000..3fd7aad
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/parenmatch.py
@@ -0,0 +1,183 @@
+"""ParenMatch -- for parenthesis matching.
+
+When you hit a right paren, the cursor should move briefly to the left
+paren.  Paren here is used generically; the matching applies to
+parentheses, square brackets, and curly braces.
+"""
+from idlelib.hyperparser import HyperParser
+from idlelib.config import idleConf
+
+_openers = {')':'(',']':'[','}':'{'}
+CHECK_DELAY = 100 # milliseconds
+
+class ParenMatch:
+    """Highlight matching openers and closers, (), [], and {}.
+
+    There are three supported styles of paren matching.  When a right
+    paren (opener) is typed:
+
+    opener -- highlight the matching left paren (closer);
+    parens -- highlight the left and right parens (opener and closer);
+    expression -- highlight the entire expression from opener to closer.
+    (For back compatibility, 'default' is a synonym for 'opener').
+
+    Flash-delay is the maximum milliseconds the highlighting remains.
+    Any cursor movement (key press or click) before that removes the
+    highlight.  If flash-delay is 0, there is no maximum.
+
+    TODO:
+    - Augment bell() with mismatch warning in status window.
+    - Highlight when cursor is moved to the right of a closer.
+      This might be too expensive to check.
+    """
+
+    RESTORE_VIRTUAL_EVENT_NAME = "<<parenmatch-check-restore>>"
+    # We want the restore event be called before the usual return and
+    # backspace events.
+    RESTORE_SEQUENCES = ("<KeyPress>", "<ButtonPress>",
+                         "<Key-Return>", "<Key-BackSpace>")
+
+    def __init__(self, editwin):
+        self.editwin = editwin
+        self.text = editwin.text
+        # Bind the check-restore event to the function restore_event,
+        # so that we can then use activate_restore (which calls event_add)
+        # and deactivate_restore (which calls event_delete).
+        editwin.text.bind(self.RESTORE_VIRTUAL_EVENT_NAME,
+                          self.restore_event)
+        self.counter = 0
+        self.is_restore_active = 0
+
+    @classmethod
+    def reload(cls):
+        cls.STYLE = idleConf.GetOption(
+            'extensions','ParenMatch','style', default='opener')
+        cls.FLASH_DELAY = idleConf.GetOption(
+                'extensions','ParenMatch','flash-delay', type='int',default=500)
+        cls.BELL = idleConf.GetOption(
+                'extensions','ParenMatch','bell', type='bool', default=1)
+        cls.HILITE_CONFIG = idleConf.GetHighlight(idleConf.CurrentTheme(),
+                                                  'hilite')
+
+    def activate_restore(self):
+        "Activate mechanism to restore text from highlighting."
+        if not self.is_restore_active:
+            for seq in self.RESTORE_SEQUENCES:
+                self.text.event_add(self.RESTORE_VIRTUAL_EVENT_NAME, seq)
+            self.is_restore_active = True
+
+    def deactivate_restore(self):
+        "Remove restore event bindings."
+        if self.is_restore_active:
+            for seq in self.RESTORE_SEQUENCES:
+                self.text.event_delete(self.RESTORE_VIRTUAL_EVENT_NAME, seq)
+            self.is_restore_active = False
+
+    def flash_paren_event(self, event):
+        "Handle editor 'show surrounding parens' event (menu or shortcut)."
+        indices = (HyperParser(self.editwin, "insert")
+                   .get_surrounding_brackets())
+        self.finish_paren_event(indices)
+        return "break"
+
+    def paren_closed_event(self, event):
+        "Handle user input of closer."
+        # If user bound non-closer to <<paren-closed>>, quit.
+        closer = self.text.get("insert-1c")
+        if closer not in _openers:
+            return
+        hp = HyperParser(self.editwin, "insert-1c")
+        if not hp.is_in_code():
+            return
+        indices = hp.get_surrounding_brackets(_openers[closer], True)
+        self.finish_paren_event(indices)
+        return  # Allow calltips to see ')'
+
+    def finish_paren_event(self, indices):
+        if indices is None and self.BELL:
+            self.text.bell()
+            return
+        self.activate_restore()
+        # self.create_tag(indices)
+        self.tagfuncs.get(self.STYLE, self.create_tag_expression)(self, indices)
+        # self.set_timeout()
+        (self.set_timeout_last if self.FLASH_DELAY else
+                            self.set_timeout_none)()
+
+    def restore_event(self, event=None):
+        "Remove effect of doing match."
+        self.text.tag_delete("paren")
+        self.deactivate_restore()
+        self.counter += 1   # disable the last timer, if there is one.
+
+    def handle_restore_timer(self, timer_count):
+        if timer_count == self.counter:
+            self.restore_event()
+
+    # any one of the create_tag_XXX methods can be used depending on
+    # the style
+
+    def create_tag_opener(self, indices):
+        """Highlight the single paren that matches"""
+        self.text.tag_add("paren", indices[0])
+        self.text.tag_config("paren", self.HILITE_CONFIG)
+
+    def create_tag_parens(self, indices):
+        """Highlight the left and right parens"""
+        if self.text.get(indices[1]) in (')', ']', '}'):
+            rightindex = indices[1]+"+1c"
+        else:
+            rightindex = indices[1]
+        self.text.tag_add("paren", indices[0], indices[0]+"+1c", rightindex+"-1c", rightindex)
+        self.text.tag_config("paren", self.HILITE_CONFIG)
+
+    def create_tag_expression(self, indices):
+        """Highlight the entire expression"""
+        if self.text.get(indices[1]) in (')', ']', '}'):
+            rightindex = indices[1]+"+1c"
+        else:
+            rightindex = indices[1]
+        self.text.tag_add("paren", indices[0], rightindex)
+        self.text.tag_config("paren", self.HILITE_CONFIG)
+
+    tagfuncs = {
+        'opener': create_tag_opener,
+        'default': create_tag_opener,
+        'parens': create_tag_parens,
+        'expression': create_tag_expression,
+        }
+
+    # any one of the set_timeout_XXX methods can be used depending on
+    # the style
+
+    def set_timeout_none(self):
+        """Highlight will remain until user input turns it off
+        or the insert has moved"""
+        # After CHECK_DELAY, call a function which disables the "paren" tag
+        # if the event is for the most recent timer and the insert has changed,
+        # or schedules another call for itself.
+        self.counter += 1
+        def callme(callme, self=self, c=self.counter,
+                   index=self.text.index("insert")):
+            if index != self.text.index("insert"):
+                self.handle_restore_timer(c)
+            else:
+                self.editwin.text_frame.after(CHECK_DELAY, callme, callme)
+        self.editwin.text_frame.after(CHECK_DELAY, callme, callme)
+
+    def set_timeout_last(self):
+        """The last highlight created will be removed after FLASH_DELAY millisecs"""
+        # associate a counter with an event; only disable the "paren"
+        # tag if the event is for the most recent timer.
+        self.counter += 1
+        self.editwin.text_frame.after(
+            self.FLASH_DELAY,
+            lambda self=self, c=self.counter: self.handle_restore_timer(c))
+
+
+ParenMatch.reload()
+
+
+if __name__ == '__main__':
+    from unittest import main
+    main('idlelib.idle_test.test_parenmatch', verbosity=2)
diff --git a/rootfs/usr/lib/python3.8/idlelib/pathbrowser.py b/rootfs/usr/lib/python3.8/idlelib/pathbrowser.py
new file mode 100644
index 0000000..6de242d
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/pathbrowser.py
@@ -0,0 +1,111 @@
+import importlib.machinery
+import os
+import sys
+
+from idlelib.browser import ModuleBrowser, ModuleBrowserTreeItem
+from idlelib.tree import TreeItem
+
+
+class PathBrowser(ModuleBrowser):
+
+    def __init__(self, master, *, _htest=False, _utest=False):
+        """
+        _htest - bool, change box location when running htest
+        """
+        self.master = master
+        self._htest = _htest
+        self._utest = _utest
+        self.init()
+
+    def settitle(self):
+        "Set window titles."
+        self.top.wm_title("Path Browser")
+        self.top.wm_iconname("Path Browser")
+
+    def rootnode(self):
+        return PathBrowserTreeItem()
+
+
+class PathBrowserTreeItem(TreeItem):
+
+    def GetText(self):
+        return "sys.path"
+
+    def GetSubList(self):
+        sublist = []
+        for dir in sys.path:
+            item = DirBrowserTreeItem(dir)
+            sublist.append(item)
+        return sublist
+
+
+class DirBrowserTreeItem(TreeItem):
+
+    def __init__(self, dir, packages=[]):
+        self.dir = dir
+        self.packages = packages
+
+    def GetText(self):
+        if not self.packages:
+            return self.dir
+        else:
+            return self.packages[-1] + ": package"
+
+    def GetSubList(self):
+        try:
+            names = os.listdir(self.dir or os.curdir)
+        except OSError:
+            return []
+        packages = []
+        for name in names:
+            file = os.path.join(self.dir, name)
+            if self.ispackagedir(file):
+                nn = os.path.normcase(name)
+                packages.append((nn, name, file))
+        packages.sort()
+        sublist = []
+        for nn, name, file in packages:
+            item = DirBrowserTreeItem(file, self.packages + [name])
+            sublist.append(item)
+        for nn, name in self.listmodules(names):
+            item = ModuleBrowserTreeItem(os.path.join(self.dir, name))
+            sublist.append(item)
+        return sublist
+
+    def ispackagedir(self, file):
+        " Return true for directories that are packages."
+        if not os.path.isdir(file):
+            return False
+        init = os.path.join(file, "__init__.py")
+        return os.path.exists(init)
+
+    def listmodules(self, allnames):
+        modules = {}
+        suffixes = importlib.machinery.EXTENSION_SUFFIXES[:]
+        suffixes += importlib.machinery.SOURCE_SUFFIXES
+        suffixes += importlib.machinery.BYTECODE_SUFFIXES
+        sorted = []
+        for suff in suffixes:
+            i = -len(suff)
+            for name in allnames[:]:
+                normed_name = os.path.normcase(name)
+                if normed_name[i:] == suff:
+                    mod_name = name[:i]
+                    if mod_name not in modules:
+                        modules[mod_name] = None
+                        sorted.append((normed_name, name))
+                        allnames.remove(name)
+        sorted.sort()
+        return sorted
+
+
+def _path_browser(parent):  # htest #
+    PathBrowser(parent, _htest=True)
+    parent.mainloop()
+
+if __name__ == "__main__":
+    from unittest import main
+    main('idlelib.idle_test.test_pathbrowser', verbosity=2, exit=False)
+
+    from idlelib.idle_test.htest import run
+    run(_path_browser)
diff --git a/rootfs/usr/lib/python3.8/idlelib/percolator.py b/rootfs/usr/lib/python3.8/idlelib/percolator.py
new file mode 100644
index 0000000..db70304
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/percolator.py
@@ -0,0 +1,103 @@
+from idlelib.delegator import Delegator
+from idlelib.redirector import WidgetRedirector
+
+
+class Percolator:
+
+    def __init__(self, text):
+        # XXX would be nice to inherit from Delegator
+        self.text = text
+        self.redir = WidgetRedirector(text)
+        self.top = self.bottom = Delegator(text)
+        self.bottom.insert = self.redir.register("insert", self.insert)
+        self.bottom.delete = self.redir.register("delete", self.delete)
+        self.filters = []
+
+    def close(self):
+        while self.top is not self.bottom:
+            self.removefilter(self.top)
+        self.top = None
+        self.bottom.setdelegate(None)
+        self.bottom = None
+        self.redir.close()
+        self.redir = None
+        self.text = None
+
+    def insert(self, index, chars, tags=None):
+        # Could go away if inheriting from Delegator
+        self.top.insert(index, chars, tags)
+
+    def delete(self, index1, index2=None):
+        # Could go away if inheriting from Delegator
+        self.top.delete(index1, index2)
+
+    def insertfilter(self, filter):
+        # Perhaps rename to pushfilter()?
+        assert isinstance(filter, Delegator)
+        assert filter.delegate is None
+        filter.setdelegate(self.top)
+        self.top = filter
+
+    def removefilter(self, filter):
+        # XXX Perhaps should only support popfilter()?
+        assert isinstance(filter, Delegator)
+        assert filter.delegate is not None
+        f = self.top
+        if f is filter:
+            self.top = filter.delegate
+            filter.setdelegate(None)
+        else:
+            while f.delegate is not filter:
+                assert f is not self.bottom
+                f.resetcache()
+                f = f.delegate
+            f.setdelegate(filter.delegate)
+            filter.setdelegate(None)
+
+
+def _percolator(parent):  # htest #
+    import tkinter as tk
+
+    class Tracer(Delegator):
+        def __init__(self, name):
+            self.name = name
+            Delegator.__init__(self, None)
+
+        def insert(self, *args):
+            print(self.name, ": insert", args)
+            self.delegate.insert(*args)
+
+        def delete(self, *args):
+            print(self.name, ": delete", args)
+            self.delegate.delete(*args)
+
+    box = tk.Toplevel(parent)
+    box.title("Test Percolator")
+    x, y = map(int, parent.geometry().split('+')[1:])
+    box.geometry("+%d+%d" % (x, y + 175))
+    text = tk.Text(box)
+    p = Percolator(text)
+    pin = p.insertfilter
+    pout = p.removefilter
+    t1 = Tracer("t1")
+    t2 = Tracer("t2")
+
+    def toggle1():
+        (pin if var1.get() else pout)(t1)
+    def toggle2():
+        (pin if var2.get() else pout)(t2)
+
+    text.pack()
+    var1 = tk.IntVar(parent)
+    cb1 = tk.Checkbutton(box, text="Tracer1", command=toggle1, variable=var1)
+    cb1.pack()
+    var2 = tk.IntVar(parent)
+    cb2 = tk.Checkbutton(box, text="Tracer2", command=toggle2, variable=var2)
+    cb2.pack()
+
+if __name__ == "__main__":
+    from unittest import main
+    main('idlelib.idle_test.test_percolator', verbosity=2, exit=False)
+
+    from idlelib.idle_test.htest import run
+    run(_percolator)
diff --git a/rootfs/usr/lib/python3.8/idlelib/pyparse.py b/rootfs/usr/lib/python3.8/idlelib/pyparse.py
new file mode 100644
index 0000000..d34872b
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/pyparse.py
@@ -0,0 +1,593 @@
+"""Define partial Python code Parser used by editor and hyperparser.
+
+Instances of ParseMap are used with str.translate.
+
+The following bound search and match functions are defined:
+_synchre - start of popular statement;
+_junkre - whitespace or comment line;
+_match_stringre: string, possibly without closer;
+_itemre - line that may have bracket structure start;
+_closere - line that must be followed by dedent.
+_chew_ordinaryre - non-special characters.
+"""
+import re
+
+# Reason last statement is continued (or C_NONE if it's not).
+(C_NONE, C_BACKSLASH, C_STRING_FIRST_LINE,
+ C_STRING_NEXT_LINES, C_BRACKET) = range(5)
+
+# Find what looks like the start of a popular statement.
+
+_synchre = re.compile(r"""
+    ^
+    [ \t]*
+    (?: while
+    |   else
+    |   def
+    |   return
+    |   assert
+    |   break
+    |   class
+    |   continue
+    |   elif
+    |   try
+    |   except
+    |   raise
+    |   import
+    |   yield
+    )
+    \b
+""", re.VERBOSE | re.MULTILINE).search
+
+# Match blank line or non-indenting comment line.
+
+_junkre = re.compile(r"""
+    [ \t]*
+    (?: \# \S .* )?
+    \n
+""", re.VERBOSE).match
+
+# Match any flavor of string; the terminating quote is optional
+# so that we're robust in the face of incomplete program text.
+
+_match_stringre = re.compile(r"""
+    \""" [^"\\]* (?:
+                     (?: \\. | "(?!"") )
+                     [^"\\]*
+                 )*
+    (?: \""" )?
+
+|   " [^"\\\n]* (?: \\. [^"\\\n]* )* "?
+
+|   ''' [^'\\]* (?:
+                   (?: \\. | '(?!'') )
+                   [^'\\]*
+                )*
+    (?: ''' )?
+
+|   ' [^'\\\n]* (?: \\. [^'\\\n]* )* '?
+""", re.VERBOSE | re.DOTALL).match
+
+# Match a line that starts with something interesting;
+# used to find the first item of a bracket structure.
+
+_itemre = re.compile(r"""
+    [ \t]*
+    [^\s#\\]    # if we match, m.end()-1 is the interesting char
+""", re.VERBOSE).match
+
+# Match start of statements that should be followed by a dedent.
+
+_closere = re.compile(r"""
+    \s*
+    (?: return
+    |   break
+    |   continue
+    |   raise
+    |   pass
+    )
+    \b
+""", re.VERBOSE).match
+
+# Chew up non-special chars as quickly as possible.  If match is
+# successful, m.end() less 1 is the index of the last boring char
+# matched.  If match is unsuccessful, the string starts with an
+# interesting char.
+
+_chew_ordinaryre = re.compile(r"""
+    [^[\](){}#'"\\]+
+""", re.VERBOSE).match
+
+
+class ParseMap(dict):
+    r"""Dict subclass that maps anything not in dict to 'x'.
+
+    This is designed to be used with str.translate in study1.
+    Anything not specifically mapped otherwise becomes 'x'.
+    Example: replace everything except whitespace with 'x'.
+
+    >>> keepwhite = ParseMap((ord(c), ord(c)) for c in ' \t\n\r')
+    >>> "a + b\tc\nd".translate(keepwhite)
+    'x x x\tx\nx'
+    """
+    # Calling this triples access time; see bpo-32940
+    def __missing__(self, key):
+        return 120  # ord('x')
+
+
+# Map all ascii to 120 to avoid __missing__ call, then replace some.
+trans = ParseMap.fromkeys(range(128), 120)
+trans.update((ord(c), ord('(')) for c in "({[")  # open brackets => '(';
+trans.update((ord(c), ord(')')) for c in ")}]")  # close brackets => ')'.
+trans.update((ord(c), ord(c)) for c in "\"'\\\n#")  # Keep these.
+
+
+class Parser:
+
+    def __init__(self, indentwidth, tabwidth):
+        self.indentwidth = indentwidth
+        self.tabwidth = tabwidth
+
+    def set_code(self, s):
+        assert len(s) == 0 or s[-1] == '\n'
+        self.code = s
+        self.study_level = 0
+
+    def find_good_parse_start(self, is_char_in_string):
+        """
+        Return index of a good place to begin parsing, as close to the
+        end of the string as possible.  This will be the start of some
+        popular stmt like "if" or "def".  Return None if none found:
+        the caller should pass more prior context then, if possible, or
+        if not (the entire program text up until the point of interest
+        has already been tried) pass 0 to set_lo().
+
+        This will be reliable iff given a reliable is_char_in_string()
+        function, meaning that when it says "no", it's absolutely
+        guaranteed that the char is not in a string.
+        """
+        code, pos = self.code, None
+
+        # Peek back from the end for a good place to start,
+        # but don't try too often; pos will be left None, or
+        # bumped to a legitimate synch point.
+        limit = len(code)
+        for tries in range(5):
+            i = code.rfind(":\n", 0, limit)
+            if i < 0:
+                break
+            i = code.rfind('\n', 0, i) + 1  # start of colon line (-1+1=0)
+            m = _synchre(code, i, limit)
+            if m and not is_char_in_string(m.start()):
+                pos = m.start()
+                break
+            limit = i
+        if pos is None:
+            # Nothing looks like a block-opener, or stuff does
+            # but is_char_in_string keeps returning true; most likely
+            # we're in or near a giant string, the colorizer hasn't
+            # caught up enough to be helpful, or there simply *aren't*
+            # any interesting stmts.  In any of these cases we're
+            # going to have to parse the whole thing to be sure, so
+            # give it one last try from the start, but stop wasting
+            # time here regardless of the outcome.
+            m = _synchre(code)
+            if m and not is_char_in_string(m.start()):
+                pos = m.start()
+            return pos
+
+        # Peeking back worked; look forward until _synchre no longer
+        # matches.
+        i = pos + 1
+        while 1:
+            m = _synchre(code, i)
+            if m:
+                s, i = m.span()
+                if not is_char_in_string(s):
+                    pos = s
+            else:
+                break
+        return pos
+
+    def set_lo(self, lo):
+        """ Throw away the start of the string.
+
+        Intended to be called with the result of find_good_parse_start().
+        """
+        assert lo == 0 or self.code[lo-1] == '\n'
+        if lo > 0:
+            self.code = self.code[lo:]
+
+    def _study1(self):
+        """Find the line numbers of non-continuation lines.
+
+        As quickly as humanly possible <wink>, find the line numbers (0-
+        based) of the non-continuation lines.
+        Creates self.{goodlines, continuation}.
+        """
+        if self.study_level >= 1:
+            return
+        self.study_level = 1
+
+        # Map all uninteresting characters to "x", all open brackets
+        # to "(", all close brackets to ")", then collapse runs of
+        # uninteresting characters.  This can cut the number of chars
+        # by a factor of 10-40, and so greatly speed the following loop.
+        code = self.code
+        code = code.translate(trans)
+        code = code.replace('xxxxxxxx', 'x')
+        code = code.replace('xxxx', 'x')
+        code = code.replace('xx', 'x')
+        code = code.replace('xx', 'x')
+        code = code.replace('\nx', '\n')
+        # Replacing x\n with \n would be incorrect because
+        # x may be preceded by a backslash.
+
+        # March over the squashed version of the program, accumulating
+        # the line numbers of non-continued stmts, and determining
+        # whether & why the last stmt is a continuation.
+        continuation = C_NONE
+        level = lno = 0     # level is nesting level; lno is line number
+        self.goodlines = goodlines = [0]
+        push_good = goodlines.append
+        i, n = 0, len(code)
+        while i < n:
+            ch = code[i]
+            i = i+1
+
+            # cases are checked in decreasing order of frequency
+            if ch == 'x':
+                continue
+
+            if ch == '\n':
+                lno = lno + 1
+                if level == 0:
+                    push_good(lno)
+                    # else we're in an unclosed bracket structure
+                continue
+
+            if ch == '(':
+                level = level + 1
+                continue
+
+            if ch == ')':
+                if level:
+                    level = level - 1
+                    # else the program is invalid, but we can't complain
+                continue
+
+            if ch == '"' or ch == "'":
+                # consume the string
+                quote = ch
+                if code[i-1:i+2] == quote * 3:
+                    quote = quote * 3
+                firstlno = lno
+                w = len(quote) - 1
+                i = i+w
+                while i < n:
+                    ch = code[i]
+                    i = i+1
+
+                    if ch == 'x':
+                        continue
+
+                    if code[i-1:i+w] == quote:
+                        i = i+w
+                        break
+
+                    if ch == '\n':
+                        lno = lno + 1
+                        if w == 0:
+                            # unterminated single-quoted string
+                            if level == 0:
+                                push_good(lno)
+                            break
+                        continue
+
+                    if ch == '\\':
+                        assert i < n
+                        if code[i] == '\n':
+                            lno = lno + 1
+                        i = i+1
+                        continue
+
+                    # else comment char or paren inside string
+
+                else:
+                    # didn't break out of the loop, so we're still
+                    # inside a string
+                    if (lno - 1) == firstlno:
+                        # before the previous \n in code, we were in the first
+                        # line of the string
+                        continuation = C_STRING_FIRST_LINE
+                    else:
+                        continuation = C_STRING_NEXT_LINES
+                continue    # with outer loop
+
+            if ch == '#':
+                # consume the comment
+                i = code.find('\n', i)
+                assert i >= 0
+                continue
+
+            assert ch == '\\'
+            assert i < n
+            if code[i] == '\n':
+                lno = lno + 1
+                if i+1 == n:
+                    continuation = C_BACKSLASH
+            i = i+1
+
+        # The last stmt may be continued for all 3 reasons.
+        # String continuation takes precedence over bracket
+        # continuation, which beats backslash continuation.
+        if (continuation != C_STRING_FIRST_LINE
+            and continuation != C_STRING_NEXT_LINES and level > 0):
+            continuation = C_BRACKET
+        self.continuation = continuation
+
+        # Push the final line number as a sentinel value, regardless of
+        # whether it's continued.
+        assert (continuation == C_NONE) == (goodlines[-1] == lno)
+        if goodlines[-1] != lno:
+            push_good(lno)
+
+    def get_continuation_type(self):
+        self._study1()
+        return self.continuation
+
+    def _study2(self):
+        """
+        study1 was sufficient to determine the continuation status,
+        but doing more requires looking at every character.  study2
+        does this for the last interesting statement in the block.
+        Creates:
+            self.stmt_start, stmt_end
+                slice indices of last interesting stmt
+            self.stmt_bracketing
+                the bracketing structure of the last interesting stmt; for
+                example, for the statement "say(boo) or die",
+                stmt_bracketing will be ((0, 0), (0, 1), (2, 0), (2, 1),
+                (4, 0)). Strings and comments are treated as brackets, for
+                the matter.
+            self.lastch
+                last interesting character before optional trailing comment
+            self.lastopenbracketpos
+                if continuation is C_BRACKET, index of last open bracket
+        """
+        if self.study_level >= 2:
+            return
+        self._study1()
+        self.study_level = 2
+
+        # Set p and q to slice indices of last interesting stmt.
+        code, goodlines = self.code, self.goodlines
+        i = len(goodlines) - 1  # Index of newest line.
+        p = len(code)  # End of goodlines[i]
+        while i:
+            assert p
+            # Make p be the index of the stmt at line number goodlines[i].
+            # Move p back to the stmt at line number goodlines[i-1].
+            q = p
+            for nothing in range(goodlines[i-1], goodlines[i]):
+                # tricky: sets p to 0 if no preceding newline
+                p = code.rfind('\n', 0, p-1) + 1
+            # The stmt code[p:q] isn't a continuation, but may be blank
+            # or a non-indenting comment line.
+            if  _junkre(code, p):
+                i = i-1
+            else:
+                break
+        if i == 0:
+            # nothing but junk!
+            assert p == 0
+            q = p
+        self.stmt_start, self.stmt_end = p, q
+
+        # Analyze this stmt, to find the last open bracket (if any)
+        # and last interesting character (if any).
+        lastch = ""
+        stack = []  # stack of open bracket indices
+        push_stack = stack.append
+        bracketing = [(p, 0)]
+        while p < q:
+            # suck up all except ()[]{}'"#\\
+            m = _chew_ordinaryre(code, p, q)
+            if m:
+                # we skipped at least one boring char
+                newp = m.end()
+                # back up over totally boring whitespace
+                i = newp - 1    # index of last boring char
+                while i >= p and code[i] in " \t\n":
+                    i = i-1
+                if i >= p:
+                    lastch = code[i]
+                p = newp
+                if p >= q:
+                    break
+
+            ch = code[p]
+
+            if ch in "([{":
+                push_stack(p)
+                bracketing.append((p, len(stack)))
+                lastch = ch
+                p = p+1
+                continue
+
+            if ch in ")]}":
+                if stack:
+                    del stack[-1]
+                lastch = ch
+                p = p+1
+                bracketing.append((p, len(stack)))
+                continue
+
+            if ch == '"' or ch == "'":
+                # consume string
+                # Note that study1 did this with a Python loop, but
+                # we use a regexp here; the reason is speed in both
+                # cases; the string may be huge, but study1 pre-squashed
+                # strings to a couple of characters per line.  study1
+                # also needed to keep track of newlines, and we don't
+                # have to.
+                bracketing.append((p, len(stack)+1))
+                lastch = ch
+                p = _match_stringre(code, p, q).end()
+                bracketing.append((p, len(stack)))
+                continue
+
+            if ch == '#':
+                # consume comment and trailing newline
+                bracketing.append((p, len(stack)+1))
+                p = code.find('\n', p, q) + 1
+                assert p > 0
+                bracketing.append((p, len(stack)))
+                continue
+
+            assert ch == '\\'
+            p = p+1     # beyond backslash
+            assert p < q
+            if code[p] != '\n':
+                # the program is invalid, but can't complain
+                lastch = ch + code[p]
+            p = p+1     # beyond escaped char
+
+        # end while p < q:
+
+        self.lastch = lastch
+        self.lastopenbracketpos = stack[-1] if stack else None
+        self.stmt_bracketing = tuple(bracketing)
+
+    def compute_bracket_indent(self):
+        """Return number of spaces the next line should be indented.
+
+        Line continuation must be C_BRACKET.
+        """
+        self._study2()
+        assert self.continuation == C_BRACKET
+        j = self.lastopenbracketpos
+        code = self.code
+        n = len(code)
+        origi = i = code.rfind('\n', 0, j) + 1
+        j = j+1     # one beyond open bracket
+        # find first list item; set i to start of its line
+        while j < n:
+            m = _itemre(code, j)
+            if m:
+                j = m.end() - 1     # index of first interesting char
+                extra = 0
+                break
+            else:
+                # this line is junk; advance to next line
+                i = j = code.find('\n', j) + 1
+        else:
+            # nothing interesting follows the bracket;
+            # reproduce the bracket line's indentation + a level
+            j = i = origi
+            while code[j] in " \t":
+                j = j+1
+            extra = self.indentwidth
+        return len(code[i:j].expandtabs(self.tabwidth)) + extra
+
+    def get_num_lines_in_stmt(self):
+        """Return number of physical lines in last stmt.
+
+        The statement doesn't have to be an interesting statement.  This is
+        intended to be called when continuation is C_BACKSLASH.
+        """
+        self._study1()
+        goodlines = self.goodlines
+        return goodlines[-1] - goodlines[-2]
+
+    def compute_backslash_indent(self):
+        """Return number of spaces the next line should be indented.
+
+        Line continuation must be C_BACKSLASH.  Also assume that the new
+        line is the first one following the initial line of the stmt.
+        """
+        self._study2()
+        assert self.continuation == C_BACKSLASH
+        code = self.code
+        i = self.stmt_start
+        while code[i] in " \t":
+            i = i+1
+        startpos = i
+
+        # See whether the initial line starts an assignment stmt; i.e.,
+        # look for an = operator
+        endpos = code.find('\n', startpos) + 1
+        found = level = 0
+        while i < endpos:
+            ch = code[i]
+            if ch in "([{":
+                level = level + 1
+                i = i+1
+            elif ch in ")]}":
+                if level:
+                    level = level - 1
+                i = i+1
+            elif ch == '"' or ch == "'":
+                i = _match_stringre(code, i, endpos).end()
+            elif ch == '#':
+                # This line is unreachable because the # makes a comment of
+                # everything after it.
+                break
+            elif level == 0 and ch == '=' and \
+                   (i == 0 or code[i-1] not in "=<>!") and \
+                   code[i+1] != '=':
+                found = 1
+                break
+            else:
+                i = i+1
+
+        if found:
+            # found a legit =, but it may be the last interesting
+            # thing on the line
+            i = i+1     # move beyond the =
+            found = re.match(r"\s*\\", code[i:endpos]) is None
+
+        if not found:
+            # oh well ... settle for moving beyond the first chunk
+            # of non-whitespace chars
+            i = startpos
+            while code[i] not in " \t\n":
+                i = i+1
+
+        return len(code[self.stmt_start:i].expandtabs(\
+                                     self.tabwidth)) + 1
+
+    def get_base_indent_string(self):
+        """Return the leading whitespace on the initial line of the last
+        interesting stmt.
+        """
+        self._study2()
+        i, n = self.stmt_start, self.stmt_end
+        j = i
+        code = self.code
+        while j < n and code[j] in " \t":
+            j = j + 1
+        return code[i:j]
+
+    def is_block_opener(self):
+        "Return True if the last interesting statement opens a block."
+        self._study2()
+        return self.lastch == ':'
+
+    def is_block_closer(self):
+        "Return True if the last interesting statement closes a block."
+        self._study2()
+        return _closere(self.code, self.stmt_start) is not None
+
+    def get_last_stmt_bracketing(self):
+        """Return bracketing structure of the last interesting statement.
+
+        The returned tuple is in the format defined in _study2().
+        """
+        self._study2()
+        return self.stmt_bracketing
+
+
+if __name__ == '__main__':
+    from unittest import main
+    main('idlelib.idle_test.test_pyparse', verbosity=2)
diff --git a/rootfs/usr/lib/python3.8/idlelib/pyshell.py b/rootfs/usr/lib/python3.8/idlelib/pyshell.py
new file mode 100755
index 0000000..fea3762
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/pyshell.py
@@ -0,0 +1,1579 @@
+#! /usr/bin/env python3
+
+import sys
+if __name__ == "__main__":
+    sys.modules['idlelib.pyshell'] = sys.modules['__main__']
+
+try:
+    from tkinter import *
+except ImportError:
+    print("** IDLE can't import Tkinter.\n"
+          "Your Python may not be configured for Tk. **", file=sys.__stderr__)
+    raise SystemExit(1)
+
+# Valid arguments for the ...Awareness call below are defined in the following.
+# https://msdn.microsoft.com/en-us/library/windows/desktop/dn280512(v=vs.85).aspx
+if sys.platform == 'win32':
+    try:
+        import ctypes
+        PROCESS_SYSTEM_DPI_AWARE = 1  # Int required.
+        ctypes.OleDLL('shcore').SetProcessDpiAwareness(PROCESS_SYSTEM_DPI_AWARE)
+    except (ImportError, AttributeError, OSError):
+        pass
+
+from tkinter import messagebox
+if TkVersion < 8.5:
+    root = Tk()  # otherwise create root in main
+    root.withdraw()
+    from idlelib.run import fix_scaling
+    fix_scaling(root)
+    messagebox.showerror("Idle Cannot Start",
+            "Idle requires tcl/tk 8.5+, not %s." % TkVersion,
+            parent=root)
+    raise SystemExit(1)
+
+from code import InteractiveInterpreter
+import linecache
+import os
+import os.path
+from platform import python_version
+import re
+import socket
+import subprocess
+from textwrap import TextWrapper
+import threading
+import time
+import tokenize
+import warnings
+
+from idlelib.colorizer import ColorDelegator
+from idlelib.config import idleConf
+from idlelib import debugger
+from idlelib import debugger_r
+from idlelib.editor import EditorWindow, fixwordbreaks
+from idlelib.filelist import FileList
+from idlelib.outwin import OutputWindow
+from idlelib import rpc
+from idlelib.run import idle_formatwarning, StdInputFile, StdOutputFile
+from idlelib.undo import UndoDelegator
+
+HOST = '127.0.0.1' # python execution server on localhost loopback
+PORT = 0  # someday pass in host, port for remote debug capability
+
+# Override warnings module to write to warning_stream.  Initialize to send IDLE
+# internal warnings to the console.  ScriptBinding.check_syntax() will
+# temporarily redirect the stream to the shell window to display warnings when
+# checking user's code.
+warning_stream = sys.__stderr__  # None, at least on Windows, if no console.
+
+def idle_showwarning(
+        message, category, filename, lineno, file=None, line=None):
+    """Show Idle-format warning (after replacing warnings.showwarning).
+
+    The differences are the formatter called, the file=None replacement,
+    which can be None, the capture of the consequence AttributeError,
+    and the output of a hard-coded prompt.
+    """
+    if file is None:
+        file = warning_stream
+    try:
+        file.write(idle_formatwarning(
+                message, category, filename, lineno, line=line))
+        file.write(">>> ")
+    except (AttributeError, OSError):
+        pass  # if file (probably __stderr__) is invalid, skip warning.
+
+_warnings_showwarning = None
+
+def capture_warnings(capture):
+    "Replace warning.showwarning with idle_showwarning, or reverse."
+
+    global _warnings_showwarning
+    if capture:
+        if _warnings_showwarning is None:
+            _warnings_showwarning = warnings.showwarning
+            warnings.showwarning = idle_showwarning
+    else:
+        if _warnings_showwarning is not None:
+            warnings.showwarning = _warnings_showwarning
+            _warnings_showwarning = None
+
+capture_warnings(True)
+
+def extended_linecache_checkcache(filename=None,
+                                  orig_checkcache=linecache.checkcache):
+    """Extend linecache.checkcache to preserve the <pyshell#...> entries
+
+    Rather than repeating the linecache code, patch it to save the
+    <pyshell#...> entries, call the original linecache.checkcache()
+    (skipping them), and then restore the saved entries.
+
+    orig_checkcache is bound at definition time to the original
+    method, allowing it to be patched.
+    """
+    cache = linecache.cache
+    save = {}
+    for key in list(cache):
+        if key[:1] + key[-1:] == '<>':
+            save[key] = cache.pop(key)
+    orig_checkcache(filename)
+    cache.update(save)
+
+# Patch linecache.checkcache():
+linecache.checkcache = extended_linecache_checkcache
+
+
+class PyShellEditorWindow(EditorWindow):
+    "Regular text edit window in IDLE, supports breakpoints"
+
+    def __init__(self, *args):
+        self.breakpoints = []
+        EditorWindow.__init__(self, *args)
+        self.text.bind("<<set-breakpoint-here>>", self.set_breakpoint_here)
+        self.text.bind("<<clear-breakpoint-here>>", self.clear_breakpoint_here)
+        self.text.bind("<<open-python-shell>>", self.flist.open_shell)
+
+        #TODO: don't read/write this from/to .idlerc when testing
+        self.breakpointPath = os.path.join(
+                idleConf.userdir, 'breakpoints.lst')
+        # whenever a file is changed, restore breakpoints
+        def filename_changed_hook(old_hook=self.io.filename_change_hook,
+                                  self=self):
+            self.restore_file_breaks()
+            old_hook()
+        self.io.set_filename_change_hook(filename_changed_hook)
+        if self.io.filename:
+            self.restore_file_breaks()
+        self.color_breakpoint_text()
+
+    rmenu_specs = [
+        ("Cut", "<<cut>>", "rmenu_check_cut"),
+        ("Copy", "<<copy>>", "rmenu_check_copy"),
+        ("Paste", "<<paste>>", "rmenu_check_paste"),
+        (None, None, None),
+        ("Set Breakpoint", "<<set-breakpoint-here>>", None),
+        ("Clear Breakpoint", "<<clear-breakpoint-here>>", None)
+    ]
+
+    def color_breakpoint_text(self, color=True):
+        "Turn colorizing of breakpoint text on or off"
+        if self.io is None:
+            # possible due to update in restore_file_breaks
+            return
+        if color:
+            theme = idleConf.CurrentTheme()
+            cfg = idleConf.GetHighlight(theme, "break")
+        else:
+            cfg = {'foreground': '', 'background': ''}
+        self.text.tag_config('BREAK', cfg)
+
+    def set_breakpoint(self, lineno):
+        text = self.text
+        filename = self.io.filename
+        text.tag_add("BREAK", "%d.0" % lineno, "%d.0" % (lineno+1))
+        try:
+            self.breakpoints.index(lineno)
+        except ValueError:  # only add if missing, i.e. do once
+            self.breakpoints.append(lineno)
+        try:    # update the subprocess debugger
+            debug = self.flist.pyshell.interp.debugger
+            debug.set_breakpoint_here(filename, lineno)
+        except: # but debugger may not be active right now....
+            pass
+
+    def set_breakpoint_here(self, event=None):
+        text = self.text
+        filename = self.io.filename
+        if not filename:
+            text.bell()
+            return
+        lineno = int(float(text.index("insert")))
+        self.set_breakpoint(lineno)
+
+    def clear_breakpoint_here(self, event=None):
+        text = self.text
+        filename = self.io.filename
+        if not filename:
+            text.bell()
+            return
+        lineno = int(float(text.index("insert")))
+        try:
+            self.breakpoints.remove(lineno)
+        except:
+            pass
+        text.tag_remove("BREAK", "insert linestart",\
+                        "insert lineend +1char")
+        try:
+            debug = self.flist.pyshell.interp.debugger
+            debug.clear_breakpoint_here(filename, lineno)
+        except:
+            pass
+
+    def clear_file_breaks(self):
+        if self.breakpoints:
+            text = self.text
+            filename = self.io.filename
+            if not filename:
+                text.bell()
+                return
+            self.breakpoints = []
+            text.tag_remove("BREAK", "1.0", END)
+            try:
+                debug = self.flist.pyshell.interp.debugger
+                debug.clear_file_breaks(filename)
+            except:
+                pass
+
+    def store_file_breaks(self):
+        "Save breakpoints when file is saved"
+        # XXX 13 Dec 2002 KBK Currently the file must be saved before it can
+        #     be run.  The breaks are saved at that time.  If we introduce
+        #     a temporary file save feature the save breaks functionality
+        #     needs to be re-verified, since the breaks at the time the
+        #     temp file is created may differ from the breaks at the last
+        #     permanent save of the file.  Currently, a break introduced
+        #     after a save will be effective, but not persistent.
+        #     This is necessary to keep the saved breaks synched with the
+        #     saved file.
+        #
+        #     Breakpoints are set as tagged ranges in the text.
+        #     Since a modified file has to be saved before it is
+        #     run, and since self.breakpoints (from which the subprocess
+        #     debugger is loaded) is updated during the save, the visible
+        #     breaks stay synched with the subprocess even if one of these
+        #     unexpected breakpoint deletions occurs.
+        breaks = self.breakpoints
+        filename = self.io.filename
+        try:
+            with open(self.breakpointPath, "r") as fp:
+                lines = fp.readlines()
+        except OSError:
+            lines = []
+        try:
+            with open(self.breakpointPath, "w") as new_file:
+                for line in lines:
+                    if not line.startswith(filename + '='):
+                        new_file.write(line)
+                self.update_breakpoints()
+                breaks = self.breakpoints
+                if breaks:
+                    new_file.write(filename + '=' + str(breaks) + '\n')
+        except OSError as err:
+            if not getattr(self.root, "breakpoint_error_displayed", False):
+                self.root.breakpoint_error_displayed = True
+                messagebox.showerror(title='IDLE Error',
+                    message='Unable to update breakpoint list:\n%s'
+                        % str(err),
+                    parent=self.text)
+
+    def restore_file_breaks(self):
+        self.text.update()   # this enables setting "BREAK" tags to be visible
+        if self.io is None:
+            # can happen if IDLE closes due to the .update() call
+            return
+        filename = self.io.filename
+        if filename is None:
+            return
+        if os.path.isfile(self.breakpointPath):
+            with open(self.breakpointPath, "r") as fp:
+                lines = fp.readlines()
+            for line in lines:
+                if line.startswith(filename + '='):
+                    breakpoint_linenumbers = eval(line[len(filename)+1:])
+                    for breakpoint_linenumber in breakpoint_linenumbers:
+                        self.set_breakpoint(breakpoint_linenumber)
+
+    def update_breakpoints(self):
+        "Retrieves all the breakpoints in the current window"
+        text = self.text
+        ranges = text.tag_ranges("BREAK")
+        linenumber_list = self.ranges_to_linenumbers(ranges)
+        self.breakpoints = linenumber_list
+
+    def ranges_to_linenumbers(self, ranges):
+        lines = []
+        for index in range(0, len(ranges), 2):
+            lineno = int(float(ranges[index].string))
+            end = int(float(ranges[index+1].string))
+            while lineno < end:
+                lines.append(lineno)
+                lineno += 1
+        return lines
+
+# XXX 13 Dec 2002 KBK Not used currently
+#    def saved_change_hook(self):
+#        "Extend base method - clear breaks if module is modified"
+#        if not self.get_saved():
+#            self.clear_file_breaks()
+#        EditorWindow.saved_change_hook(self)
+
+    def _close(self):
+        "Extend base method - clear breaks when module is closed"
+        self.clear_file_breaks()
+        EditorWindow._close(self)
+
+
+class PyShellFileList(FileList):
+    "Extend base class: IDLE supports a shell and breakpoints"
+
+    # override FileList's class variable, instances return PyShellEditorWindow
+    # instead of EditorWindow when new edit windows are created.
+    EditorWindow = PyShellEditorWindow
+
+    pyshell = None
+
+    def open_shell(self, event=None):
+        if self.pyshell:
+            self.pyshell.top.wakeup()
+        else:
+            self.pyshell = PyShell(self)
+            if self.pyshell:
+                if not self.pyshell.begin():
+                    return None
+        return self.pyshell
+
+
+class ModifiedColorDelegator(ColorDelegator):
+    "Extend base class: colorizer for the shell window itself"
+
+    def __init__(self):
+        ColorDelegator.__init__(self)
+        self.LoadTagDefs()
+
+    def recolorize_main(self):
+        self.tag_remove("TODO", "1.0", "iomark")
+        self.tag_add("SYNC", "1.0", "iomark")
+        ColorDelegator.recolorize_main(self)
+
+    def LoadTagDefs(self):
+        ColorDelegator.LoadTagDefs(self)
+        theme = idleConf.CurrentTheme()
+        self.tagdefs.update({
+            "stdin": {'background':None,'foreground':None},
+            "stdout": idleConf.GetHighlight(theme, "stdout"),
+            "stderr": idleConf.GetHighlight(theme, "stderr"),
+            "console": idleConf.GetHighlight(theme, "console"),
+        })
+
+    def removecolors(self):
+        # Don't remove shell color tags before "iomark"
+        for tag in self.tagdefs:
+            self.tag_remove(tag, "iomark", "end")
+
+class ModifiedUndoDelegator(UndoDelegator):
+    "Extend base class: forbid insert/delete before the I/O mark"
+
+    def insert(self, index, chars, tags=None):
+        try:
+            if self.delegate.compare(index, "<", "iomark"):
+                self.delegate.bell()
+                return
+        except TclError:
+            pass
+        UndoDelegator.insert(self, index, chars, tags)
+
+    def delete(self, index1, index2=None):
+        try:
+            if self.delegate.compare(index1, "<", "iomark"):
+                self.delegate.bell()
+                return
+        except TclError:
+            pass
+        UndoDelegator.delete(self, index1, index2)
+
+
+class MyRPCClient(rpc.RPCClient):
+
+    def handle_EOF(self):
+        "Override the base class - just re-raise EOFError"
+        raise EOFError
+
+def restart_line(width, filename):  # See bpo-38141.
+    """Return width long restart line formatted with filename.
+
+    Fill line with balanced '='s, with any extras and at least one at
+    the beginning.  Do not end with a trailing space.
+    """
+    tag = f"= RESTART: {filename or 'Shell'} ="
+    if width >= len(tag):
+        div, mod = divmod((width -len(tag)), 2)
+        return f"{(div+mod)*'='}{tag}{div*'='}"
+    else:
+        return tag[:-2]  # Remove ' ='.
+
+
+class ModifiedInterpreter(InteractiveInterpreter):
+
+    def __init__(self, tkconsole):
+        self.tkconsole = tkconsole
+        locals = sys.modules['__main__'].__dict__
+        InteractiveInterpreter.__init__(self, locals=locals)
+        self.restarting = False
+        self.subprocess_arglist = None
+        self.port = PORT
+        self.original_compiler_flags = self.compile.compiler.flags
+
+    _afterid = None
+    rpcclt = None
+    rpcsubproc = None
+
+    def spawn_subprocess(self):
+        if self.subprocess_arglist is None:
+            self.subprocess_arglist = self.build_subprocess_arglist()
+        self.rpcsubproc = subprocess.Popen(self.subprocess_arglist)
+
+    def build_subprocess_arglist(self):
+        assert (self.port!=0), (
+            "Socket should have been assigned a port number.")
+        w = ['-W' + s for s in sys.warnoptions]
+        # Maybe IDLE is installed and is being accessed via sys.path,
+        # or maybe it's not installed and the idle.py script is being
+        # run from the IDLE source directory.
+        del_exitf = idleConf.GetOption('main', 'General', 'delete-exitfunc',
+                                       default=False, type='bool')
+        command = "__import__('idlelib.run').run.main(%r)" % (del_exitf,)
+        return [sys.executable] + w + ["-c", command, str(self.port)]
+
+    def start_subprocess(self):
+        addr = (HOST, self.port)
+        # GUI makes several attempts to acquire socket, listens for connection
+        for i in range(3):
+            time.sleep(i)
+            try:
+                self.rpcclt = MyRPCClient(addr)
+                break
+            except OSError:
+                pass
+        else:
+            self.display_port_binding_error()
+            return None
+        # if PORT was 0, system will assign an 'ephemeral' port. Find it out:
+        self.port = self.rpcclt.listening_sock.getsockname()[1]
+        # if PORT was not 0, probably working with a remote execution server
+        if PORT != 0:
+            # To allow reconnection within the 2MSL wait (cf. Stevens TCP
+            # V1, 18.6),  set SO_REUSEADDR.  Note that this can be problematic
+            # on Windows since the implementation allows two active sockets on
+            # the same address!
+            self.rpcclt.listening_sock.setsockopt(socket.SOL_SOCKET,
+                                           socket.SO_REUSEADDR, 1)
+        self.spawn_subprocess()
+        #time.sleep(20) # test to simulate GUI not accepting connection
+        # Accept the connection from the Python execution server
+        self.rpcclt.listening_sock.settimeout(10)
+        try:
+            self.rpcclt.accept()
+        except socket.timeout:
+            self.display_no_subprocess_error()
+            return None
+        self.rpcclt.register("console", self.tkconsole)
+        self.rpcclt.register("stdin", self.tkconsole.stdin)
+        self.rpcclt.register("stdout", self.tkconsole.stdout)
+        self.rpcclt.register("stderr", self.tkconsole.stderr)
+        self.rpcclt.register("flist", self.tkconsole.flist)
+        self.rpcclt.register("linecache", linecache)
+        self.rpcclt.register("interp", self)
+        self.transfer_path(with_cwd=True)
+        self.poll_subprocess()
+        return self.rpcclt
+
+    def restart_subprocess(self, with_cwd=False, filename=''):
+        if self.restarting:
+            return self.rpcclt
+        self.restarting = True
+        # close only the subprocess debugger
+        debug = self.getdebugger()
+        if debug:
+            try:
+                # Only close subprocess debugger, don't unregister gui_adap!
+                debugger_r.close_subprocess_debugger(self.rpcclt)
+            except:
+                pass
+        # Kill subprocess, spawn a new one, accept connection.
+        self.rpcclt.close()
+        self.terminate_subprocess()
+        console = self.tkconsole
+        was_executing = console.executing
+        console.executing = False
+        self.spawn_subprocess()
+        try:
+            self.rpcclt.accept()
+        except socket.timeout:
+            self.display_no_subprocess_error()
+            return None
+        self.transfer_path(with_cwd=with_cwd)
+        console.stop_readline()
+        # annotate restart in shell window and mark it
+        console.text.delete("iomark", "end-1c")
+        console.write('\n')
+        console.write(restart_line(console.width, filename))
+        console.text.mark_set("restart", "end-1c")
+        console.text.mark_gravity("restart", "left")
+        if not filename:
+            console.showprompt()
+        # restart subprocess debugger
+        if debug:
+            # Restarted debugger connects to current instance of debug GUI
+            debugger_r.restart_subprocess_debugger(self.rpcclt)
+            # reload remote debugger breakpoints for all PyShellEditWindows
+            debug.load_breakpoints()
+        self.compile.compiler.flags = self.original_compiler_flags
+        self.restarting = False
+        return self.rpcclt
+
+    def __request_interrupt(self):
+        self.rpcclt.remotecall("exec", "interrupt_the_server", (), {})
+
+    def interrupt_subprocess(self):
+        threading.Thread(target=self.__request_interrupt).start()
+
+    def kill_subprocess(self):
+        if self._afterid is not None:
+            self.tkconsole.text.after_cancel(self._afterid)
+        try:
+            self.rpcclt.listening_sock.close()
+        except AttributeError:  # no socket
+            pass
+        try:
+            self.rpcclt.close()
+        except AttributeError:  # no socket
+            pass
+        self.terminate_subprocess()
+        self.tkconsole.executing = False
+        self.rpcclt = None
+
+    def terminate_subprocess(self):
+        "Make sure subprocess is terminated"
+        try:
+            self.rpcsubproc.kill()
+        except OSError:
+            # process already terminated
+            return
+        else:
+            try:
+                self.rpcsubproc.wait()
+            except OSError:
+                return
+
+    def transfer_path(self, with_cwd=False):
+        if with_cwd:        # Issue 13506
+            path = ['']     # include Current Working Directory
+            path.extend(sys.path)
+        else:
+            path = sys.path
+
+        self.runcommand("""if 1:
+        import sys as _sys
+        _sys.path = %r
+        del _sys
+        \n""" % (path,))
+
+    active_seq = None
+
+    def poll_subprocess(self):
+        clt = self.rpcclt
+        if clt is None:
+            return
+        try:
+            response = clt.pollresponse(self.active_seq, wait=0.05)
+        except (EOFError, OSError, KeyboardInterrupt):
+            # lost connection or subprocess terminated itself, restart
+            # [the KBI is from rpc.SocketIO.handle_EOF()]
+            if self.tkconsole.closing:
+                return
+            response = None
+            self.restart_subprocess()
+        if response:
+            self.tkconsole.resetoutput()
+            self.active_seq = None
+            how, what = response
+            console = self.tkconsole.console
+            if how == "OK":
+                if what is not None:
+                    print(repr(what), file=console)
+            elif how == "EXCEPTION":
+                if self.tkconsole.getvar("<<toggle-jit-stack-viewer>>"):
+                    self.remote_stack_viewer()
+            elif how == "ERROR":
+                errmsg = "pyshell.ModifiedInterpreter: Subprocess ERROR:\n"
+                print(errmsg, what, file=sys.__stderr__)
+                print(errmsg, what, file=console)
+            # we received a response to the currently active seq number:
+            try:
+                self.tkconsole.endexecuting()
+            except AttributeError:  # shell may have closed
+                pass
+        # Reschedule myself
+        if not self.tkconsole.closing:
+            self._afterid = self.tkconsole.text.after(
+                self.tkconsole.pollinterval, self.poll_subprocess)
+
+    debugger = None
+
+    def setdebugger(self, debugger):
+        self.debugger = debugger
+
+    def getdebugger(self):
+        return self.debugger
+
+    def open_remote_stack_viewer(self):
+        """Initiate the remote stack viewer from a separate thread.
+
+        This method is called from the subprocess, and by returning from this
+        method we allow the subprocess to unblock.  After a bit the shell
+        requests the subprocess to open the remote stack viewer which returns a
+        static object looking at the last exception.  It is queried through
+        the RPC mechanism.
+
+        """
+        self.tkconsole.text.after(300, self.remote_stack_viewer)
+        return
+
+    def remote_stack_viewer(self):
+        from idlelib import debugobj_r
+        oid = self.rpcclt.remotequeue("exec", "stackviewer", ("flist",), {})
+        if oid is None:
+            self.tkconsole.root.bell()
+            return
+        item = debugobj_r.StubObjectTreeItem(self.rpcclt, oid)
+        from idlelib.tree import ScrolledCanvas, TreeNode
+        top = Toplevel(self.tkconsole.root)
+        theme = idleConf.CurrentTheme()
+        background = idleConf.GetHighlight(theme, 'normal')['background']
+        sc = ScrolledCanvas(top, bg=background, highlightthickness=0)
+        sc.frame.pack(expand=1, fill="both")
+        node = TreeNode(sc.canvas, None, item)
+        node.expand()
+        # XXX Should GC the remote tree when closing the window
+
+    gid = 0
+
+    def execsource(self, source):
+        "Like runsource() but assumes complete exec source"
+        filename = self.stuffsource(source)
+        self.execfile(filename, source)
+
+    def execfile(self, filename, source=None):
+        "Execute an existing file"
+        if source is None:
+            with tokenize.open(filename) as fp:
+                source = fp.read()
+                if use_subprocess:
+                    source = (f"__file__ = r'''{os.path.abspath(filename)}'''\n"
+                              + source + "\ndel __file__")
+        try:
+            code = compile(source, filename, "exec")
+        except (OverflowError, SyntaxError):
+            self.tkconsole.resetoutput()
+            print('*** Error in script or command!\n'
+                 'Traceback (most recent call last):',
+                  file=self.tkconsole.stderr)
+            InteractiveInterpreter.showsyntaxerror(self, filename)
+            self.tkconsole.showprompt()
+        else:
+            self.runcode(code)
+
+    def runsource(self, source):
+        "Extend base class method: Stuff the source in the line cache first"
+        filename = self.stuffsource(source)
+        # at the moment, InteractiveInterpreter expects str
+        assert isinstance(source, str)
+        # InteractiveInterpreter.runsource() calls its runcode() method,
+        # which is overridden (see below)
+        return InteractiveInterpreter.runsource(self, source, filename)
+
+    def stuffsource(self, source):
+        "Stuff source in the filename cache"
+        filename = "<pyshell#%d>" % self.gid
+        self.gid = self.gid + 1
+        lines = source.split("\n")
+        linecache.cache[filename] = len(source)+1, 0, lines, filename
+        return filename
+
+    def prepend_syspath(self, filename):
+        "Prepend sys.path with file's directory if not already included"
+        self.runcommand("""if 1:
+            _filename = %r
+            import sys as _sys
+            from os.path import dirname as _dirname
+            _dir = _dirname(_filename)
+            if not _dir in _sys.path:
+                _sys.path.insert(0, _dir)
+            del _filename, _sys, _dirname, _dir
+            \n""" % (filename,))
+
+    def showsyntaxerror(self, filename=None):
+        """Override Interactive Interpreter method: Use Colorizing
+
+        Color the offending position instead of printing it and pointing at it
+        with a caret.
+
+        """
+        tkconsole = self.tkconsole
+        text = tkconsole.text
+        text.tag_remove("ERROR", "1.0", "end")
+        type, value, tb = sys.exc_info()
+        msg = getattr(value, 'msg', '') or value or "<no detail available>"
+        lineno = getattr(value, 'lineno', '') or 1
+        offset = getattr(value, 'offset', '') or 0
+        if offset == 0:
+            lineno += 1 #mark end of offending line
+        if lineno == 1:
+            pos = "iomark + %d chars" % (offset-1)
+        else:
+            pos = "iomark linestart + %d lines + %d chars" % \
+                  (lineno-1, offset-1)
+        tkconsole.colorize_syntax_error(text, pos)
+        tkconsole.resetoutput()
+        self.write("SyntaxError: %s\n" % msg)
+        tkconsole.showprompt()
+
+    def showtraceback(self):
+        "Extend base class method to reset output properly"
+        self.tkconsole.resetoutput()
+        self.checklinecache()
+        InteractiveInterpreter.showtraceback(self)
+        if self.tkconsole.getvar("<<toggle-jit-stack-viewer>>"):
+            self.tkconsole.open_stack_viewer()
+
+    def checklinecache(self):
+        c = linecache.cache
+        for key in list(c.keys()):
+            if key[:1] + key[-1:] != "<>":
+                del c[key]
+
+    def runcommand(self, code):
+        "Run the code without invoking the debugger"
+        # The code better not raise an exception!
+        if self.tkconsole.executing:
+            self.display_executing_dialog()
+            return 0
+        if self.rpcclt:
+            self.rpcclt.remotequeue("exec", "runcode", (code,), {})
+        else:
+            exec(code, self.locals)
+        return 1
+
+    def runcode(self, code):
+        "Override base class method"
+        if self.tkconsole.executing:
+            self.restart_subprocess()
+        self.checklinecache()
+        debugger = self.debugger
+        try:
+            self.tkconsole.beginexecuting()
+            if not debugger and self.rpcclt is not None:
+                self.active_seq = self.rpcclt.asyncqueue("exec", "runcode",
+                                                        (code,), {})
+            elif debugger:
+                debugger.run(code, self.locals)
+            else:
+                exec(code, self.locals)
+        except SystemExit:
+            if not self.tkconsole.closing:
+                if messagebox.askyesno(
+                    "Exit?",
+                    "Do you want to exit altogether?",
+                    default="yes",
+                    parent=self.tkconsole.text):
+                    raise
+                else:
+                    self.showtraceback()
+            else:
+                raise
+        except:
+            if use_subprocess:
+                print("IDLE internal error in runcode()",
+                      file=self.tkconsole.stderr)
+                self.showtraceback()
+                self.tkconsole.endexecuting()
+            else:
+                if self.tkconsole.canceled:
+                    self.tkconsole.canceled = False
+                    print("KeyboardInterrupt", file=self.tkconsole.stderr)
+                else:
+                    self.showtraceback()
+        finally:
+            if not use_subprocess:
+                try:
+                    self.tkconsole.endexecuting()
+                except AttributeError:  # shell may have closed
+                    pass
+
+    def write(self, s):
+        "Override base class method"
+        return self.tkconsole.stderr.write(s)
+
+    def display_port_binding_error(self):
+        messagebox.showerror(
+            "Port Binding Error",
+            "IDLE can't bind to a TCP/IP port, which is necessary to "
+            "communicate with its Python execution server.  This might be "
+            "because no networking is installed on this computer.  "
+            "Run IDLE with the -n command line switch to start without a "
+            "subprocess and refer to Help/IDLE Help 'Running without a "
+            "subprocess' for further details.",
+            parent=self.tkconsole.text)
+
+    def display_no_subprocess_error(self):
+        messagebox.showerror(
+            "Subprocess Connection Error",
+            "IDLE's subprocess didn't make connection.\n"
+            "See the 'Startup failure' section of the IDLE doc, online at\n"
+            "https://docs.python.org/3/library/idle.html#startup-failure",
+            parent=self.tkconsole.text)
+
+    def display_executing_dialog(self):
+        messagebox.showerror(
+            "Already executing",
+            "The Python Shell window is already executing a command; "
+            "please wait until it is finished.",
+            parent=self.tkconsole.text)
+
+
+class PyShell(OutputWindow):
+
+    shell_title = "IDLE Shell " + python_version()
+
+    # Override classes
+    ColorDelegator = ModifiedColorDelegator
+    UndoDelegator = ModifiedUndoDelegator
+
+    # Override menus
+    menu_specs = [
+        ("file", "_File"),
+        ("edit", "_Edit"),
+        ("debug", "_Debug"),
+        ("options", "_Options"),
+        ("window", "_Window"),
+        ("help", "_Help"),
+    ]
+
+    # Extend right-click context menu
+    rmenu_specs = OutputWindow.rmenu_specs + [
+        ("Squeeze", "<<squeeze-current-text>>"),
+    ]
+
+    allow_line_numbers = False
+
+    # New classes
+    from idlelib.history import History
+
+    def __init__(self, flist=None):
+        if use_subprocess:
+            ms = self.menu_specs
+            if ms[2][0] != "shell":
+                ms.insert(2, ("shell", "She_ll"))
+        self.interp = ModifiedInterpreter(self)
+        if flist is None:
+            root = Tk()
+            fixwordbreaks(root)
+            root.withdraw()
+            flist = PyShellFileList(root)
+
+        OutputWindow.__init__(self, flist, None, None)
+
+        self.usetabs = True
+        # indentwidth must be 8 when using tabs.  See note in EditorWindow:
+        self.indentwidth = 8
+
+        self.sys_ps1 = sys.ps1 if hasattr(sys, 'ps1') else '>>> '
+        self.prompt_last_line = self.sys_ps1.split('\n')[-1]
+        self.prompt = self.sys_ps1  # Changes when debug active
+
+        text = self.text
+        text.configure(wrap="char")
+        text.bind("<<newline-and-indent>>", self.enter_callback)
+        text.bind("<<plain-newline-and-indent>>", self.linefeed_callback)
+        text.bind("<<interrupt-execution>>", self.cancel_callback)
+        text.bind("<<end-of-file>>", self.eof_callback)
+        text.bind("<<open-stack-viewer>>", self.open_stack_viewer)
+        text.bind("<<toggle-debugger>>", self.toggle_debugger)
+        text.bind("<<toggle-jit-stack-viewer>>", self.toggle_jit_stack_viewer)
+        if use_subprocess:
+            text.bind("<<view-restart>>", self.view_restart_mark)
+            text.bind("<<restart-shell>>", self.restart_shell)
+        squeezer = self.Squeezer(self)
+        text.bind("<<squeeze-current-text>>",
+                  squeezer.squeeze_current_text_event)
+
+        self.save_stdout = sys.stdout
+        self.save_stderr = sys.stderr
+        self.save_stdin = sys.stdin
+        from idlelib import iomenu
+        self.stdin = StdInputFile(self, "stdin",
+                                  iomenu.encoding, iomenu.errors)
+        self.stdout = StdOutputFile(self, "stdout",
+                                    iomenu.encoding, iomenu.errors)
+        self.stderr = StdOutputFile(self, "stderr",
+                                    iomenu.encoding, "backslashreplace")
+        self.console = StdOutputFile(self, "console",
+                                     iomenu.encoding, iomenu.errors)
+        if not use_subprocess:
+            sys.stdout = self.stdout
+            sys.stderr = self.stderr
+            sys.stdin = self.stdin
+        try:
+            # page help() text to shell.
+            import pydoc # import must be done here to capture i/o rebinding.
+            # XXX KBK 27Dec07 use text viewer someday, but must work w/o subproc
+            pydoc.pager = pydoc.plainpager
+        except:
+            sys.stderr = sys.__stderr__
+            raise
+        #
+        self.history = self.History(self.text)
+        #
+        self.pollinterval = 50  # millisec
+
+    def get_standard_extension_names(self):
+        return idleConf.GetExtensions(shell_only=True)
+
+    reading = False
+    executing = False
+    canceled = False
+    endoffile = False
+    closing = False
+    _stop_readline_flag = False
+
+    def set_warning_stream(self, stream):
+        global warning_stream
+        warning_stream = stream
+
+    def get_warning_stream(self):
+        return warning_stream
+
+    def toggle_debugger(self, event=None):
+        if self.executing:
+            messagebox.showerror("Don't debug now",
+                "You can only toggle the debugger when idle",
+                parent=self.text)
+            self.set_debugger_indicator()
+            return "break"
+        else:
+            db = self.interp.getdebugger()
+            if db:
+                self.close_debugger()
+            else:
+                self.open_debugger()
+
+    def set_debugger_indicator(self):
+        db = self.interp.getdebugger()
+        self.setvar("<<toggle-debugger>>", not not db)
+
+    def toggle_jit_stack_viewer(self, event=None):
+        pass # All we need is the variable
+
+    def close_debugger(self):
+        db = self.interp.getdebugger()
+        if db:
+            self.interp.setdebugger(None)
+            db.close()
+            if self.interp.rpcclt:
+                debugger_r.close_remote_debugger(self.interp.rpcclt)
+            self.resetoutput()
+            self.console.write("[DEBUG OFF]\n")
+            self.prompt = self.sys_ps1
+            self.showprompt()
+        self.set_debugger_indicator()
+
+    def open_debugger(self):
+        if self.interp.rpcclt:
+            dbg_gui = debugger_r.start_remote_debugger(self.interp.rpcclt,
+                                                           self)
+        else:
+            dbg_gui = debugger.Debugger(self)
+        self.interp.setdebugger(dbg_gui)
+        dbg_gui.load_breakpoints()
+        self.prompt = "[DEBUG ON]\n" + self.sys_ps1
+        self.showprompt()
+        self.set_debugger_indicator()
+
+    def debug_menu_postcommand(self):
+        state = 'disabled' if self.executing else 'normal'
+        self.update_menu_state('debug', '*tack*iewer', state)
+
+    def beginexecuting(self):
+        "Helper for ModifiedInterpreter"
+        self.resetoutput()
+        self.executing = True
+
+    def endexecuting(self):
+        "Helper for ModifiedInterpreter"
+        self.executing = False
+        self.canceled = False
+        self.showprompt()
+
+    def close(self):
+        "Extend EditorWindow.close()"
+        if self.executing:
+            response = messagebox.askokcancel(
+                "Kill?",
+                "Your program is still running!\n Do you want to kill it?",
+                default="ok",
+                parent=self.text)
+            if response is False:
+                return "cancel"
+        self.stop_readline()
+        self.canceled = True
+        self.closing = True
+        return EditorWindow.close(self)
+
+    def _close(self):
+        "Extend EditorWindow._close(), shut down debugger and execution server"
+        self.close_debugger()
+        if use_subprocess:
+            self.interp.kill_subprocess()
+        # Restore std streams
+        sys.stdout = self.save_stdout
+        sys.stderr = self.save_stderr
+        sys.stdin = self.save_stdin
+        # Break cycles
+        self.interp = None
+        self.console = None
+        self.flist.pyshell = None
+        self.history = None
+        EditorWindow._close(self)
+
+    def ispythonsource(self, filename):
+        "Override EditorWindow method: never remove the colorizer"
+        return True
+
+    def short_title(self):
+        return self.shell_title
+
+    COPYRIGHT = \
+          'Type "help", "copyright", "credits" or "license()" for more information.'
+
+    def begin(self):
+        self.text.mark_set("iomark", "insert")
+        self.resetoutput()
+        if use_subprocess:
+            nosub = ''
+            client = self.interp.start_subprocess()
+            if not client:
+                self.close()
+                return False
+        else:
+            nosub = ("==== No Subprocess ====\n\n" +
+                    "WARNING: Running IDLE without a Subprocess is deprecated\n" +
+                    "and will be removed in a later version. See Help/IDLE Help\n" +
+                    "for details.\n\n")
+            sys.displayhook = rpc.displayhook
+
+        self.write("Python %s on %s\n%s\n%s" %
+                   (sys.version, sys.platform, self.COPYRIGHT, nosub))
+        self.text.focus_force()
+        self.showprompt()
+        # User code should use separate default Tk root window
+        import tkinter
+        tkinter._support_default_root = True
+        tkinter._default_root = None
+        return True
+
+    def stop_readline(self):
+        if not self.reading:  # no nested mainloop to exit.
+            return
+        self._stop_readline_flag = True
+        self.top.quit()
+
+    def readline(self):
+        save = self.reading
+        try:
+            self.reading = True
+            self.top.mainloop()  # nested mainloop()
+        finally:
+            self.reading = save
+        if self._stop_readline_flag:
+            self._stop_readline_flag = False
+            return ""
+        line = self.text.get("iomark", "end-1c")
+        if len(line) == 0:  # may be EOF if we quit our mainloop with Ctrl-C
+            line = "\n"
+        self.resetoutput()
+        if self.canceled:
+            self.canceled = False
+            if not use_subprocess:
+                raise KeyboardInterrupt
+        if self.endoffile:
+            self.endoffile = False
+            line = ""
+        return line
+
+    def isatty(self):
+        return True
+
+    def cancel_callback(self, event=None):
+        try:
+            if self.text.compare("sel.first", "!=", "sel.last"):
+                return # Active selection -- always use default binding
+        except:
+            pass
+        if not (self.executing or self.reading):
+            self.resetoutput()
+            self.interp.write("KeyboardInterrupt\n")
+            self.showprompt()
+            return "break"
+        self.endoffile = False
+        self.canceled = True
+        if (self.executing and self.interp.rpcclt):
+            if self.interp.getdebugger():
+                self.interp.restart_subprocess()
+            else:
+                self.interp.interrupt_subprocess()
+        if self.reading:
+            self.top.quit()  # exit the nested mainloop() in readline()
+        return "break"
+
+    def eof_callback(self, event):
+        if self.executing and not self.reading:
+            return # Let the default binding (delete next char) take over
+        if not (self.text.compare("iomark", "==", "insert") and
+                self.text.compare("insert", "==", "end-1c")):
+            return # Let the default binding (delete next char) take over
+        if not self.executing:
+            self.resetoutput()
+            self.close()
+        else:
+            self.canceled = False
+            self.endoffile = True
+            self.top.quit()
+        return "break"
+
+    def linefeed_callback(self, event):
+        # Insert a linefeed without entering anything (still autoindented)
+        if self.reading:
+            self.text.insert("insert", "\n")
+            self.text.see("insert")
+        else:
+            self.newline_and_indent_event(event)
+        return "break"
+
+    def enter_callback(self, event):
+        if self.executing and not self.reading:
+            return # Let the default binding (insert '\n') take over
+        # If some text is selected, recall the selection
+        # (but only if this before the I/O mark)
+        try:
+            sel = self.text.get("sel.first", "sel.last")
+            if sel:
+                if self.text.compare("sel.last", "<=", "iomark"):
+                    self.recall(sel, event)
+                    return "break"
+        except:
+            pass
+        # If we're strictly before the line containing iomark, recall
+        # the current line, less a leading prompt, less leading or
+        # trailing whitespace
+        if self.text.compare("insert", "<", "iomark linestart"):
+            # Check if there's a relevant stdin range -- if so, use it
+            prev = self.text.tag_prevrange("stdin", "insert")
+            if prev and self.text.compare("insert", "<", prev[1]):
+                self.recall(self.text.get(prev[0], prev[1]), event)
+                return "break"
+            next = self.text.tag_nextrange("stdin", "insert")
+            if next and self.text.compare("insert lineend", ">=", next[0]):
+                self.recall(self.text.get(next[0], next[1]), event)
+                return "break"
+            # No stdin mark -- just get the current line, less any prompt
+            indices = self.text.tag_nextrange("console", "insert linestart")
+            if indices and \
+               self.text.compare(indices[0], "<=", "insert linestart"):
+                self.recall(self.text.get(indices[1], "insert lineend"), event)
+            else:
+                self.recall(self.text.get("insert linestart", "insert lineend"), event)
+            return "break"
+        # If we're between the beginning of the line and the iomark, i.e.
+        # in the prompt area, move to the end of the prompt
+        if self.text.compare("insert", "<", "iomark"):
+            self.text.mark_set("insert", "iomark")
+        # If we're in the current input and there's only whitespace
+        # beyond the cursor, erase that whitespace first
+        s = self.text.get("insert", "end-1c")
+        if s and not s.strip():
+            self.text.delete("insert", "end-1c")
+        # If we're in the current input before its last line,
+        # insert a newline right at the insert point
+        if self.text.compare("insert", "<", "end-1c linestart"):
+            self.newline_and_indent_event(event)
+            return "break"
+        # We're in the last line; append a newline and submit it
+        self.text.mark_set("insert", "end-1c")
+        if self.reading:
+            self.text.insert("insert", "\n")
+            self.text.see("insert")
+        else:
+            self.newline_and_indent_event(event)
+        self.text.tag_add("stdin", "iomark", "end-1c")
+        self.text.update_idletasks()
+        if self.reading:
+            self.top.quit() # Break out of recursive mainloop()
+        else:
+            self.runit()
+        return "break"
+
+    def recall(self, s, event):
+        # remove leading and trailing empty or whitespace lines
+        s = re.sub(r'^\s*\n', '' , s)
+        s = re.sub(r'\n\s*$', '', s)
+        lines = s.split('\n')
+        self.text.undo_block_start()
+        try:
+            self.text.tag_remove("sel", "1.0", "end")
+            self.text.mark_set("insert", "end-1c")
+            prefix = self.text.get("insert linestart", "insert")
+            if prefix.rstrip().endswith(':'):
+                self.newline_and_indent_event(event)
+                prefix = self.text.get("insert linestart", "insert")
+            self.text.insert("insert", lines[0].strip())
+            if len(lines) > 1:
+                orig_base_indent = re.search(r'^([ \t]*)', lines[0]).group(0)
+                new_base_indent  = re.search(r'^([ \t]*)', prefix).group(0)
+                for line in lines[1:]:
+                    if line.startswith(orig_base_indent):
+                        # replace orig base indentation with new indentation
+                        line = new_base_indent + line[len(orig_base_indent):]
+                    self.text.insert('insert', '\n'+line.rstrip())
+        finally:
+            self.text.see("insert")
+            self.text.undo_block_stop()
+
+    def runit(self):
+        line = self.text.get("iomark", "end-1c")
+        # Strip off last newline and surrounding whitespace.
+        # (To allow you to hit return twice to end a statement.)
+        i = len(line)
+        while i > 0 and line[i-1] in " \t":
+            i = i-1
+        if i > 0 and line[i-1] == "\n":
+            i = i-1
+        while i > 0 and line[i-1] in " \t":
+            i = i-1
+        line = line[:i]
+        self.interp.runsource(line)
+
+    def open_stack_viewer(self, event=None):
+        if self.interp.rpcclt:
+            return self.interp.remote_stack_viewer()
+        try:
+            sys.last_traceback
+        except:
+            messagebox.showerror("No stack trace",
+                "There is no stack trace yet.\n"
+                "(sys.last_traceback is not defined)",
+                parent=self.text)
+            return
+        from idlelib.stackviewer import StackBrowser
+        StackBrowser(self.root, self.flist)
+
+    def view_restart_mark(self, event=None):
+        self.text.see("iomark")
+        self.text.see("restart")
+
+    def restart_shell(self, event=None):
+        "Callback for Run/Restart Shell Cntl-F6"
+        self.interp.restart_subprocess(with_cwd=True)
+
+    def showprompt(self):
+        self.resetoutput()
+        self.console.write(self.prompt)
+        self.text.mark_set("insert", "end-1c")
+        self.set_line_and_column()
+        self.io.reset_undo()
+
+    def show_warning(self, msg):
+        width = self.interp.tkconsole.width
+        wrapper = TextWrapper(width=width, tabsize=8, expand_tabs=True)
+        wrapped_msg = '\n'.join(wrapper.wrap(msg))
+        if not wrapped_msg.endswith('\n'):
+            wrapped_msg += '\n'
+        self.per.bottom.insert("iomark linestart", wrapped_msg, "stderr")
+
+    def resetoutput(self):
+        source = self.text.get("iomark", "end-1c")
+        if self.history:
+            self.history.store(source)
+        if self.text.get("end-2c") != "\n":
+            self.text.insert("end-1c", "\n")
+        self.text.mark_set("iomark", "end-1c")
+        self.set_line_and_column()
+        self.ctip.remove_calltip_window()
+
+    def write(self, s, tags=()):
+        try:
+            self.text.mark_gravity("iomark", "right")
+            count = OutputWindow.write(self, s, tags, "iomark")
+            self.text.mark_gravity("iomark", "left")
+        except:
+            raise ###pass  # ### 11Aug07 KBK if we are expecting exceptions
+                           # let's find out what they are and be specific.
+        if self.canceled:
+            self.canceled = False
+            if not use_subprocess:
+                raise KeyboardInterrupt
+        return count
+
+    def rmenu_check_cut(self):
+        try:
+            if self.text.compare('sel.first', '<', 'iomark'):
+                return 'disabled'
+        except TclError: # no selection, so the index 'sel.first' doesn't exist
+            return 'disabled'
+        return super().rmenu_check_cut()
+
+    def rmenu_check_paste(self):
+        if self.text.compare('insert','<','iomark'):
+            return 'disabled'
+        return super().rmenu_check_paste()
+
+
+def fix_x11_paste(root):
+    "Make paste replace selection on x11.  See issue #5124."
+    if root._windowingsystem == 'x11':
+        for cls in 'Text', 'Entry', 'Spinbox':
+            root.bind_class(
+                cls,
+                '<<Paste>>',
+                'catch {%W delete sel.first sel.last}\n' +
+                        root.bind_class(cls, '<<Paste>>'))
+
+
+usage_msg = """\
+
+USAGE: idle  [-deins] [-t title] [file]*
+       idle  [-dns] [-t title] (-c cmd | -r file) [arg]*
+       idle  [-dns] [-t title] - [arg]*
+
+  -h         print this help message and exit
+  -n         run IDLE without a subprocess (DEPRECATED,
+             see Help/IDLE Help for details)
+
+The following options will override the IDLE 'settings' configuration:
+
+  -e         open an edit window
+  -i         open a shell window
+
+The following options imply -i and will open a shell:
+
+  -c cmd     run the command in a shell, or
+  -r file    run script from file
+
+  -d         enable the debugger
+  -s         run $IDLESTARTUP or $PYTHONSTARTUP before anything else
+  -t title   set title of shell window
+
+A default edit window will be bypassed when -c, -r, or - are used.
+
+[arg]* are passed to the command (-c) or script (-r) in sys.argv[1:].
+
+Examples:
+
+idle
+        Open an edit window or shell depending on IDLE's configuration.
+
+idle foo.py foobar.py
+        Edit the files, also open a shell if configured to start with shell.
+
+idle -est "Baz" foo.py
+        Run $IDLESTARTUP or $PYTHONSTARTUP, edit foo.py, and open a shell
+        window with the title "Baz".
+
+idle -c "import sys; print(sys.argv)" "foo"
+        Open a shell window and run the command, passing "-c" in sys.argv[0]
+        and "foo" in sys.argv[1].
+
+idle -d -s -r foo.py "Hello World"
+        Open a shell window, run a startup script, enable the debugger, and
+        run foo.py, passing "foo.py" in sys.argv[0] and "Hello World" in
+        sys.argv[1].
+
+echo "import sys; print(sys.argv)" | idle - "foobar"
+        Open a shell window, run the script piped in, passing '' in sys.argv[0]
+        and "foobar" in sys.argv[1].
+"""
+
+def main():
+    import getopt
+    from platform import system
+    from idlelib import testing  # bool value
+    from idlelib import macosx
+
+    global flist, root, use_subprocess
+
+    capture_warnings(True)
+    use_subprocess = True
+    enable_shell = False
+    enable_edit = False
+    debug = False
+    cmd = None
+    script = None
+    startup = False
+    try:
+        opts, args = getopt.getopt(sys.argv[1:], "c:deihnr:st:")
+    except getopt.error as msg:
+        print("Error: %s\n%s" % (msg, usage_msg), file=sys.stderr)
+        sys.exit(2)
+    for o, a in opts:
+        if o == '-c':
+            cmd = a
+            enable_shell = True
+        if o == '-d':
+            debug = True
+            enable_shell = True
+        if o == '-e':
+            enable_edit = True
+        if o == '-h':
+            sys.stdout.write(usage_msg)
+            sys.exit()
+        if o == '-i':
+            enable_shell = True
+        if o == '-n':
+            print(" Warning: running IDLE without a subprocess is deprecated.",
+                  file=sys.stderr)
+            use_subprocess = False
+        if o == '-r':
+            script = a
+            if os.path.isfile(script):
+                pass
+            else:
+                print("No script file: ", script)
+                sys.exit()
+            enable_shell = True
+        if o == '-s':
+            startup = True
+            enable_shell = True
+        if o == '-t':
+            PyShell.shell_title = a
+            enable_shell = True
+    if args and args[0] == '-':
+        cmd = sys.stdin.read()
+        enable_shell = True
+    # process sys.argv and sys.path:
+    for i in range(len(sys.path)):
+        sys.path[i] = os.path.abspath(sys.path[i])
+    if args and args[0] == '-':
+        sys.argv = [''] + args[1:]
+    elif cmd:
+        sys.argv = ['-c'] + args
+    elif script:
+        sys.argv = [script] + args
+    elif args:
+        enable_edit = True
+        pathx = []
+        for filename in args:
+            pathx.append(os.path.dirname(filename))
+        for dir in pathx:
+            dir = os.path.abspath(dir)
+            if not dir in sys.path:
+                sys.path.insert(0, dir)
+    else:
+        dir = os.getcwd()
+        if dir not in sys.path:
+            sys.path.insert(0, dir)
+    # check the IDLE settings configuration (but command line overrides)
+    edit_start = idleConf.GetOption('main', 'General',
+                                    'editor-on-startup', type='bool')
+    enable_edit = enable_edit or edit_start
+    enable_shell = enable_shell or not enable_edit
+
+    # Setup root.  Don't break user code run in IDLE process.
+    # Don't change environment when testing.
+    if use_subprocess and not testing:
+        NoDefaultRoot()
+    root = Tk(className="Idle")
+    root.withdraw()
+    from idlelib.run import fix_scaling
+    fix_scaling(root)
+
+    # set application icon
+    icondir = os.path.join(os.path.dirname(__file__), 'Icons')
+    if system() == 'Windows':
+        iconfile = os.path.join(icondir, 'idle.ico')
+        root.wm_iconbitmap(default=iconfile)
+    elif not macosx.isAquaTk():
+        if TkVersion >= 8.6:
+            ext = '.png'
+            sizes = (16, 32, 48, 256)
+        else:
+            ext = '.gif'
+            sizes = (16, 32, 48)
+        iconfiles = [os.path.join(icondir, 'idle_%d%s' % (size, ext))
+                     for size in sizes]
+        icons = [PhotoImage(master=root, file=iconfile)
+                 for iconfile in iconfiles]
+        root.wm_iconphoto(True, *icons)
+
+    # start editor and/or shell windows:
+    fixwordbreaks(root)
+    fix_x11_paste(root)
+    flist = PyShellFileList(root)
+    macosx.setupApp(root, flist)
+
+    if enable_edit:
+        if not (cmd or script):
+            for filename in args[:]:
+                if flist.open(filename) is None:
+                    # filename is a directory actually, disconsider it
+                    args.remove(filename)
+            if not args:
+                flist.new()
+
+    if enable_shell:
+        shell = flist.open_shell()
+        if not shell:
+            return # couldn't open shell
+        if macosx.isAquaTk() and flist.dict:
+            # On OSX: when the user has double-clicked on a file that causes
+            # IDLE to be launched the shell window will open just in front of
+            # the file she wants to see. Lower the interpreter window when
+            # there are open files.
+            shell.top.lower()
+    else:
+        shell = flist.pyshell
+
+    # Handle remaining options. If any of these are set, enable_shell
+    # was set also, so shell must be true to reach here.
+    if debug:
+        shell.open_debugger()
+    if startup:
+        filename = os.environ.get("IDLESTARTUP") or \
+                   os.environ.get("PYTHONSTARTUP")
+        if filename and os.path.isfile(filename):
+            shell.interp.execfile(filename)
+    if cmd or script:
+        shell.interp.runcommand("""if 1:
+            import sys as _sys
+            _sys.argv = %r
+            del _sys
+            \n""" % (sys.argv,))
+        if cmd:
+            shell.interp.execsource(cmd)
+        elif script:
+            shell.interp.prepend_syspath(script)
+            shell.interp.execfile(script)
+    elif shell:
+        # If there is a shell window and no cmd or script in progress,
+        # check for problematic issues and print warning message(s) in
+        # the IDLE shell window; this is less intrusive than always
+        # opening a separate window.
+
+        # Warn if using a problematic OS X Tk version.
+        tkversionwarning = macosx.tkVersionWarning(root)
+        if tkversionwarning:
+            shell.show_warning(tkversionwarning)
+
+        # Warn if the "Prefer tabs when opening documents" system
+        # preference is set to "Always".
+        prefer_tabs_preference_warning = macosx.preferTabsPreferenceWarning()
+        if prefer_tabs_preference_warning:
+            shell.show_warning(prefer_tabs_preference_warning)
+
+    while flist.inversedict:  # keep IDLE running while files are open.
+        root.mainloop()
+    root.destroy()
+    capture_warnings(False)
+
+if __name__ == "__main__":
+    main()
+
+capture_warnings(False)  # Make sure turned off; see issue 18081
diff --git a/rootfs/usr/lib/python3.8/idlelib/query.py b/rootfs/usr/lib/python3.8/idlelib/query.py
new file mode 100644
index 0000000..fefa5aa
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/query.py
@@ -0,0 +1,392 @@
+"""
+Dialogs that query users and verify the answer before accepting.
+
+Query is the generic base class for a popup dialog.
+The user must either enter a valid answer or close the dialog.
+Entries are validated when <Return> is entered or [Ok] is clicked.
+Entries are ignored when [Cancel] or [X] are clicked.
+The 'return value' is .result set to either a valid answer or None.
+
+Subclass SectionName gets a name for a new config file section.
+Configdialog uses it for new highlight theme and keybinding set names.
+Subclass ModuleName gets a name for File => Open Module.
+Subclass HelpSource gets menu item and path for additions to Help menu.
+"""
+# Query and Section name result from splitting GetCfgSectionNameDialog
+# of configSectionNameDialog.py (temporarily config_sec.py) into
+# generic and specific parts.  3.6 only, July 2016.
+# ModuleName.entry_ok came from editor.EditorWindow.load_module.
+# HelpSource was extracted from configHelpSourceEdit.py (temporarily
+# config_help.py), with darwin code moved from ok to path_ok.
+
+import importlib.util, importlib.abc
+import os
+import shlex
+from sys import executable, platform  # Platform is set for one test.
+
+from tkinter import Toplevel, StringVar, BooleanVar, W, E, S
+from tkinter.ttk import Frame, Button, Entry, Label, Checkbutton
+from tkinter import filedialog
+from tkinter.font import Font
+from tkinter.simpledialog import _setup_dialog
+
+class Query(Toplevel):
+    """Base class for getting verified answer from a user.
+
+    For this base class, accept any non-blank string.
+    """
+    def __init__(self, parent, title, message, *, text0='', used_names={},
+                 _htest=False, _utest=False):
+        """Create modal popup, return when destroyed.
+
+        Additional subclass init must be done before this unless
+        _utest=True is passed to suppress wait_window().
+
+        title - string, title of popup dialog
+        message - string, informational message to display
+        text0 - initial value for entry
+        used_names - names already in use
+        _htest - bool, change box location when running htest
+        _utest - bool, leave window hidden and not modal
+        """
+        self.parent = parent  # Needed for Font call.
+        self.message = message
+        self.text0 = text0
+        self.used_names = used_names
+
+        Toplevel.__init__(self, parent)
+        self.withdraw()  # Hide while configuring, especially geometry.
+        self.title(title)
+        self.transient(parent)
+        if not _utest:  # Otherwise fail when directly run unittest.
+            self.grab_set()
+
+        _setup_dialog(self)
+        if self._windowingsystem == 'aqua':
+            self.bind("<Command-.>", self.cancel)
+        self.bind('<Key-Escape>', self.cancel)
+        self.protocol("WM_DELETE_WINDOW", self.cancel)
+        self.bind('<Key-Return>', self.ok)
+        self.bind("<KP_Enter>", self.ok)
+
+        self.create_widgets()
+        self.update_idletasks()  # Need here for winfo_reqwidth below.
+        self.geometry(  # Center dialog over parent (or below htest box).
+                "+%d+%d" % (
+                    parent.winfo_rootx() +
+                    (parent.winfo_width()/2 - self.winfo_reqwidth()/2),
+                    parent.winfo_rooty() +
+                    ((parent.winfo_height()/2 - self.winfo_reqheight()/2)
+                    if not _htest else 150)
+                ) )
+        self.resizable(height=False, width=False)
+
+        if not _utest:
+            self.deiconify()  # Unhide now that geometry set.
+            self.wait_window()
+
+    def create_widgets(self, ok_text='OK'):  # Do not replace.
+        """Create entry (rows, extras, buttons.
+
+        Entry stuff on rows 0-2, spanning cols 0-2.
+        Buttons on row 99, cols 1, 2.
+        """
+        # Bind to self the widgets needed for entry_ok or unittest.
+        self.frame = frame = Frame(self, padding=10)
+        frame.grid(column=0, row=0, sticky='news')
+        frame.grid_columnconfigure(0, weight=1)
+
+        entrylabel = Label(frame, anchor='w', justify='left',
+                           text=self.message)
+        self.entryvar = StringVar(self, self.text0)
+        self.entry = Entry(frame, width=30, textvariable=self.entryvar)
+        self.entry.focus_set()
+        self.error_font = Font(name='TkCaptionFont',
+                               exists=True, root=self.parent)
+        self.entry_error = Label(frame, text=' ', foreground='red',
+                                 font=self.error_font)
+        # Display or blank error by setting ['text'] =.
+        entrylabel.grid(column=0, row=0, columnspan=3, padx=5, sticky=W)
+        self.entry.grid(column=0, row=1, columnspan=3, padx=5, sticky=W+E,
+                        pady=[10,0])
+        self.entry_error.grid(column=0, row=2, columnspan=3, padx=5,
+                              sticky=W+E)
+
+        self.create_extra()
+
+        self.button_ok = Button(
+                frame, text=ok_text, default='active', command=self.ok)
+        self.button_cancel = Button(
+                frame, text='Cancel', command=self.cancel)
+
+        self.button_ok.grid(column=1, row=99, padx=5)
+        self.button_cancel.grid(column=2, row=99, padx=5)
+
+    def create_extra(self): pass  # Override to add widgets.
+
+    def showerror(self, message, widget=None):
+        #self.bell(displayof=self)
+        (widget or self.entry_error)['text'] = 'ERROR: ' + message
+
+    def entry_ok(self):  # Example: usually replace.
+        "Return non-blank entry or None."
+        entry = self.entry.get().strip()
+        if not entry:
+            self.showerror('blank line.')
+            return None
+        return entry
+
+    def ok(self, event=None):  # Do not replace.
+        '''If entry is valid, bind it to 'result' and destroy tk widget.
+
+        Otherwise leave dialog open for user to correct entry or cancel.
+        '''
+        self.entry_error['text'] = ''
+        entry = self.entry_ok()
+        if entry is not None:
+            self.result = entry
+            self.destroy()
+        else:
+            # [Ok] moves focus.  (<Return> does not.)  Move it back.
+            self.entry.focus_set()
+
+    def cancel(self, event=None):  # Do not replace.
+        "Set dialog result to None and destroy tk widget."
+        self.result = None
+        self.destroy()
+
+    def destroy(self):
+        self.grab_release()
+        super().destroy()
+
+
+class SectionName(Query):
+    "Get a name for a config file section name."
+    # Used in ConfigDialog.GetNewKeysName, .GetNewThemeName (837)
+
+    def __init__(self, parent, title, message, used_names,
+                 *, _htest=False, _utest=False):
+        super().__init__(parent, title, message, used_names=used_names,
+                         _htest=_htest, _utest=_utest)
+
+    def entry_ok(self):
+        "Return sensible ConfigParser section name or None."
+        name = self.entry.get().strip()
+        if not name:
+            self.showerror('no name specified.')
+            return None
+        elif len(name)>30:
+            self.showerror('name is longer than 30 characters.')
+            return None
+        elif name in self.used_names:
+            self.showerror('name is already in use.')
+            return None
+        return name
+
+
+class ModuleName(Query):
+    "Get a module name for Open Module menu entry."
+    # Used in open_module (editor.EditorWindow until move to iobinding).
+
+    def __init__(self, parent, title, message, text0,
+                 *, _htest=False, _utest=False):
+        super().__init__(parent, title, message, text0=text0,
+                       _htest=_htest, _utest=_utest)
+
+    def entry_ok(self):
+        "Return entered module name as file path or None."
+        name = self.entry.get().strip()
+        if not name:
+            self.showerror('no name specified.')
+            return None
+        # XXX Ought to insert current file's directory in front of path.
+        try:
+            spec = importlib.util.find_spec(name)
+        except (ValueError, ImportError) as msg:
+            self.showerror(str(msg))
+            return None
+        if spec is None:
+            self.showerror("module not found.")
+            return None
+        if not isinstance(spec.loader, importlib.abc.SourceLoader):
+            self.showerror("not a source-based module.")
+            return None
+        try:
+            file_path = spec.loader.get_filename(name)
+        except AttributeError:
+            self.showerror("loader does not support get_filename.")
+            return None
+        except ImportError:
+            # Some special modules require this (e.g. os.path)
+            try:
+                file_path = spec.loader.get_filename()
+            except TypeError:
+                self.showerror("loader failed to get filename.")
+                return None
+        return file_path
+
+
+class Goto(Query):
+    "Get a positive line number for editor Go To Line."
+    # Used in editor.EditorWindow.goto_line_event.
+
+    def entry_ok(self):
+        try:
+            lineno = int(self.entry.get())
+        except ValueError:
+            self.showerror('not a base 10 integer.')
+            return None
+        if lineno <= 0:
+            self.showerror('not a positive integer.')
+            return None
+        return lineno
+
+
+class HelpSource(Query):
+    "Get menu name and help source for Help menu."
+    # Used in ConfigDialog.HelpListItemAdd/Edit, (941/9)
+
+    def __init__(self, parent, title, *, menuitem='', filepath='',
+                 used_names={}, _htest=False, _utest=False):
+        """Get menu entry and url/local file for Additional Help.
+
+        User enters a name for the Help resource and a web url or file
+        name. The user can browse for the file.
+        """
+        self.filepath = filepath
+        message = 'Name for item on Help menu:'
+        super().__init__(
+                parent, title, message, text0=menuitem,
+                used_names=used_names, _htest=_htest, _utest=_utest)
+
+    def create_extra(self):
+        "Add path widjets to rows 10-12."
+        frame = self.frame
+        pathlabel = Label(frame, anchor='w', justify='left',
+                          text='Help File Path: Enter URL or browse for file')
+        self.pathvar = StringVar(self, self.filepath)
+        self.path = Entry(frame, textvariable=self.pathvar, width=40)
+        browse = Button(frame, text='Browse', width=8,
+                        command=self.browse_file)
+        self.path_error = Label(frame, text=' ', foreground='red',
+                                font=self.error_font)
+
+        pathlabel.grid(column=0, row=10, columnspan=3, padx=5, pady=[10,0],
+                       sticky=W)
+        self.path.grid(column=0, row=11, columnspan=2, padx=5, sticky=W+E,
+                       pady=[10,0])
+        browse.grid(column=2, row=11, padx=5, sticky=W+S)
+        self.path_error.grid(column=0, row=12, columnspan=3, padx=5,
+                             sticky=W+E)
+
+    def askfilename(self, filetypes, initdir, initfile):  # htest #
+        # Extracted from browse_file so can mock for unittests.
+        # Cannot unittest as cannot simulate button clicks.
+        # Test by running htest, such as by running this file.
+        return filedialog.Open(parent=self, filetypes=filetypes)\
+               .show(initialdir=initdir, initialfile=initfile)
+
+    def browse_file(self):
+        filetypes = [
+            ("HTML Files", "*.htm *.html", "TEXT"),
+            ("PDF Files", "*.pdf", "TEXT"),
+            ("Windows Help Files", "*.chm"),
+            ("Text Files", "*.txt", "TEXT"),
+            ("All Files", "*")]
+        path = self.pathvar.get()
+        if path:
+            dir, base = os.path.split(path)
+        else:
+            base = None
+            if platform[:3] == 'win':
+                dir = os.path.join(os.path.dirname(executable), 'Doc')
+                if not os.path.isdir(dir):
+                    dir = os.getcwd()
+            else:
+                dir = os.getcwd()
+        file = self.askfilename(filetypes, dir, base)
+        if file:
+            self.pathvar.set(file)
+
+    item_ok = SectionName.entry_ok  # localize for test override
+
+    def path_ok(self):
+        "Simple validity check for menu file path"
+        path = self.path.get().strip()
+        if not path: #no path specified
+            self.showerror('no help file path specified.', self.path_error)
+            return None
+        elif not path.startswith(('www.', 'http')):
+            if path[:5] == 'file:':
+                path = path[5:]
+            if not os.path.exists(path):
+                self.showerror('help file path does not exist.',
+                               self.path_error)
+                return None
+            if platform == 'darwin':  # for Mac Safari
+                path =  "file://" + path
+        return path
+
+    def entry_ok(self):
+        "Return apparently valid (name, path) or None"
+        self.path_error['text'] = ''
+        name = self.item_ok()
+        path = self.path_ok()
+        return None if name is None or path is None else (name, path)
+
+class CustomRun(Query):
+    """Get settings for custom run of module.
+
+    1. Command line arguments to extend sys.argv.
+    2. Whether to restart Shell or not.
+    """
+    # Used in runscript.run_custom_event
+
+    def __init__(self, parent, title, *, cli_args=[],
+                 _htest=False, _utest=False):
+        """cli_args is a list of strings.
+
+        The list is assigned to the default Entry StringVar.
+        The strings are displayed joined by ' ' for display.
+        """
+        message = 'Command Line Arguments for sys.argv:'
+        super().__init__(
+                parent, title, message, text0=cli_args,
+                _htest=_htest, _utest=_utest)
+
+    def create_extra(self):
+        "Add run mode on rows 10-12."
+        frame = self.frame
+        self.restartvar = BooleanVar(self, value=True)
+        restart = Checkbutton(frame, variable=self.restartvar, onvalue=True,
+                              offvalue=False, text='Restart shell')
+        self.args_error = Label(frame, text=' ', foreground='red',
+                                font=self.error_font)
+
+        restart.grid(column=0, row=10, columnspan=3, padx=5, sticky='w')
+        self.args_error.grid(column=0, row=12, columnspan=3, padx=5,
+                             sticky='we')
+
+    def cli_args_ok(self):
+        "Validity check and parsing for command line arguments."
+        cli_string = self.entry.get().strip()
+        try:
+            cli_args = shlex.split(cli_string, posix=True)
+        except ValueError as err:
+            self.showerror(str(err))
+            return None
+        return cli_args
+
+    def entry_ok(self):
+        "Return apparently valid (cli_args, restart) or None."
+        cli_args = self.cli_args_ok()
+        restart = self.restartvar.get()
+        return None if cli_args is None else (cli_args, restart)
+
+
+if __name__ == '__main__':
+    from unittest import main
+    main('idlelib.idle_test.test_query', verbosity=2, exit=False)
+
+    from idlelib.idle_test.htest import run
+    run(Query, HelpSource, CustomRun)
diff --git a/rootfs/usr/lib/python3.8/idlelib/redirector.py b/rootfs/usr/lib/python3.8/idlelib/redirector.py
new file mode 100644
index 0000000..9ab34c5
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/redirector.py
@@ -0,0 +1,174 @@
+from tkinter import TclError
+
+class WidgetRedirector:
+    """Support for redirecting arbitrary widget subcommands.
+
+    Some Tk operations don't normally pass through tkinter.  For example, if a
+    character is inserted into a Text widget by pressing a key, a default Tk
+    binding to the widget's 'insert' operation is activated, and the Tk library
+    processes the insert without calling back into tkinter.
+
+    Although a binding to <Key> could be made via tkinter, what we really want
+    to do is to hook the Tk 'insert' operation itself.  For one thing, we want
+    a text.insert call in idle code to have the same effect as a key press.
+
+    When a widget is instantiated, a Tcl command is created whose name is the
+    same as the pathname widget._w.  This command is used to invoke the various
+    widget operations, e.g. insert (for a Text widget). We are going to hook
+    this command and provide a facility ('register') to intercept the widget
+    operation.  We will also intercept method calls on the tkinter class
+    instance that represents the tk widget.
+
+    In IDLE, WidgetRedirector is used in Percolator to intercept Text
+    commands.  The function being registered provides access to the top
+    of a Percolator chain.  At the bottom of the chain is a call to the
+    original Tk widget operation.
+    """
+    def __init__(self, widget):
+        '''Initialize attributes and setup redirection.
+
+        _operations: dict mapping operation name to new function.
+        widget: the widget whose tcl command is to be intercepted.
+        tk: widget.tk, a convenience attribute, probably not needed.
+        orig: new name of the original tcl command.
+
+        Since renaming to orig fails with TclError when orig already
+        exists, only one WidgetDirector can exist for a given widget.
+        '''
+        self._operations = {}
+        self.widget = widget            # widget instance
+        self.tk = tk = widget.tk        # widget's root
+        w = widget._w                   # widget's (full) Tk pathname
+        self.orig = w + "_orig"
+        # Rename the Tcl command within Tcl:
+        tk.call("rename", w, self.orig)
+        # Create a new Tcl command whose name is the widget's pathname, and
+        # whose action is to dispatch on the operation passed to the widget:
+        tk.createcommand(w, self.dispatch)
+
+    def __repr__(self):
+        return "%s(%s<%s>)" % (self.__class__.__name__,
+                               self.widget.__class__.__name__,
+                               self.widget._w)
+
+    def close(self):
+        "Unregister operations and revert redirection created by .__init__."
+        for operation in list(self._operations):
+            self.unregister(operation)
+        widget = self.widget
+        tk = widget.tk
+        w = widget._w
+        # Restore the original widget Tcl command.
+        tk.deletecommand(w)
+        tk.call("rename", self.orig, w)
+        del self.widget, self.tk  # Should not be needed
+        # if instance is deleted after close, as in Percolator.
+
+    def register(self, operation, function):
+        '''Return OriginalCommand(operation) after registering function.
+
+        Registration adds an operation: function pair to ._operations.
+        It also adds a widget function attribute that masks the tkinter
+        class instance method.  Method masking operates independently
+        from command dispatch.
+
+        If a second function is registered for the same operation, the
+        first function is replaced in both places.
+        '''
+        self._operations[operation] = function
+        setattr(self.widget, operation, function)
+        return OriginalCommand(self, operation)
+
+    def unregister(self, operation):
+        '''Return the function for the operation, or None.
+
+        Deleting the instance attribute unmasks the class attribute.
+        '''
+        if operation in self._operations:
+            function = self._operations[operation]
+            del self._operations[operation]
+            try:
+                delattr(self.widget, operation)
+            except AttributeError:
+                pass
+            return function
+        else:
+            return None
+
+    def dispatch(self, operation, *args):
+        '''Callback from Tcl which runs when the widget is referenced.
+
+        If an operation has been registered in self._operations, apply the
+        associated function to the args passed into Tcl. Otherwise, pass the
+        operation through to Tk via the original Tcl function.
+
+        Note that if a registered function is called, the operation is not
+        passed through to Tk.  Apply the function returned by self.register()
+        to *args to accomplish that.  For an example, see colorizer.py.
+
+        '''
+        m = self._operations.get(operation)
+        try:
+            if m:
+                return m(*args)
+            else:
+                return self.tk.call((self.orig, operation) + args)
+        except TclError:
+            return ""
+
+
+class OriginalCommand:
+    '''Callable for original tk command that has been redirected.
+
+    Returned by .register; can be used in the function registered.
+    redir = WidgetRedirector(text)
+    def my_insert(*args):
+        print("insert", args)
+        original_insert(*args)
+    original_insert = redir.register("insert", my_insert)
+    '''
+
+    def __init__(self, redir, operation):
+        '''Create .tk_call and .orig_and_operation for .__call__ method.
+
+        .redir and .operation store the input args for __repr__.
+        .tk and .orig copy attributes of .redir (probably not needed).
+        '''
+        self.redir = redir
+        self.operation = operation
+        self.tk = redir.tk  # redundant with self.redir
+        self.orig = redir.orig  # redundant with self.redir
+        # These two could be deleted after checking recipient code.
+        self.tk_call = redir.tk.call
+        self.orig_and_operation = (redir.orig, operation)
+
+    def __repr__(self):
+        return "%s(%r, %r)" % (self.__class__.__name__,
+                               self.redir, self.operation)
+
+    def __call__(self, *args):
+        return self.tk_call(self.orig_and_operation + args)
+
+
+def _widget_redirector(parent):  # htest #
+    from tkinter import Toplevel, Text
+
+    top = Toplevel(parent)
+    top.title("Test WidgetRedirector")
+    x, y = map(int, parent.geometry().split('+')[1:])
+    top.geometry("+%d+%d" % (x, y + 175))
+    text = Text(top)
+    text.pack()
+    text.focus_set()
+    redir = WidgetRedirector(text)
+    def my_insert(*args):
+        print("insert", args)
+        original_insert(*args)
+    original_insert = redir.register("insert", my_insert)
+
+if __name__ == "__main__":
+    from unittest import main
+    main('idlelib.idle_test.test_redirector', verbosity=2, exit=False)
+
+    from idlelib.idle_test.htest import run
+    run(_widget_redirector)
diff --git a/rootfs/usr/lib/python3.8/idlelib/replace.py b/rootfs/usr/lib/python3.8/idlelib/replace.py
new file mode 100644
index 0000000..6be034a
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/replace.py
@@ -0,0 +1,307 @@
+"""Replace dialog for IDLE. Inherits SearchDialogBase for GUI.
+Uses idlelib.searchengine.SearchEngine for search capability.
+Defines various replace related functions like replace, replace all,
+and replace+find.
+"""
+import re
+
+from tkinter import StringVar, TclError
+
+from idlelib.searchbase import SearchDialogBase
+from idlelib import searchengine
+
+
+def replace(text):
+    """Create or reuse a singleton ReplaceDialog instance.
+
+    The singleton dialog saves user entries and preferences
+    across instances.
+
+    Args:
+        text: Text widget containing the text to be searched.
+    """
+    root = text._root()
+    engine = searchengine.get(root)
+    if not hasattr(engine, "_replacedialog"):
+        engine._replacedialog = ReplaceDialog(root, engine)
+    dialog = engine._replacedialog
+    dialog.open(text)
+
+
+class ReplaceDialog(SearchDialogBase):
+    "Dialog for finding and replacing a pattern in text."
+
+    title = "Replace Dialog"
+    icon = "Replace"
+
+    def __init__(self, root, engine):
+        """Create search dialog for finding and replacing text.
+
+        Uses SearchDialogBase as the basis for the GUI and a
+        searchengine instance to prepare the search.
+
+        Attributes:
+            replvar: StringVar containing 'Replace with:' value.
+            replent: Entry widget for replvar.  Created in
+                create_entries().
+            ok: Boolean used in searchengine.search_text to indicate
+                whether the search includes the selection.
+        """
+        super().__init__(root, engine)
+        self.replvar = StringVar(root)
+
+    def open(self, text):
+        """Make dialog visible on top of others and ready to use.
+
+        Also, highlight the currently selected text and set the
+        search to include the current selection (self.ok).
+
+        Args:
+            text: Text widget being searched.
+        """
+        SearchDialogBase.open(self, text)
+        try:
+            first = text.index("sel.first")
+        except TclError:
+            first = None
+        try:
+            last = text.index("sel.last")
+        except TclError:
+            last = None
+        first = first or text.index("insert")
+        last = last or first
+        self.show_hit(first, last)
+        self.ok = True
+
+    def create_entries(self):
+        "Create base and additional label and text entry widgets."
+        SearchDialogBase.create_entries(self)
+        self.replent = self.make_entry("Replace with:", self.replvar)[0]
+
+    def create_command_buttons(self):
+        """Create base and additional command buttons.
+
+        The additional buttons are for Find, Replace,
+        Replace+Find, and Replace All.
+        """
+        SearchDialogBase.create_command_buttons(self)
+        self.make_button("Find", self.find_it)
+        self.make_button("Replace", self.replace_it)
+        self.make_button("Replace+Find", self.default_command, isdef=True)
+        self.make_button("Replace All", self.replace_all)
+
+    def find_it(self, event=None):
+        "Handle the Find button."
+        self.do_find(False)
+
+    def replace_it(self, event=None):
+        """Handle the Replace button.
+
+        If the find is successful, then perform replace.
+        """
+        if self.do_find(self.ok):
+            self.do_replace()
+
+    def default_command(self, event=None):
+        """Handle the Replace+Find button as the default command.
+
+        First performs a replace and then, if the replace was
+        successful, a find next.
+        """
+        if self.do_find(self.ok):
+            if self.do_replace():  # Only find next match if replace succeeded.
+                                   # A bad re can cause it to fail.
+                self.do_find(False)
+
+    def _replace_expand(self, m, repl):
+        "Expand replacement text if regular expression."
+        if self.engine.isre():
+            try:
+                new = m.expand(repl)
+            except re.error:
+                self.engine.report_error(repl, 'Invalid Replace Expression')
+                new = None
+        else:
+            new = repl
+
+        return new
+
+    def replace_all(self, event=None):
+        """Handle the Replace All button.
+
+        Search text for occurrences of the Find value and replace
+        each of them.  The 'wrap around' value controls the start
+        point for searching.  If wrap isn't set, then the searching
+        starts at the first occurrence after the current selection;
+        if wrap is set, the replacement starts at the first line.
+        The replacement is always done top-to-bottom in the text.
+        """
+        prog = self.engine.getprog()
+        if not prog:
+            return
+        repl = self.replvar.get()
+        text = self.text
+        res = self.engine.search_text(text, prog)
+        if not res:
+            self.bell()
+            return
+        text.tag_remove("sel", "1.0", "end")
+        text.tag_remove("hit", "1.0", "end")
+        line = res[0]
+        col = res[1].start()
+        if self.engine.iswrap():
+            line = 1
+            col = 0
+        ok = True
+        first = last = None
+        # XXX ought to replace circular instead of top-to-bottom when wrapping
+        text.undo_block_start()
+        while True:
+            res = self.engine.search_forward(text, prog, line, col,
+                                             wrap=False, ok=ok)
+            if not res:
+                break
+            line, m = res
+            chars = text.get("%d.0" % line, "%d.0" % (line+1))
+            orig = m.group()
+            new = self._replace_expand(m, repl)
+            if new is None:
+                break
+            i, j = m.span()
+            first = "%d.%d" % (line, i)
+            last = "%d.%d" % (line, j)
+            if new == orig:
+                text.mark_set("insert", last)
+            else:
+                text.mark_set("insert", first)
+                if first != last:
+                    text.delete(first, last)
+                if new:
+                    text.insert(first, new)
+            col = i + len(new)
+            ok = False
+        text.undo_block_stop()
+        if first and last:
+            self.show_hit(first, last)
+        self.close()
+
+    def do_find(self, ok=False):
+        """Search for and highlight next occurrence of pattern in text.
+
+        No text replacement is done with this option.
+        """
+        if not self.engine.getprog():
+            return False
+        text = self.text
+        res = self.engine.search_text(text, None, ok)
+        if not res:
+            self.bell()
+            return False
+        line, m = res
+        i, j = m.span()
+        first = "%d.%d" % (line, i)
+        last = "%d.%d" % (line, j)
+        self.show_hit(first, last)
+        self.ok = True
+        return True
+
+    def do_replace(self):
+        "Replace search pattern in text with replacement value."
+        prog = self.engine.getprog()
+        if not prog:
+            return False
+        text = self.text
+        try:
+            first = pos = text.index("sel.first")
+            last = text.index("sel.last")
+        except TclError:
+            pos = None
+        if not pos:
+            first = last = pos = text.index("insert")
+        line, col = searchengine.get_line_col(pos)
+        chars = text.get("%d.0" % line, "%d.0" % (line+1))
+        m = prog.match(chars, col)
+        if not prog:
+            return False
+        new = self._replace_expand(m, self.replvar.get())
+        if new is None:
+            return False
+        text.mark_set("insert", first)
+        text.undo_block_start()
+        if m.group():
+            text.delete(first, last)
+        if new:
+            text.insert(first, new)
+        text.undo_block_stop()
+        self.show_hit(first, text.index("insert"))
+        self.ok = False
+        return True
+
+    def show_hit(self, first, last):
+        """Highlight text between first and last indices.
+
+        Text is highlighted via the 'hit' tag and the marked
+        section is brought into view.
+
+        The colors from the 'hit' tag aren't currently shown
+        when the text is displayed.  This is due to the 'sel'
+        tag being added first, so the colors in the 'sel'
+        config are seen instead of the colors for 'hit'.
+        """
+        text = self.text
+        text.mark_set("insert", first)
+        text.tag_remove("sel", "1.0", "end")
+        text.tag_add("sel", first, last)
+        text.tag_remove("hit", "1.0", "end")
+        if first == last:
+            text.tag_add("hit", first)
+        else:
+            text.tag_add("hit", first, last)
+        text.see("insert")
+        text.update_idletasks()
+
+    def close(self, event=None):
+        "Close the dialog and remove hit tags."
+        SearchDialogBase.close(self, event)
+        self.text.tag_remove("hit", "1.0", "end")
+
+
+def _replace_dialog(parent):  # htest #
+    from tkinter import Toplevel, Text, END, SEL
+    from tkinter.ttk import Frame, Button
+
+    top = Toplevel(parent)
+    top.title("Test ReplaceDialog")
+    x, y = map(int, parent.geometry().split('+')[1:])
+    top.geometry("+%d+%d" % (x, y + 175))
+
+    # mock undo delegator methods
+    def undo_block_start():
+        pass
+
+    def undo_block_stop():
+        pass
+
+    frame = Frame(top)
+    frame.pack()
+    text = Text(frame, inactiveselectbackground='gray')
+    text.undo_block_start = undo_block_start
+    text.undo_block_stop = undo_block_stop
+    text.pack()
+    text.insert("insert","This is a sample sTring\nPlus MORE.")
+    text.focus_set()
+
+    def show_replace():
+        text.tag_add(SEL, "1.0", END)
+        replace(text)
+        text.tag_remove(SEL, "1.0", END)
+
+    button = Button(frame, text="Replace", command=show_replace)
+    button.pack()
+
+if __name__ == '__main__':
+    from unittest import main
+    main('idlelib.idle_test.test_replace', verbosity=2, exit=False)
+
+    from idlelib.idle_test.htest import run
+    run(_replace_dialog)
diff --git a/rootfs/usr/lib/python3.8/idlelib/rpc.py b/rootfs/usr/lib/python3.8/idlelib/rpc.py
new file mode 100644
index 0000000..8efcf04
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/rpc.py
@@ -0,0 +1,635 @@
+"""RPC Implementation, originally written for the Python Idle IDE
+
+For security reasons, GvR requested that Idle's Python execution server process
+connect to the Idle process, which listens for the connection.  Since Idle has
+only one client per server, this was not a limitation.
+
+   +---------------------------------+ +-------------+
+   | socketserver.BaseRequestHandler | | SocketIO    |
+   +---------------------------------+ +-------------+
+                   ^                   | register()  |
+                   |                   | unregister()|
+                   |                   +-------------+
+                   |                      ^  ^
+                   |                      |  |
+                   | + -------------------+  |
+                   | |                       |
+   +-------------------------+        +-----------------+
+   | RPCHandler              |        | RPCClient       |
+   | [attribute of RPCServer]|        |                 |
+   +-------------------------+        +-----------------+
+
+The RPCServer handler class is expected to provide register/unregister methods.
+RPCHandler inherits the mix-in class SocketIO, which provides these methods.
+
+See the Idle run.main() docstring for further information on how this was
+accomplished in Idle.
+
+"""
+import builtins
+import copyreg
+import io
+import marshal
+import os
+import pickle
+import queue
+import select
+import socket
+import socketserver
+import struct
+import sys
+import threading
+import traceback
+import types
+
+def unpickle_code(ms):
+    "Return code object from marshal string ms."
+    co = marshal.loads(ms)
+    assert isinstance(co, types.CodeType)
+    return co
+
+def pickle_code(co):
+    "Return unpickle function and tuple with marshalled co code object."
+    assert isinstance(co, types.CodeType)
+    ms = marshal.dumps(co)
+    return unpickle_code, (ms,)
+
+def dumps(obj, protocol=None):
+    "Return pickled (or marshalled) string for obj."
+    # IDLE passes 'None' to select pickle.DEFAULT_PROTOCOL.
+    f = io.BytesIO()
+    p = CodePickler(f, protocol)
+    p.dump(obj)
+    return f.getvalue()
+
+
+class CodePickler(pickle.Pickler):
+    dispatch_table = {types.CodeType: pickle_code, **copyreg.dispatch_table}
+
+
+BUFSIZE = 8*1024
+LOCALHOST = '127.0.0.1'
+
+class RPCServer(socketserver.TCPServer):
+
+    def __init__(self, addr, handlerclass=None):
+        if handlerclass is None:
+            handlerclass = RPCHandler
+        socketserver.TCPServer.__init__(self, addr, handlerclass)
+
+    def server_bind(self):
+        "Override TCPServer method, no bind() phase for connecting entity"
+        pass
+
+    def server_activate(self):
+        """Override TCPServer method, connect() instead of listen()
+
+        Due to the reversed connection, self.server_address is actually the
+        address of the Idle Client to which we are connecting.
+
+        """
+        self.socket.connect(self.server_address)
+
+    def get_request(self):
+        "Override TCPServer method, return already connected socket"
+        return self.socket, self.server_address
+
+    def handle_error(self, request, client_address):
+        """Override TCPServer method
+
+        Error message goes to __stderr__.  No error message if exiting
+        normally or socket raised EOF.  Other exceptions not handled in
+        server code will cause os._exit.
+
+        """
+        try:
+            raise
+        except SystemExit:
+            raise
+        except:
+            erf = sys.__stderr__
+            print('\n' + '-'*40, file=erf)
+            print('Unhandled server exception!', file=erf)
+            print('Thread: %s' % threading.current_thread().name, file=erf)
+            print('Client Address: ', client_address, file=erf)
+            print('Request: ', repr(request), file=erf)
+            traceback.print_exc(file=erf)
+            print('\n*** Unrecoverable, server exiting!', file=erf)
+            print('-'*40, file=erf)
+            os._exit(0)
+
+#----------------- end class RPCServer --------------------
+
+objecttable = {}
+request_queue = queue.Queue(0)
+response_queue = queue.Queue(0)
+
+
+class SocketIO:
+
+    nextseq = 0
+
+    def __init__(self, sock, objtable=None, debugging=None):
+        self.sockthread = threading.current_thread()
+        if debugging is not None:
+            self.debugging = debugging
+        self.sock = sock
+        if objtable is None:
+            objtable = objecttable
+        self.objtable = objtable
+        self.responses = {}
+        self.cvars = {}
+
+    def close(self):
+        sock = self.sock
+        self.sock = None
+        if sock is not None:
+            sock.close()
+
+    def exithook(self):
+        "override for specific exit action"
+        os._exit(0)
+
+    def debug(self, *args):
+        if not self.debugging:
+            return
+        s = self.location + " " + str(threading.current_thread().name)
+        for a in args:
+            s = s + " " + str(a)
+        print(s, file=sys.__stderr__)
+
+    def register(self, oid, object):
+        self.objtable[oid] = object
+
+    def unregister(self, oid):
+        try:
+            del self.objtable[oid]
+        except KeyError:
+            pass
+
+    def localcall(self, seq, request):
+        self.debug("localcall:", request)
+        try:
+            how, (oid, methodname, args, kwargs) = request
+        except TypeError:
+            return ("ERROR", "Bad request format")
+        if oid not in self.objtable:
+            return ("ERROR", "Unknown object id: %r" % (oid,))
+        obj = self.objtable[oid]
+        if methodname == "__methods__":
+            methods = {}
+            _getmethods(obj, methods)
+            return ("OK", methods)
+        if methodname == "__attributes__":
+            attributes = {}
+            _getattributes(obj, attributes)
+            return ("OK", attributes)
+        if not hasattr(obj, methodname):
+            return ("ERROR", "Unsupported method name: %r" % (methodname,))
+        method = getattr(obj, methodname)
+        try:
+            if how == 'CALL':
+                ret = method(*args, **kwargs)
+                if isinstance(ret, RemoteObject):
+                    ret = remoteref(ret)
+                return ("OK", ret)
+            elif how == 'QUEUE':
+                request_queue.put((seq, (method, args, kwargs)))
+                return("QUEUED", None)
+            else:
+                return ("ERROR", "Unsupported message type: %s" % how)
+        except SystemExit:
+            raise
+        except KeyboardInterrupt:
+            raise
+        except OSError:
+            raise
+        except Exception as ex:
+            return ("CALLEXC", ex)
+        except:
+            msg = "*** Internal Error: rpc.py:SocketIO.localcall()\n\n"\
+                  " Object: %s \n Method: %s \n Args: %s\n"
+            print(msg % (oid, method, args), file=sys.__stderr__)
+            traceback.print_exc(file=sys.__stderr__)
+            return ("EXCEPTION", None)
+
+    def remotecall(self, oid, methodname, args, kwargs):
+        self.debug("remotecall:asynccall: ", oid, methodname)
+        seq = self.asynccall(oid, methodname, args, kwargs)
+        return self.asyncreturn(seq)
+
+    def remotequeue(self, oid, methodname, args, kwargs):
+        self.debug("remotequeue:asyncqueue: ", oid, methodname)
+        seq = self.asyncqueue(oid, methodname, args, kwargs)
+        return self.asyncreturn(seq)
+
+    def asynccall(self, oid, methodname, args, kwargs):
+        request = ("CALL", (oid, methodname, args, kwargs))
+        seq = self.newseq()
+        if threading.current_thread() != self.sockthread:
+            cvar = threading.Condition()
+            self.cvars[seq] = cvar
+        self.debug(("asynccall:%d:" % seq), oid, methodname, args, kwargs)
+        self.putmessage((seq, request))
+        return seq
+
+    def asyncqueue(self, oid, methodname, args, kwargs):
+        request = ("QUEUE", (oid, methodname, args, kwargs))
+        seq = self.newseq()
+        if threading.current_thread() != self.sockthread:
+            cvar = threading.Condition()
+            self.cvars[seq] = cvar
+        self.debug(("asyncqueue:%d:" % seq), oid, methodname, args, kwargs)
+        self.putmessage((seq, request))
+        return seq
+
+    def asyncreturn(self, seq):
+        self.debug("asyncreturn:%d:call getresponse(): " % seq)
+        response = self.getresponse(seq, wait=0.05)
+        self.debug(("asyncreturn:%d:response: " % seq), response)
+        return self.decoderesponse(response)
+
+    def decoderesponse(self, response):
+        how, what = response
+        if how == "OK":
+            return what
+        if how == "QUEUED":
+            return None
+        if how == "EXCEPTION":
+            self.debug("decoderesponse: EXCEPTION")
+            return None
+        if how == "EOF":
+            self.debug("decoderesponse: EOF")
+            self.decode_interrupthook()
+            return None
+        if how == "ERROR":
+            self.debug("decoderesponse: Internal ERROR:", what)
+            raise RuntimeError(what)
+        if how == "CALLEXC":
+            self.debug("decoderesponse: Call Exception:", what)
+            raise what
+        raise SystemError(how, what)
+
+    def decode_interrupthook(self):
+        ""
+        raise EOFError
+
+    def mainloop(self):
+        """Listen on socket until I/O not ready or EOF
+
+        pollresponse() will loop looking for seq number None, which
+        never comes, and exit on EOFError.
+
+        """
+        try:
+            self.getresponse(myseq=None, wait=0.05)
+        except EOFError:
+            self.debug("mainloop:return")
+            return
+
+    def getresponse(self, myseq, wait):
+        response = self._getresponse(myseq, wait)
+        if response is not None:
+            how, what = response
+            if how == "OK":
+                response = how, self._proxify(what)
+        return response
+
+    def _proxify(self, obj):
+        if isinstance(obj, RemoteProxy):
+            return RPCProxy(self, obj.oid)
+        if isinstance(obj, list):
+            return list(map(self._proxify, obj))
+        # XXX Check for other types -- not currently needed
+        return obj
+
+    def _getresponse(self, myseq, wait):
+        self.debug("_getresponse:myseq:", myseq)
+        if threading.current_thread() is self.sockthread:
+            # this thread does all reading of requests or responses
+            while 1:
+                response = self.pollresponse(myseq, wait)
+                if response is not None:
+                    return response
+        else:
+            # wait for notification from socket handling thread
+            cvar = self.cvars[myseq]
+            cvar.acquire()
+            while myseq not in self.responses:
+                cvar.wait()
+            response = self.responses[myseq]
+            self.debug("_getresponse:%s: thread woke up: response: %s" %
+                       (myseq, response))
+            del self.responses[myseq]
+            del self.cvars[myseq]
+            cvar.release()
+            return response
+
+    def newseq(self):
+        self.nextseq = seq = self.nextseq + 2
+        return seq
+
+    def putmessage(self, message):
+        self.debug("putmessage:%d:" % message[0])
+        try:
+            s = dumps(message)
+        except pickle.PicklingError:
+            print("Cannot pickle:", repr(message), file=sys.__stderr__)
+            raise
+        s = struct.pack("<i", len(s)) + s
+        while len(s) > 0:
+            try:
+                r, w, x = select.select([], [self.sock], [])
+                n = self.sock.send(s[:BUFSIZE])
+            except (AttributeError, TypeError):
+                raise OSError("socket no longer exists")
+            s = s[n:]
+
+    buff = b''
+    bufneed = 4
+    bufstate = 0 # meaning: 0 => reading count; 1 => reading data
+
+    def pollpacket(self, wait):
+        self._stage0()
+        if len(self.buff) < self.bufneed:
+            r, w, x = select.select([self.sock.fileno()], [], [], wait)
+            if len(r) == 0:
+                return None
+            try:
+                s = self.sock.recv(BUFSIZE)
+            except OSError:
+                raise EOFError
+            if len(s) == 0:
+                raise EOFError
+            self.buff += s
+            self._stage0()
+        return self._stage1()
+
+    def _stage0(self):
+        if self.bufstate == 0 and len(self.buff) >= 4:
+            s = self.buff[:4]
+            self.buff = self.buff[4:]
+            self.bufneed = struct.unpack("<i", s)[0]
+            self.bufstate = 1
+
+    def _stage1(self):
+        if self.bufstate == 1 and len(self.buff) >= self.bufneed:
+            packet = self.buff[:self.bufneed]
+            self.buff = self.buff[self.bufneed:]
+            self.bufneed = 4
+            self.bufstate = 0
+            return packet
+
+    def pollmessage(self, wait):
+        packet = self.pollpacket(wait)
+        if packet is None:
+            return None
+        try:
+            message = pickle.loads(packet)
+        except pickle.UnpicklingError:
+            print("-----------------------", file=sys.__stderr__)
+            print("cannot unpickle packet:", repr(packet), file=sys.__stderr__)
+            traceback.print_stack(file=sys.__stderr__)
+            print("-----------------------", file=sys.__stderr__)
+            raise
+        return message
+
+    def pollresponse(self, myseq, wait):
+        """Handle messages received on the socket.
+
+        Some messages received may be asynchronous 'call' or 'queue' requests,
+        and some may be responses for other threads.
+
+        'call' requests are passed to self.localcall() with the expectation of
+        immediate execution, during which time the socket is not serviced.
+
+        'queue' requests are used for tasks (which may block or hang) to be
+        processed in a different thread.  These requests are fed into
+        request_queue by self.localcall().  Responses to queued requests are
+        taken from response_queue and sent across the link with the associated
+        sequence numbers.  Messages in the queues are (sequence_number,
+        request/response) tuples and code using this module removing messages
+        from the request_queue is responsible for returning the correct
+        sequence number in the response_queue.
+
+        pollresponse() will loop until a response message with the myseq
+        sequence number is received, and will save other responses in
+        self.responses and notify the owning thread.
+
+        """
+        while 1:
+            # send queued response if there is one available
+            try:
+                qmsg = response_queue.get(0)
+            except queue.Empty:
+                pass
+            else:
+                seq, response = qmsg
+                message = (seq, ('OK', response))
+                self.putmessage(message)
+            # poll for message on link
+            try:
+                message = self.pollmessage(wait)
+                if message is None:  # socket not ready
+                    return None
+            except EOFError:
+                self.handle_EOF()
+                return None
+            except AttributeError:
+                return None
+            seq, resq = message
+            how = resq[0]
+            self.debug("pollresponse:%d:myseq:%s" % (seq, myseq))
+            # process or queue a request
+            if how in ("CALL", "QUEUE"):
+                self.debug("pollresponse:%d:localcall:call:" % seq)
+                response = self.localcall(seq, resq)
+                self.debug("pollresponse:%d:localcall:response:%s"
+                           % (seq, response))
+                if how == "CALL":
+                    self.putmessage((seq, response))
+                elif how == "QUEUE":
+                    # don't acknowledge the 'queue' request!
+                    pass
+                continue
+            # return if completed message transaction
+            elif seq == myseq:
+                return resq
+            # must be a response for a different thread:
+            else:
+                cv = self.cvars.get(seq, None)
+                # response involving unknown sequence number is discarded,
+                # probably intended for prior incarnation of server
+                if cv is not None:
+                    cv.acquire()
+                    self.responses[seq] = resq
+                    cv.notify()
+                    cv.release()
+                continue
+
+    def handle_EOF(self):
+        "action taken upon link being closed by peer"
+        self.EOFhook()
+        self.debug("handle_EOF")
+        for key in self.cvars:
+            cv = self.cvars[key]
+            cv.acquire()
+            self.responses[key] = ('EOF', None)
+            cv.notify()
+            cv.release()
+        # call our (possibly overridden) exit function
+        self.exithook()
+
+    def EOFhook(self):
+        "Classes using rpc client/server can override to augment EOF action"
+        pass
+
+#----------------- end class SocketIO --------------------
+
+class RemoteObject:
+    # Token mix-in class
+    pass
+
+
+def remoteref(obj):
+    oid = id(obj)
+    objecttable[oid] = obj
+    return RemoteProxy(oid)
+
+
+class RemoteProxy:
+
+    def __init__(self, oid):
+        self.oid = oid
+
+
+class RPCHandler(socketserver.BaseRequestHandler, SocketIO):
+
+    debugging = False
+    location = "#S"  # Server
+
+    def __init__(self, sock, addr, svr):
+        svr.current_handler = self ## cgt xxx
+        SocketIO.__init__(self, sock)
+        socketserver.BaseRequestHandler.__init__(self, sock, addr, svr)
+
+    def handle(self):
+        "handle() method required by socketserver"
+        self.mainloop()
+
+    def get_remote_proxy(self, oid):
+        return RPCProxy(self, oid)
+
+
+class RPCClient(SocketIO):
+
+    debugging = False
+    location = "#C"  # Client
+
+    nextseq = 1 # Requests coming from the client are odd numbered
+
+    def __init__(self, address, family=socket.AF_INET, type=socket.SOCK_STREAM):
+        self.listening_sock = socket.socket(family, type)
+        self.listening_sock.bind(address)
+        self.listening_sock.listen(1)
+
+    def accept(self):
+        working_sock, address = self.listening_sock.accept()
+        if self.debugging:
+            print("****** Connection request from ", address, file=sys.__stderr__)
+        if address[0] == LOCALHOST:
+            SocketIO.__init__(self, working_sock)
+        else:
+            print("** Invalid host: ", address, file=sys.__stderr__)
+            raise OSError
+
+    def get_remote_proxy(self, oid):
+        return RPCProxy(self, oid)
+
+
+class RPCProxy:
+
+    __methods = None
+    __attributes = None
+
+    def __init__(self, sockio, oid):
+        self.sockio = sockio
+        self.oid = oid
+
+    def __getattr__(self, name):
+        if self.__methods is None:
+            self.__getmethods()
+        if self.__methods.get(name):
+            return MethodProxy(self.sockio, self.oid, name)
+        if self.__attributes is None:
+            self.__getattributes()
+        if name in self.__attributes:
+            value = self.sockio.remotecall(self.oid, '__getattribute__',
+                                           (name,), {})
+            return value
+        else:
+            raise AttributeError(name)
+
+    def __getattributes(self):
+        self.__attributes = self.sockio.remotecall(self.oid,
+                                                "__attributes__", (), {})
+
+    def __getmethods(self):
+        self.__methods = self.sockio.remotecall(self.oid,
+                                                "__methods__", (), {})
+
+def _getmethods(obj, methods):
+    # Helper to get a list of methods from an object
+    # Adds names to dictionary argument 'methods'
+    for name in dir(obj):
+        attr = getattr(obj, name)
+        if callable(attr):
+            methods[name] = 1
+    if isinstance(obj, type):
+        for super in obj.__bases__:
+            _getmethods(super, methods)
+
+def _getattributes(obj, attributes):
+    for name in dir(obj):
+        attr = getattr(obj, name)
+        if not callable(attr):
+            attributes[name] = 1
+
+
+class MethodProxy:
+
+    def __init__(self, sockio, oid, name):
+        self.sockio = sockio
+        self.oid = oid
+        self.name = name
+
+    def __call__(self, /, *args, **kwargs):
+        value = self.sockio.remotecall(self.oid, self.name, args, kwargs)
+        return value
+
+
+# XXX KBK 09Sep03  We need a proper unit test for this module.  Previously
+#                  existing test code was removed at Rev 1.27 (r34098).
+
+def displayhook(value):
+    """Override standard display hook to use non-locale encoding"""
+    if value is None:
+        return
+    # Set '_' to None to avoid recursion
+    builtins._ = None
+    text = repr(value)
+    try:
+        sys.stdout.write(text)
+    except UnicodeEncodeError:
+        # let's use ascii while utf8-bmp codec doesn't present
+        encoding = 'ascii'
+        bytes = text.encode(encoding, 'backslashreplace')
+        text = bytes.decode(encoding, 'strict')
+        sys.stdout.write(text)
+    sys.stdout.write("\n")
+    builtins._ = value
+
+
+if __name__ == '__main__':
+    from unittest import main
+    main('idlelib.idle_test.test_rpc', verbosity=2,)
diff --git a/rootfs/usr/lib/python3.8/idlelib/run.py b/rootfs/usr/lib/python3.8/idlelib/run.py
new file mode 100644
index 0000000..07e9a2b
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/run.py
@@ -0,0 +1,623 @@
+""" idlelib.run
+
+Simplified, pyshell.ModifiedInterpreter spawns a subprocess with
+f'''{sys.executable} -c "__import__('idlelib.run').run.main()"'''
+'.run' is needed because __import__ returns idlelib, not idlelib.run.
+"""
+import functools
+import io
+import linecache
+import queue
+import sys
+import textwrap
+import time
+import traceback
+import _thread as thread
+import threading
+import warnings
+
+import idlelib  # testing
+from idlelib import autocomplete  # AutoComplete, fetch_encodings
+from idlelib import calltip  # Calltip
+from idlelib import debugger_r  # start_debugger
+from idlelib import debugobj_r  # remote_object_tree_item
+from idlelib import iomenu  # encoding
+from idlelib import rpc  # multiple objects
+from idlelib import stackviewer  # StackTreeItem
+import __main__
+
+import tkinter  # Use tcl and, if startup fails, messagebox.
+if not hasattr(sys.modules['idlelib.run'], 'firstrun'):
+    # Undo modifications of tkinter by idlelib imports; see bpo-25507.
+    for mod in ('simpledialog', 'messagebox', 'font',
+                'dialog', 'filedialog', 'commondialog',
+                'ttk'):
+        delattr(tkinter, mod)
+        del sys.modules['tkinter.' + mod]
+    # Avoid AttributeError if run again; see bpo-37038.
+    sys.modules['idlelib.run'].firstrun = False
+
+LOCALHOST = '127.0.0.1'
+
+
+def idle_formatwarning(message, category, filename, lineno, line=None):
+    """Format warnings the IDLE way."""
+
+    s = "\nWarning (from warnings module):\n"
+    s += '  File \"%s\", line %s\n' % (filename, lineno)
+    if line is None:
+        line = linecache.getline(filename, lineno)
+    line = line.strip()
+    if line:
+        s += "    %s\n" % line
+    s += "%s: %s\n" % (category.__name__, message)
+    return s
+
+def idle_showwarning_subproc(
+        message, category, filename, lineno, file=None, line=None):
+    """Show Idle-format warning after replacing warnings.showwarning.
+
+    The only difference is the formatter called.
+    """
+    if file is None:
+        file = sys.stderr
+    try:
+        file.write(idle_formatwarning(
+                message, category, filename, lineno, line))
+    except OSError:
+        pass # the file (probably stderr) is invalid - this warning gets lost.
+
+_warnings_showwarning = None
+
+def capture_warnings(capture):
+    "Replace warning.showwarning with idle_showwarning_subproc, or reverse."
+
+    global _warnings_showwarning
+    if capture:
+        if _warnings_showwarning is None:
+            _warnings_showwarning = warnings.showwarning
+            warnings.showwarning = idle_showwarning_subproc
+    else:
+        if _warnings_showwarning is not None:
+            warnings.showwarning = _warnings_showwarning
+            _warnings_showwarning = None
+
+capture_warnings(True)
+tcl = tkinter.Tcl()
+
+def handle_tk_events(tcl=tcl):
+    """Process any tk events that are ready to be dispatched if tkinter
+    has been imported, a tcl interpreter has been created and tk has been
+    loaded."""
+    tcl.eval("update")
+
+# Thread shared globals: Establish a queue between a subthread (which handles
+# the socket) and the main thread (which runs user code), plus global
+# completion, exit and interruptable (the main thread) flags:
+
+exit_now = False
+quitting = False
+interruptable = False
+
+def main(del_exitfunc=False):
+    """Start the Python execution server in a subprocess
+
+    In the Python subprocess, RPCServer is instantiated with handlerclass
+    MyHandler, which inherits register/unregister methods from RPCHandler via
+    the mix-in class SocketIO.
+
+    When the RPCServer 'server' is instantiated, the TCPServer initialization
+    creates an instance of run.MyHandler and calls its handle() method.
+    handle() instantiates a run.Executive object, passing it a reference to the
+    MyHandler object.  That reference is saved as attribute rpchandler of the
+    Executive instance.  The Executive methods have access to the reference and
+    can pass it on to entities that they command
+    (e.g. debugger_r.Debugger.start_debugger()).  The latter, in turn, can
+    call MyHandler(SocketIO) register/unregister methods via the reference to
+    register and unregister themselves.
+
+    """
+    global exit_now
+    global quitting
+    global no_exitfunc
+    no_exitfunc = del_exitfunc
+    #time.sleep(15) # test subprocess not responding
+    try:
+        assert(len(sys.argv) > 1)
+        port = int(sys.argv[-1])
+    except:
+        print("IDLE Subprocess: no IP port passed in sys.argv.",
+              file=sys.__stderr__)
+        return
+
+    capture_warnings(True)
+    sys.argv[:] = [""]
+    sockthread = threading.Thread(target=manage_socket,
+                                  name='SockThread',
+                                  args=((LOCALHOST, port),))
+    sockthread.daemon = True
+    sockthread.start()
+    while 1:
+        try:
+            if exit_now:
+                try:
+                    exit()
+                except KeyboardInterrupt:
+                    # exiting but got an extra KBI? Try again!
+                    continue
+            try:
+                request = rpc.request_queue.get(block=True, timeout=0.05)
+            except queue.Empty:
+                request = None
+                # Issue 32207: calling handle_tk_events here adds spurious
+                # queue.Empty traceback to event handling exceptions.
+            if request:
+                seq, (method, args, kwargs) = request
+                ret = method(*args, **kwargs)
+                rpc.response_queue.put((seq, ret))
+            else:
+                handle_tk_events()
+        except KeyboardInterrupt:
+            if quitting:
+                exit_now = True
+            continue
+        except SystemExit:
+            capture_warnings(False)
+            raise
+        except:
+            type, value, tb = sys.exc_info()
+            try:
+                print_exception()
+                rpc.response_queue.put((seq, None))
+            except:
+                # Link didn't work, print same exception to __stderr__
+                traceback.print_exception(type, value, tb, file=sys.__stderr__)
+                exit()
+            else:
+                continue
+
+def manage_socket(address):
+    for i in range(3):
+        time.sleep(i)
+        try:
+            server = MyRPCServer(address, MyHandler)
+            break
+        except OSError as err:
+            print("IDLE Subprocess: OSError: " + err.args[1] +
+                  ", retrying....", file=sys.__stderr__)
+            socket_error = err
+    else:
+        print("IDLE Subprocess: Connection to "
+              "IDLE GUI failed, exiting.", file=sys.__stderr__)
+        show_socket_error(socket_error, address)
+        global exit_now
+        exit_now = True
+        return
+    server.handle_request() # A single request only
+
+def show_socket_error(err, address):
+    "Display socket error from manage_socket."
+    import tkinter
+    from tkinter.messagebox import showerror
+    root = tkinter.Tk()
+    fix_scaling(root)
+    root.withdraw()
+    showerror(
+            "Subprocess Connection Error",
+            f"IDLE's subprocess can't connect to {address[0]}:{address[1]}.\n"
+            f"Fatal OSError #{err.errno}: {err.strerror}.\n"
+            "See the 'Startup failure' section of the IDLE doc, online at\n"
+            "https://docs.python.org/3/library/idle.html#startup-failure",
+            parent=root)
+    root.destroy()
+
+def print_exception():
+    import linecache
+    linecache.checkcache()
+    flush_stdout()
+    efile = sys.stderr
+    typ, val, tb = excinfo = sys.exc_info()
+    sys.last_type, sys.last_value, sys.last_traceback = excinfo
+    seen = set()
+
+    def print_exc(typ, exc, tb):
+        seen.add(id(exc))
+        context = exc.__context__
+        cause = exc.__cause__
+        if cause is not None and id(cause) not in seen:
+            print_exc(type(cause), cause, cause.__traceback__)
+            print("\nThe above exception was the direct cause "
+                  "of the following exception:\n", file=efile)
+        elif (context is not None and
+              not exc.__suppress_context__ and
+              id(context) not in seen):
+            print_exc(type(context), context, context.__traceback__)
+            print("\nDuring handling of the above exception, "
+                  "another exception occurred:\n", file=efile)
+        if tb:
+            tbe = traceback.extract_tb(tb)
+            print('Traceback (most recent call last):', file=efile)
+            exclude = ("run.py", "rpc.py", "threading.py", "queue.py",
+                       "debugger_r.py", "bdb.py")
+            cleanup_traceback(tbe, exclude)
+            traceback.print_list(tbe, file=efile)
+        lines = traceback.format_exception_only(typ, exc)
+        for line in lines:
+            print(line, end='', file=efile)
+
+    print_exc(typ, val, tb)
+
+def cleanup_traceback(tb, exclude):
+    "Remove excluded traces from beginning/end of tb; get cached lines"
+    orig_tb = tb[:]
+    while tb:
+        for rpcfile in exclude:
+            if tb[0][0].count(rpcfile):
+                break    # found an exclude, break for: and delete tb[0]
+        else:
+            break        # no excludes, have left RPC code, break while:
+        del tb[0]
+    while tb:
+        for rpcfile in exclude:
+            if tb[-1][0].count(rpcfile):
+                break
+        else:
+            break
+        del tb[-1]
+    if len(tb) == 0:
+        # exception was in IDLE internals, don't prune!
+        tb[:] = orig_tb[:]
+        print("** IDLE Internal Exception: ", file=sys.stderr)
+    rpchandler = rpc.objecttable['exec'].rpchandler
+    for i in range(len(tb)):
+        fn, ln, nm, line = tb[i]
+        if nm == '?':
+            nm = "-toplevel-"
+        if not line and fn.startswith("<pyshell#"):
+            line = rpchandler.remotecall('linecache', 'getline',
+                                              (fn, ln), {})
+        tb[i] = fn, ln, nm, line
+
+def flush_stdout():
+    """XXX How to do this now?"""
+
+def exit():
+    """Exit subprocess, possibly after first clearing exit functions.
+
+    If config-main.cfg/.def 'General' 'delete-exitfunc' is True, then any
+    functions registered with atexit will be removed before exiting.
+    (VPython support)
+
+    """
+    if no_exitfunc:
+        import atexit
+        atexit._clear()
+    capture_warnings(False)
+    sys.exit(0)
+
+
+def fix_scaling(root):
+    """Scale fonts on HiDPI displays."""
+    import tkinter.font
+    scaling = float(root.tk.call('tk', 'scaling'))
+    if scaling > 1.4:
+        for name in tkinter.font.names(root):
+            font = tkinter.font.Font(root=root, name=name, exists=True)
+            size = int(font['size'])
+            if size < 0:
+                font['size'] = round(-0.75*size)
+
+
+def fixdoc(fun, text):
+    tem = (fun.__doc__ + '\n\n') if fun.__doc__ is not None else ''
+    fun.__doc__ = tem + textwrap.fill(textwrap.dedent(text))
+
+RECURSIONLIMIT_DELTA = 30
+
+def install_recursionlimit_wrappers():
+    """Install wrappers to always add 30 to the recursion limit."""
+    # see: bpo-26806
+
+    @functools.wraps(sys.setrecursionlimit)
+    def setrecursionlimit(*args, **kwargs):
+        # mimic the original sys.setrecursionlimit()'s input handling
+        if kwargs:
+            raise TypeError(
+                "setrecursionlimit() takes no keyword arguments")
+        try:
+            limit, = args
+        except ValueError:
+            raise TypeError(f"setrecursionlimit() takes exactly one "
+                            f"argument ({len(args)} given)")
+        if not limit > 0:
+            raise ValueError(
+                "recursion limit must be greater or equal than 1")
+
+        return setrecursionlimit.__wrapped__(limit + RECURSIONLIMIT_DELTA)
+
+    fixdoc(setrecursionlimit, f"""\
+            This IDLE wrapper adds {RECURSIONLIMIT_DELTA} to prevent possible
+            uninterruptible loops.""")
+
+    @functools.wraps(sys.getrecursionlimit)
+    def getrecursionlimit():
+        return getrecursionlimit.__wrapped__() - RECURSIONLIMIT_DELTA
+
+    fixdoc(getrecursionlimit, f"""\
+            This IDLE wrapper subtracts {RECURSIONLIMIT_DELTA} to compensate
+            for the {RECURSIONLIMIT_DELTA} IDLE adds when setting the limit.""")
+
+    # add the delta to the default recursion limit, to compensate
+    sys.setrecursionlimit(sys.getrecursionlimit() + RECURSIONLIMIT_DELTA)
+
+    sys.setrecursionlimit = setrecursionlimit
+    sys.getrecursionlimit = getrecursionlimit
+
+
+def uninstall_recursionlimit_wrappers():
+    """Uninstall the recursion limit wrappers from the sys module.
+
+    IDLE only uses this for tests. Users can import run and call
+    this to remove the wrapping.
+    """
+    if (
+            getattr(sys.setrecursionlimit, '__wrapped__', None) and
+            getattr(sys.getrecursionlimit, '__wrapped__', None)
+    ):
+        sys.setrecursionlimit = sys.setrecursionlimit.__wrapped__
+        sys.getrecursionlimit = sys.getrecursionlimit.__wrapped__
+        sys.setrecursionlimit(sys.getrecursionlimit() - RECURSIONLIMIT_DELTA)
+
+
+class MyRPCServer(rpc.RPCServer):
+
+    def handle_error(self, request, client_address):
+        """Override RPCServer method for IDLE
+
+        Interrupt the MainThread and exit server if link is dropped.
+
+        """
+        global quitting
+        try:
+            raise
+        except SystemExit:
+            raise
+        except EOFError:
+            global exit_now
+            exit_now = True
+            thread.interrupt_main()
+        except:
+            erf = sys.__stderr__
+            print(textwrap.dedent(f"""
+            {'-'*40}
+            Unhandled exception in user code execution server!'
+            Thread: {threading.current_thread().name}
+            IDLE Client Address: {client_address}
+            Request: {request!r}
+            """), file=erf)
+            traceback.print_exc(limit=-20, file=erf)
+            print(textwrap.dedent(f"""
+            *** Unrecoverable, server exiting!
+
+            Users should never see this message; it is likely transient.
+            If this recurs, report this with a copy of the message
+            and an explanation of how to make it repeat.
+            {'-'*40}"""), file=erf)
+            quitting = True
+            thread.interrupt_main()
+
+
+# Pseudofiles for shell-remote communication (also used in pyshell)
+
+class StdioFile(io.TextIOBase):
+
+    def __init__(self, shell, tags, encoding='utf-8', errors='strict'):
+        self.shell = shell
+        self.tags = tags
+        self._encoding = encoding
+        self._errors = errors
+
+    @property
+    def encoding(self):
+        return self._encoding
+
+    @property
+    def errors(self):
+        return self._errors
+
+    @property
+    def name(self):
+        return '<%s>' % self.tags
+
+    def isatty(self):
+        return True
+
+
+class StdOutputFile(StdioFile):
+
+    def writable(self):
+        return True
+
+    def write(self, s):
+        if self.closed:
+            raise ValueError("write to closed file")
+        s = str.encode(s, self.encoding, self.errors).decode(self.encoding, self.errors)
+        return self.shell.write(s, self.tags)
+
+
+class StdInputFile(StdioFile):
+    _line_buffer = ''
+
+    def readable(self):
+        return True
+
+    def read(self, size=-1):
+        if self.closed:
+            raise ValueError("read from closed file")
+        if size is None:
+            size = -1
+        elif not isinstance(size, int):
+            raise TypeError('must be int, not ' + type(size).__name__)
+        result = self._line_buffer
+        self._line_buffer = ''
+        if size < 0:
+            while True:
+                line = self.shell.readline()
+                if not line: break
+                result += line
+        else:
+            while len(result) < size:
+                line = self.shell.readline()
+                if not line: break
+                result += line
+            self._line_buffer = result[size:]
+            result = result[:size]
+        return result
+
+    def readline(self, size=-1):
+        if self.closed:
+            raise ValueError("read from closed file")
+        if size is None:
+            size = -1
+        elif not isinstance(size, int):
+            raise TypeError('must be int, not ' + type(size).__name__)
+        line = self._line_buffer or self.shell.readline()
+        if size < 0:
+            size = len(line)
+        eol = line.find('\n', 0, size)
+        if eol >= 0:
+            size = eol + 1
+        self._line_buffer = line[size:]
+        return line[:size]
+
+    def close(self):
+        self.shell.close()
+
+
+class MyHandler(rpc.RPCHandler):
+
+    def handle(self):
+        """Override base method"""
+        executive = Executive(self)
+        self.register("exec", executive)
+        self.console = self.get_remote_proxy("console")
+        sys.stdin = StdInputFile(self.console, "stdin",
+                                 iomenu.encoding, iomenu.errors)
+        sys.stdout = StdOutputFile(self.console, "stdout",
+                                   iomenu.encoding, iomenu.errors)
+        sys.stderr = StdOutputFile(self.console, "stderr",
+                                   iomenu.encoding, "backslashreplace")
+
+        sys.displayhook = rpc.displayhook
+        # page help() text to shell.
+        import pydoc # import must be done here to capture i/o binding
+        pydoc.pager = pydoc.plainpager
+
+        # Keep a reference to stdin so that it won't try to exit IDLE if
+        # sys.stdin gets changed from within IDLE's shell. See issue17838.
+        self._keep_stdin = sys.stdin
+
+        install_recursionlimit_wrappers()
+
+        self.interp = self.get_remote_proxy("interp")
+        rpc.RPCHandler.getresponse(self, myseq=None, wait=0.05)
+
+    def exithook(self):
+        "override SocketIO method - wait for MainThread to shut us down"
+        time.sleep(10)
+
+    def EOFhook(self):
+        "Override SocketIO method - terminate wait on callback and exit thread"
+        global quitting
+        quitting = True
+        thread.interrupt_main()
+
+    def decode_interrupthook(self):
+        "interrupt awakened thread"
+        global quitting
+        quitting = True
+        thread.interrupt_main()
+
+
+class Executive:
+
+    def __init__(self, rpchandler):
+        self.rpchandler = rpchandler
+        if idlelib.testing is False:
+            self.locals = __main__.__dict__
+            self.calltip = calltip.Calltip()
+            self.autocomplete = autocomplete.AutoComplete()
+        else:
+            self.locals = {}
+
+    def runcode(self, code):
+        global interruptable
+        try:
+            self.user_exc_info = None
+            interruptable = True
+            try:
+                exec(code, self.locals)
+            finally:
+                interruptable = False
+        except SystemExit as e:
+            if e.args:  # SystemExit called with an argument.
+                ob = e.args[0]
+                if not isinstance(ob, (type(None), int)):
+                    print('SystemExit: ' + str(ob), file=sys.stderr)
+            # Return to the interactive prompt.
+        except:
+            self.user_exc_info = sys.exc_info()  # For testing, hook, viewer.
+            if quitting:
+                exit()
+            if sys.excepthook is sys.__excepthook__:
+                print_exception()
+            else:
+                try:
+                    sys.excepthook(*self.user_exc_info)
+                except:
+                    self.user_exc_info = sys.exc_info()  # For testing.
+                    print_exception()
+            jit = self.rpchandler.console.getvar("<<toggle-jit-stack-viewer>>")
+            if jit:
+                self.rpchandler.interp.open_remote_stack_viewer()
+        else:
+            flush_stdout()
+
+    def interrupt_the_server(self):
+        if interruptable:
+            thread.interrupt_main()
+
+    def start_the_debugger(self, gui_adap_oid):
+        return debugger_r.start_debugger(self.rpchandler, gui_adap_oid)
+
+    def stop_the_debugger(self, idb_adap_oid):
+        "Unregister the Idb Adapter.  Link objects and Idb then subject to GC"
+        self.rpchandler.unregister(idb_adap_oid)
+
+    def get_the_calltip(self, name):
+        return self.calltip.fetch_tip(name)
+
+    def get_the_completion_list(self, what, mode):
+        return self.autocomplete.fetch_completions(what, mode)
+
+    def stackviewer(self, flist_oid=None):
+        if self.user_exc_info:
+            typ, val, tb = self.user_exc_info
+        else:
+            return None
+        flist = None
+        if flist_oid is not None:
+            flist = self.rpchandler.get_remote_proxy(flist_oid)
+        while tb and tb.tb_frame.f_globals["__name__"] in ["rpc", "run"]:
+            tb = tb.tb_next
+        sys.last_type = typ
+        sys.last_value = val
+        item = stackviewer.StackTreeItem(flist, tb)
+        return debugobj_r.remote_object_tree_item(item)
+
+
+if __name__ == '__main__':
+    from unittest import main
+    main('idlelib.idle_test.test_run', verbosity=2)
+
+capture_warnings(False)  # Make sure turned off; see bpo-18081.
diff --git a/rootfs/usr/lib/python3.8/idlelib/runscript.py b/rootfs/usr/lib/python3.8/idlelib/runscript.py
new file mode 100644
index 0000000..55712e9
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/runscript.py
@@ -0,0 +1,213 @@
+"""Execute code from an editor.
+
+Check module: do a full syntax check of the current module.
+Also run the tabnanny to catch any inconsistent tabs.
+
+Run module: also execute the module's code in the __main__ namespace.
+The window must have been saved previously. The module is added to
+sys.modules, and is also added to the __main__ namespace.
+
+TODO: Specify command line arguments in a dialog box.
+"""
+import os
+import tabnanny
+import time
+import tokenize
+
+from tkinter import messagebox
+
+from idlelib.config import idleConf
+from idlelib import macosx
+from idlelib import pyshell
+from idlelib.query import CustomRun
+from idlelib import outwin
+
+indent_message = """Error: Inconsistent indentation detected!
+
+1) Your indentation is outright incorrect (easy to fix), OR
+
+2) Your indentation mixes tabs and spaces.
+
+To fix case 2, change all tabs to spaces by using Edit->Select All followed \
+by Format->Untabify Region and specify the number of columns used by each tab.
+"""
+
+
+class ScriptBinding:
+
+    def __init__(self, editwin):
+        self.editwin = editwin
+        # Provide instance variables referenced by debugger
+        # XXX This should be done differently
+        self.flist = self.editwin.flist
+        self.root = self.editwin.root
+        # cli_args is list of strings that extends sys.argv
+        self.cli_args = []
+        self.perf = 0.0    # Workaround for macOS 11 Uni2; see bpo-42508.
+
+    def check_module_event(self, event):
+        if isinstance(self.editwin, outwin.OutputWindow):
+            self.editwin.text.bell()
+            return 'break'
+        filename = self.getfilename()
+        if not filename:
+            return 'break'
+        if not self.checksyntax(filename):
+            return 'break'
+        if not self.tabnanny(filename):
+            return 'break'
+        return "break"
+
+    def tabnanny(self, filename):
+        # XXX: tabnanny should work on binary files as well
+        with tokenize.open(filename) as f:
+            try:
+                tabnanny.process_tokens(tokenize.generate_tokens(f.readline))
+            except tokenize.TokenError as msg:
+                msgtxt, (lineno, start) = msg.args
+                self.editwin.gotoline(lineno)
+                self.errorbox("Tabnanny Tokenizing Error",
+                              "Token Error: %s" % msgtxt)
+                return False
+            except tabnanny.NannyNag as nag:
+                # The error messages from tabnanny are too confusing...
+                self.editwin.gotoline(nag.get_lineno())
+                self.errorbox("Tab/space error", indent_message)
+                return False
+        return True
+
+    def checksyntax(self, filename):
+        self.shell = shell = self.flist.open_shell()
+        saved_stream = shell.get_warning_stream()
+        shell.set_warning_stream(shell.stderr)
+        with open(filename, 'rb') as f:
+            source = f.read()
+        if b'\r' in source:
+            source = source.replace(b'\r\n', b'\n')
+            source = source.replace(b'\r', b'\n')
+        if source and source[-1] != ord(b'\n'):
+            source = source + b'\n'
+        editwin = self.editwin
+        text = editwin.text
+        text.tag_remove("ERROR", "1.0", "end")
+        try:
+            # If successful, return the compiled code
+            return compile(source, filename, "exec")
+        except (SyntaxError, OverflowError, ValueError) as value:
+            msg = getattr(value, 'msg', '') or value or "<no detail available>"
+            lineno = getattr(value, 'lineno', '') or 1
+            offset = getattr(value, 'offset', '') or 0
+            if offset == 0:
+                lineno += 1  #mark end of offending line
+            pos = "0.0 + %d lines + %d chars" % (lineno-1, offset-1)
+            editwin.colorize_syntax_error(text, pos)
+            self.errorbox("SyntaxError", "%-20s" % msg)
+            return False
+        finally:
+            shell.set_warning_stream(saved_stream)
+
+    def run_custom_event(self, event):
+        return self.run_module_event(event, customize=True)
+
+    def run_module_event(self, event, *, customize=False):
+        """Run the module after setting up the environment.
+
+        First check the syntax.  Next get customization.  If OK, make
+        sure the shell is active and then transfer the arguments, set
+        the run environment's working directory to the directory of the
+        module being executed and also add that directory to its
+        sys.path if not already included.
+        """
+        if macosx.isCocoaTk() and (time.perf_counter() - self.perf < .05):
+            return 'break'
+        if isinstance(self.editwin, outwin.OutputWindow):
+            self.editwin.text.bell()
+            return 'break'
+        filename = self.getfilename()
+        if not filename:
+            return 'break'
+        code = self.checksyntax(filename)
+        if not code:
+            return 'break'
+        if not self.tabnanny(filename):
+            return 'break'
+        if customize:
+            title = f"Customize {self.editwin.short_title()} Run"
+            run_args = CustomRun(self.shell.text, title,
+                                 cli_args=self.cli_args).result
+            if not run_args:  # User cancelled.
+                return 'break'
+        self.cli_args, restart = run_args if customize else ([], True)
+        interp = self.shell.interp
+        if pyshell.use_subprocess and restart:
+            interp.restart_subprocess(
+                    with_cwd=False, filename=filename)
+        dirname = os.path.dirname(filename)
+        argv = [filename]
+        if self.cli_args:
+            argv += self.cli_args
+        interp.runcommand(f"""if 1:
+            __file__ = {filename!r}
+            import sys as _sys
+            from os.path import basename as _basename
+            argv = {argv!r}
+            if (not _sys.argv or
+                _basename(_sys.argv[0]) != _basename(__file__) or
+                len(argv) > 1):
+                _sys.argv = argv
+            import os as _os
+            _os.chdir({dirname!r})
+            del _sys, argv, _basename, _os
+            \n""")
+        interp.prepend_syspath(filename)
+        # XXX KBK 03Jul04 When run w/o subprocess, runtime warnings still
+        #         go to __stderr__.  With subprocess, they go to the shell.
+        #         Need to change streams in pyshell.ModifiedInterpreter.
+        interp.runcode(code)
+        return 'break'
+
+    def getfilename(self):
+        """Get source filename.  If not saved, offer to save (or create) file
+
+        The debugger requires a source file.  Make sure there is one, and that
+        the current version of the source buffer has been saved.  If the user
+        declines to save or cancels the Save As dialog, return None.
+
+        If the user has configured IDLE for Autosave, the file will be
+        silently saved if it already exists and is dirty.
+
+        """
+        filename = self.editwin.io.filename
+        if not self.editwin.get_saved():
+            autosave = idleConf.GetOption('main', 'General',
+                                          'autosave', type='bool')
+            if autosave and filename:
+                self.editwin.io.save(None)
+            else:
+                confirm = self.ask_save_dialog()
+                self.editwin.text.focus_set()
+                if confirm:
+                    self.editwin.io.save(None)
+                    filename = self.editwin.io.filename
+                else:
+                    filename = None
+        return filename
+
+    def ask_save_dialog(self):
+        msg = "Source Must Be Saved\n" + 5*' ' + "OK to Save?"
+        confirm = messagebox.askokcancel(title="Save Before Run or Check",
+                                           message=msg,
+                                           default=messagebox.OK,
+                                           parent=self.editwin.text)
+        return confirm
+
+    def errorbox(self, title, message):
+        # XXX This should really be a function of EditorWindow...
+        messagebox.showerror(title, message, parent=self.editwin.text)
+        self.editwin.text.focus_set()
+        self.perf = time.perf_counter()
+
+
+if __name__ == "__main__":
+    from unittest import main
+    main('idlelib.idle_test.test_runscript', verbosity=2,)
diff --git a/rootfs/usr/lib/python3.8/idlelib/scrolledlist.py b/rootfs/usr/lib/python3.8/idlelib/scrolledlist.py
new file mode 100644
index 0000000..71fd18a
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/scrolledlist.py
@@ -0,0 +1,149 @@
+from tkinter import *
+from tkinter.ttk import Frame, Scrollbar
+
+from idlelib import macosx
+
+
+class ScrolledList:
+
+    default = "(None)"
+
+    def __init__(self, master, **options):
+        # Create top frame, with scrollbar and listbox
+        self.master = master
+        self.frame = frame = Frame(master)
+        self.frame.pack(fill="both", expand=1)
+        self.vbar = vbar = Scrollbar(frame, name="vbar")
+        self.vbar.pack(side="right", fill="y")
+        self.listbox = listbox = Listbox(frame, exportselection=0,
+            background="white")
+        if options:
+            listbox.configure(options)
+        listbox.pack(expand=1, fill="both")
+        # Tie listbox and scrollbar together
+        vbar["command"] = listbox.yview
+        listbox["yscrollcommand"] = vbar.set
+        # Bind events to the list box
+        listbox.bind("<ButtonRelease-1>", self.click_event)
+        listbox.bind("<Double-ButtonRelease-1>", self.double_click_event)
+        if macosx.isAquaTk():
+            listbox.bind("<ButtonPress-2>", self.popup_event)
+            listbox.bind("<Control-Button-1>", self.popup_event)
+        else:
+            listbox.bind("<ButtonPress-3>", self.popup_event)
+        listbox.bind("<Key-Up>", self.up_event)
+        listbox.bind("<Key-Down>", self.down_event)
+        # Mark as empty
+        self.clear()
+
+    def close(self):
+        self.frame.destroy()
+
+    def clear(self):
+        self.listbox.delete(0, "end")
+        self.empty = 1
+        self.listbox.insert("end", self.default)
+
+    def append(self, item):
+        if self.empty:
+            self.listbox.delete(0, "end")
+            self.empty = 0
+        self.listbox.insert("end", str(item))
+
+    def get(self, index):
+        return self.listbox.get(index)
+
+    def click_event(self, event):
+        self.listbox.activate("@%d,%d" % (event.x, event.y))
+        index = self.listbox.index("active")
+        self.select(index)
+        self.on_select(index)
+        return "break"
+
+    def double_click_event(self, event):
+        index = self.listbox.index("active")
+        self.select(index)
+        self.on_double(index)
+        return "break"
+
+    menu = None
+
+    def popup_event(self, event):
+        if not self.menu:
+            self.make_menu()
+        menu = self.menu
+        self.listbox.activate("@%d,%d" % (event.x, event.y))
+        index = self.listbox.index("active")
+        self.select(index)
+        menu.tk_popup(event.x_root, event.y_root)
+        return "break"
+
+    def make_menu(self):
+        menu = Menu(self.listbox, tearoff=0)
+        self.menu = menu
+        self.fill_menu()
+
+    def up_event(self, event):
+        index = self.listbox.index("active")
+        if self.listbox.selection_includes(index):
+            index = index - 1
+        else:
+            index = self.listbox.size() - 1
+        if index < 0:
+            self.listbox.bell()
+        else:
+            self.select(index)
+            self.on_select(index)
+        return "break"
+
+    def down_event(self, event):
+        index = self.listbox.index("active")
+        if self.listbox.selection_includes(index):
+            index = index + 1
+        else:
+            index = 0
+        if index >= self.listbox.size():
+            self.listbox.bell()
+        else:
+            self.select(index)
+            self.on_select(index)
+        return "break"
+
+    def select(self, index):
+        self.listbox.focus_set()
+        self.listbox.activate(index)
+        self.listbox.selection_clear(0, "end")
+        self.listbox.selection_set(index)
+        self.listbox.see(index)
+
+    # Methods to override for specific actions
+
+    def fill_menu(self):
+        pass
+
+    def on_select(self, index):
+        pass
+
+    def on_double(self, index):
+        pass
+
+
+def _scrolled_list(parent):  # htest #
+    top = Toplevel(parent)
+    x, y = map(int, parent.geometry().split('+')[1:])
+    top.geometry("+%d+%d" % (x+200, y + 175))
+    class MyScrolledList(ScrolledList):
+        def fill_menu(self): self.menu.add_command(label="right click")
+        def on_select(self, index): print("select", self.get(index))
+        def on_double(self, index): print("double", self.get(index))
+
+    scrolled_list = MyScrolledList(top)
+    for i in range(30):
+        scrolled_list.append("Item %02d" % i)
+
+if __name__ == '__main__':
+    from unittest import main
+    main('idlelib.idle_test.test_scrolledlist', verbosity=2,)
+
+    from idlelib.idle_test.htest import run
+    run(_scrolled_list)
diff --git a/rootfs/usr/lib/python3.8/idlelib/search.py b/rootfs/usr/lib/python3.8/idlelib/search.py
new file mode 100644
index 0000000..b35f3b5
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/search.py
@@ -0,0 +1,164 @@
+"""Search dialog for Find, Find Again, and Find Selection
+   functionality.
+
+   Inherits from SearchDialogBase for GUI and uses searchengine
+   to prepare search pattern.
+"""
+from tkinter import TclError
+
+from idlelib import searchengine
+from idlelib.searchbase import SearchDialogBase
+
+def _setup(text):
+    """Return the new or existing singleton SearchDialog instance.
+
+    The singleton dialog saves user entries and preferences
+    across instances.
+
+    Args:
+        text: Text widget containing the text to be searched.
+    """
+    root = text._root()
+    engine = searchengine.get(root)
+    if not hasattr(engine, "_searchdialog"):
+        engine._searchdialog = SearchDialog(root, engine)
+    return engine._searchdialog
+
+def find(text):
+    """Open the search dialog.
+
+    Module-level function to access the singleton SearchDialog
+    instance and open the dialog.  If text is selected, it is
+    used as the search phrase; otherwise, the previous entry
+    is used.  No search is done with this command.
+    """
+    pat = text.get("sel.first", "sel.last")
+    return _setup(text).open(text, pat)  # Open is inherited from SDBase.
+
+def find_again(text):
+    """Repeat the search for the last pattern and preferences.
+
+    Module-level function to access the singleton SearchDialog
+    instance to search again using the user entries and preferences
+    from the last dialog.  If there was no prior search, open the
+    search dialog; otherwise, perform the search without showing the
+    dialog.
+    """
+    return _setup(text).find_again(text)
+
+def find_selection(text):
+    """Search for the selected pattern in the text.
+
+    Module-level function to access the singleton SearchDialog
+    instance to search using the selected text.  With a text
+    selection, perform the search without displaying the dialog.
+    Without a selection, use the prior entry as the search phrase
+    and don't display the dialog.  If there has been no prior
+    search, open the search dialog.
+    """
+    return _setup(text).find_selection(text)
+
+
+class SearchDialog(SearchDialogBase):
+    "Dialog for finding a pattern in text."
+
+    def create_widgets(self):
+        "Create the base search dialog and add a button for Find Next."
+        SearchDialogBase.create_widgets(self)
+        # TODO - why is this here and not in a create_command_buttons?
+        self.make_button("Find Next", self.default_command, isdef=True)
+
+    def default_command(self, event=None):
+        "Handle the Find Next button as the default command."
+        if not self.engine.getprog():
+            return
+        self.find_again(self.text)
+
+    def find_again(self, text):
+        """Repeat the last search.
+
+        If no search was previously run, open a new search dialog.  In
+        this case, no search is done.
+
+        If a search was previously run, the search dialog won't be
+        shown and the options from the previous search (including the
+        search pattern) will be used to find the next occurrence
+        of the pattern.  Next is relative based on direction.
+
+        Position the window to display the located occurrence in the
+        text.
+
+        Return True if the search was successful and False otherwise.
+        """
+        if not self.engine.getpat():
+            self.open(text)
+            return False
+        if not self.engine.getprog():
+            return False
+        res = self.engine.search_text(text)
+        if res:
+            line, m = res
+            i, j = m.span()
+            first = "%d.%d" % (line, i)
+            last = "%d.%d" % (line, j)
+            try:
+                selfirst = text.index("sel.first")
+                sellast = text.index("sel.last")
+                if selfirst == first and sellast == last:
+                    self.bell()
+                    return False
+            except TclError:
+                pass
+            text.tag_remove("sel", "1.0", "end")
+            text.tag_add("sel", first, last)
+            text.mark_set("insert", self.engine.isback() and first or last)
+            text.see("insert")
+            return True
+        else:
+            self.bell()
+            return False
+
+    def find_selection(self, text):
+        """Search for selected text with previous dialog preferences.
+
+        Instead of using the same pattern for searching (as Find
+        Again does), this first resets the pattern to the currently
+        selected text.  If the selected text isn't changed, then use
+        the prior search phrase.
+        """
+        pat = text.get("sel.first", "sel.last")
+        if pat:
+            self.engine.setcookedpat(pat)
+        return self.find_again(text)
+
+
+def _search_dialog(parent):  # htest #
+    "Display search test box."
+    from tkinter import Toplevel, Text
+    from tkinter.ttk import Frame, Button
+
+    top = Toplevel(parent)
+    top.title("Test SearchDialog")
+    x, y = map(int, parent.geometry().split('+')[1:])
+    top.geometry("+%d+%d" % (x, y + 175))
+
+    frame = Frame(top)
+    frame.pack()
+    text = Text(frame, inactiveselectbackground='gray')
+    text.pack()
+    text.insert("insert","This is a sample string.\n"*5)
+
+    def show_find():
+        text.tag_add('sel', '1.0', 'end')
+        _setup(text).open(text)
+        text.tag_remove('sel', '1.0', 'end')
+
+    button = Button(frame, text="Search (selection ignored)", command=show_find)
+    button.pack()
+
+if __name__ == '__main__':
+    from unittest import main
+    main('idlelib.idle_test.test_search', verbosity=2, exit=False)
+
+    from idlelib.idle_test.htest import run
+    run(_search_dialog)
diff --git a/rootfs/usr/lib/python3.8/idlelib/searchbase.py b/rootfs/usr/lib/python3.8/idlelib/searchbase.py
new file mode 100644
index 0000000..64ed50c
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/searchbase.py
@@ -0,0 +1,210 @@
+'''Define SearchDialogBase used by Search, Replace, and Grep dialogs.'''
+
+from tkinter import Toplevel
+from tkinter.ttk import Frame, Entry, Label, Button, Checkbutton, Radiobutton
+from tkinter.simpledialog import _setup_dialog
+
+
+class SearchDialogBase:
+    '''Create most of a 3 or 4 row, 3 column search dialog.
+
+    The left and wide middle column contain:
+    1 or 2 labeled text entry lines (make_entry, create_entries);
+    a row of standard Checkbuttons (make_frame, create_option_buttons),
+    each of which corresponds to a search engine Variable;
+    a row of dialog-specific Check/Radiobuttons (create_other_buttons).
+
+    The narrow right column contains command buttons
+    (make_button, create_command_buttons).
+    These are bound to functions that execute the command.
+
+    Except for command buttons, this base class is not limited to items
+    common to all three subclasses.  Rather, it is the Find dialog minus
+    the "Find Next" command, its execution function, and the
+    default_command attribute needed in create_widgets. The other
+    dialogs override attributes and methods, the latter to replace and
+    add widgets.
+    '''
+
+    title = "Search Dialog"  # replace in subclasses
+    icon = "Search"
+    needwrapbutton = 1  # not in Find in Files
+
+    def __init__(self, root, engine):
+        '''Initialize root, engine, and top attributes.
+
+        top (level widget): set in create_widgets() called from open().
+        frame: container for all widgets in dialog.
+        text (Text searched): set in open(), only used in subclasses().
+        ent (ry): created in make_entry() called from create_entry().
+        row (of grid): 0 in create_widgets(), +1 in make_entry/frame().
+        default_command: set in subclasses, used in create_widgets().
+
+        title (of dialog): class attribute, override in subclasses.
+        icon (of dialog): ditto, use unclear if cannot minimize dialog.
+        '''
+        self.root = root
+        self.bell = root.bell
+        self.engine = engine
+        self.top = None
+
+    def open(self, text, searchphrase=None):
+        "Make dialog visible on top of others and ready to use."
+        self.text = text
+        if not self.top:
+            self.create_widgets()
+        else:
+            self.top.deiconify()
+            self.top.tkraise()
+        self.top.transient(text.winfo_toplevel())
+        if searchphrase:
+            self.ent.delete(0,"end")
+            self.ent.insert("end",searchphrase)
+        self.ent.focus_set()
+        self.ent.selection_range(0, "end")
+        self.ent.icursor(0)
+        self.top.grab_set()
+
+    def close(self, event=None):
+        "Put dialog away for later use."
+        if self.top:
+            self.top.grab_release()
+            self.top.transient('')
+            self.top.withdraw()
+
+    def create_widgets(self):
+        '''Create basic 3 row x 3 col search (find) dialog.
+
+        Other dialogs override subsidiary create_x methods as needed.
+        Replace and Find-in-Files add another entry row.
+        '''
+        top = Toplevel(self.root)
+        top.bind("<Return>", self.default_command)
+        top.bind("<Escape>", self.close)
+        top.protocol("WM_DELETE_WINDOW", self.close)
+        top.wm_title(self.title)
+        top.wm_iconname(self.icon)
+        _setup_dialog(top)
+        self.top = top
+        self.frame = Frame(top, padding="5px")
+        self.frame.grid(sticky="nwes")
+        top.grid_columnconfigure(0, weight=100)
+        top.grid_rowconfigure(0, weight=100)
+
+        self.row = 0
+        self.frame.grid_columnconfigure(0, pad=2, weight=0)
+        self.frame.grid_columnconfigure(1, pad=2, minsize=100, weight=100)
+
+        self.create_entries()  # row 0 (and maybe 1), cols 0, 1
+        self.create_option_buttons()  # next row, cols 0, 1
+        self.create_other_buttons()  # next row, cols 0, 1
+        self.create_command_buttons()  # col 2, all rows
+
+    def make_entry(self, label_text, var):
+        '''Return (entry, label), .
+
+        entry - gridded labeled Entry for text entry.
+        label - Label widget, returned for testing.
+        '''
+        label = Label(self.frame, text=label_text)
+        label.grid(row=self.row, column=0, sticky="nw")
+        entry = Entry(self.frame, textvariable=var, exportselection=0)
+        entry.grid(row=self.row, column=1, sticky="nwe")
+        self.row = self.row + 1
+        return entry, label
+
+    def create_entries(self):
+        "Create one or more entry lines with make_entry."
+        self.ent = self.make_entry("Find:", self.engine.patvar)[0]
+
+    def make_frame(self,labeltext=None):
+        '''Return (frame, label).
+
+        frame - gridded labeled Frame for option or other buttons.
+        label - Label widget, returned for testing.
+        '''
+        if labeltext:
+            label = Label(self.frame, text=labeltext)
+            label.grid(row=self.row, column=0, sticky="nw")
+        else:
+            label = ''
+        frame = Frame(self.frame)
+        frame.grid(row=self.row, column=1, columnspan=1, sticky="nwe")
+        self.row = self.row + 1
+        return frame, label
+
+    def create_option_buttons(self):
+        '''Return (filled frame, options) for testing.
+
+        Options is a list of searchengine booleanvar, label pairs.
+        A gridded frame from make_frame is filled with a Checkbutton
+        for each pair, bound to the var, with the corresponding label.
+        '''
+        frame = self.make_frame("Options")[0]
+        engine = self.engine
+        options = [(engine.revar, "Regular expression"),
+                   (engine.casevar, "Match case"),
+                   (engine.wordvar, "Whole word")]
+        if self.needwrapbutton:
+            options.append((engine.wrapvar, "Wrap around"))
+        for var, label in options:
+            btn = Checkbutton(frame, variable=var, text=label)
+            btn.pack(side="left", fill="both")
+        return frame, options
+
+    def create_other_buttons(self):
+        '''Return (frame, others) for testing.
+
+        Others is a list of value, label pairs.
+        A gridded frame from make_frame is filled with radio buttons.
+        '''
+        frame = self.make_frame("Direction")[0]
+        var = self.engine.backvar
+        others = [(1, 'Up'), (0, 'Down')]
+        for val, label in others:
+            btn = Radiobutton(frame, variable=var, value=val, text=label)
+            btn.pack(side="left", fill="both")
+        return frame, others
+
+    def make_button(self, label, command, isdef=0):
+        "Return command button gridded in command frame."
+        b = Button(self.buttonframe,
+                   text=label, command=command,
+                   default=isdef and "active" or "normal")
+        cols,rows=self.buttonframe.grid_size()
+        b.grid(pady=1,row=rows,column=0,sticky="ew")
+        self.buttonframe.grid(rowspan=rows+1)
+        return b
+
+    def create_command_buttons(self):
+        "Place buttons in vertical command frame gridded on right."
+        f = self.buttonframe = Frame(self.frame)
+        f.grid(row=0,column=2,padx=2,pady=2,ipadx=2,ipady=2)
+
+        b = self.make_button("Close", self.close)
+        b.lower()
+
+
+class _searchbase(SearchDialogBase):  # htest #
+    "Create auto-opening dialog with no text connection."
+
+    def __init__(self, parent):
+        import re
+        from idlelib import searchengine
+
+        self.root = parent
+        self.engine = searchengine.get(parent)
+        self.create_widgets()
+        print(parent.geometry())
+        width,height, x,y = list(map(int, re.split('[x+]', parent.geometry())))
+        self.top.geometry("+%d+%d" % (x + 40, y + 175))
+
+    def default_command(self, dummy): pass
+
+
+if __name__ == '__main__':
+    from unittest import main
+    main('idlelib.idle_test.test_searchbase', verbosity=2, exit=False)
+
+    from idlelib.idle_test.htest import run
+    run(_searchbase)
diff --git a/rootfs/usr/lib/python3.8/idlelib/searchengine.py b/rootfs/usr/lib/python3.8/idlelib/searchengine.py
new file mode 100644
index 0000000..eddef58
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/searchengine.py
@@ -0,0 +1,234 @@
+'''Define SearchEngine for search dialogs.'''
+import re
+
+from tkinter import StringVar, BooleanVar, TclError
+from tkinter import messagebox
+
+def get(root):
+    '''Return the singleton SearchEngine instance for the process.
+
+    The single SearchEngine saves settings between dialog instances.
+    If there is not a SearchEngine already, make one.
+    '''
+    if not hasattr(root, "_searchengine"):
+        root._searchengine = SearchEngine(root)
+        # This creates a cycle that persists until root is deleted.
+    return root._searchengine
+
+
+class SearchEngine:
+    """Handles searching a text widget for Find, Replace, and Grep."""
+
+    def __init__(self, root):
+        '''Initialize Variables that save search state.
+
+        The dialogs bind these to the UI elements present in the dialogs.
+        '''
+        self.root = root  # need for report_error()
+        self.patvar = StringVar(root, '')   # search pattern
+        self.revar = BooleanVar(root, False)   # regular expression?
+        self.casevar = BooleanVar(root, False)   # match case?
+        self.wordvar = BooleanVar(root, False)   # match whole word?
+        self.wrapvar = BooleanVar(root, True)   # wrap around buffer?
+        self.backvar = BooleanVar(root, False)   # search backwards?
+
+    # Access methods
+
+    def getpat(self):
+        return self.patvar.get()
+
+    def setpat(self, pat):
+        self.patvar.set(pat)
+
+    def isre(self):
+        return self.revar.get()
+
+    def iscase(self):
+        return self.casevar.get()
+
+    def isword(self):
+        return self.wordvar.get()
+
+    def iswrap(self):
+        return self.wrapvar.get()
+
+    def isback(self):
+        return self.backvar.get()
+
+    # Higher level access methods
+
+    def setcookedpat(self, pat):
+        "Set pattern after escaping if re."
+        # called only in search.py: 66
+        if self.isre():
+            pat = re.escape(pat)
+        self.setpat(pat)
+
+    def getcookedpat(self):
+        pat = self.getpat()
+        if not self.isre():  # if True, see setcookedpat
+            pat = re.escape(pat)
+        if self.isword():
+            pat = r"\b%s\b" % pat
+        return pat
+
+    def getprog(self):
+        "Return compiled cooked search pattern."
+        pat = self.getpat()
+        if not pat:
+            self.report_error(pat, "Empty regular expression")
+            return None
+        pat = self.getcookedpat()
+        flags = 0
+        if not self.iscase():
+            flags = flags | re.IGNORECASE
+        try:
+            prog = re.compile(pat, flags)
+        except re.error as e:
+            self.report_error(pat, e.msg, e.pos)
+            return None
+        return prog
+
+    def report_error(self, pat, msg, col=None):
+        # Derived class could override this with something fancier
+        msg = "Error: " + str(msg)
+        if pat:
+            msg = msg + "\nPattern: " + str(pat)
+        if col is not None:
+            msg = msg + "\nOffset: " + str(col)
+        messagebox.showerror("Regular expression error",
+                               msg, master=self.root)
+
+    def search_text(self, text, prog=None, ok=0):
+        '''Return (lineno, matchobj) or None for forward/backward search.
+
+        This function calls the right function with the right arguments.
+        It directly return the result of that call.
+
+        Text is a text widget. Prog is a precompiled pattern.
+        The ok parameter is a bit complicated as it has two effects.
+
+        If there is a selection, the search begin at either end,
+        depending on the direction setting and ok, with ok meaning that
+        the search starts with the selection. Otherwise, search begins
+        at the insert mark.
+
+        To aid progress, the search functions do not return an empty
+        match at the starting position unless ok is True.
+        '''
+
+        if not prog:
+            prog = self.getprog()
+            if not prog:
+                return None # Compilation failed -- stop
+        wrap = self.wrapvar.get()
+        first, last = get_selection(text)
+        if self.isback():
+            if ok:
+                start = last
+            else:
+                start = first
+            line, col = get_line_col(start)
+            res = self.search_backward(text, prog, line, col, wrap, ok)
+        else:
+            if ok:
+                start = first
+            else:
+                start = last
+            line, col = get_line_col(start)
+            res = self.search_forward(text, prog, line, col, wrap, ok)
+        return res
+
+    def search_forward(self, text, prog, line, col, wrap, ok=0):
+        wrapped = 0
+        startline = line
+        chars = text.get("%d.0" % line, "%d.0" % (line+1))
+        while chars:
+            m = prog.search(chars[:-1], col)
+            if m:
+                if ok or m.end() > col:
+                    return line, m
+            line = line + 1
+            if wrapped and line > startline:
+                break
+            col = 0
+            ok = 1
+            chars = text.get("%d.0" % line, "%d.0" % (line+1))
+            if not chars and wrap:
+                wrapped = 1
+                wrap = 0
+                line = 1
+                chars = text.get("1.0", "2.0")
+        return None
+
+    def search_backward(self, text, prog, line, col, wrap, ok=0):
+        wrapped = 0
+        startline = line
+        chars = text.get("%d.0" % line, "%d.0" % (line+1))
+        while 1:
+            m = search_reverse(prog, chars[:-1], col)
+            if m:
+                if ok or m.start() < col:
+                    return line, m
+            line = line - 1
+            if wrapped and line < startline:
+                break
+            ok = 1
+            if line <= 0:
+                if not wrap:
+                    break
+                wrapped = 1
+                wrap = 0
+                pos = text.index("end-1c")
+                line, col = map(int, pos.split("."))
+            chars = text.get("%d.0" % line, "%d.0" % (line+1))
+            col = len(chars) - 1
+        return None
+
+
+def search_reverse(prog, chars, col):
+    '''Search backwards and return an re match object or None.
+
+    This is done by searching forwards until there is no match.
+    Prog: compiled re object with a search method returning a match.
+    Chars: line of text, without \\n.
+    Col: stop index for the search; the limit for match.end().
+    '''
+    m = prog.search(chars)
+    if not m:
+        return None
+    found = None
+    i, j = m.span()  # m.start(), m.end() == match slice indexes
+    while i < col and j <= col:
+        found = m
+        if i == j:
+            j = j+1
+        m = prog.search(chars, j)
+        if not m:
+            break
+        i, j = m.span()
+    return found
+
+def get_selection(text):
+    '''Return tuple of 'line.col' indexes from selection or insert mark.
+    '''
+    try:
+        first = text.index("sel.first")
+        last = text.index("sel.last")
+    except TclError:
+        first = last = None
+    if not first:
+        first = text.index("insert")
+    if not last:
+        last = first
+    return first, last
+
+def get_line_col(index):
+    '''Return (line, col) tuple of ints from 'line.col' string.'''
+    line, col = map(int, index.split(".")) # Fails on invalid index
+    return line, col
+
+
+if __name__ == "__main__":
+    from unittest import main
+    main('idlelib.idle_test.test_searchengine', verbosity=2)
diff --git a/rootfs/usr/lib/python3.8/idlelib/sidebar.py b/rootfs/usr/lib/python3.8/idlelib/sidebar.py
new file mode 100644
index 0000000..41c0968
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/sidebar.py
@@ -0,0 +1,341 @@
+"""Line numbering implementation for IDLE as an extension.
+Includes BaseSideBar which can be extended for other sidebar based extensions
+"""
+import functools
+import itertools
+
+import tkinter as tk
+from idlelib.config import idleConf
+from idlelib.delegator import Delegator
+
+
+def get_end_linenumber(text):
+    """Utility to get the last line's number in a Tk text widget."""
+    return int(float(text.index('end-1c')))
+
+
+def get_widget_padding(widget):
+    """Get the total padding of a Tk widget, including its border."""
+    # TODO: use also in codecontext.py
+    manager = widget.winfo_manager()
+    if manager == 'pack':
+        info = widget.pack_info()
+    elif manager == 'grid':
+        info = widget.grid_info()
+    else:
+        raise ValueError(f"Unsupported geometry manager: {manager}")
+
+    # All values are passed through getint(), since some
+    # values may be pixel objects, which can't simply be added to ints.
+    padx = sum(map(widget.tk.getint, [
+        info['padx'],
+        widget.cget('padx'),
+        widget.cget('border'),
+    ]))
+    pady = sum(map(widget.tk.getint, [
+        info['pady'],
+        widget.cget('pady'),
+        widget.cget('border'),
+    ]))
+    return padx, pady
+
+
+class BaseSideBar:
+    """
+    The base class for extensions which require a sidebar.
+    """
+    def __init__(self, editwin):
+        self.editwin = editwin
+        self.parent = editwin.text_frame
+        self.text = editwin.text
+
+        _padx, pady = get_widget_padding(self.text)
+        self.sidebar_text = tk.Text(self.parent, width=1, wrap=tk.NONE,
+                                    padx=2, pady=pady,
+                                    borderwidth=0, highlightthickness=0)
+        self.sidebar_text.config(state=tk.DISABLED)
+        self.text['yscrollcommand'] = self.redirect_yscroll_event
+        self.update_font()
+        self.update_colors()
+
+        self.is_shown = False
+
+    def update_font(self):
+        """Update the sidebar text font, usually after config changes."""
+        font = idleConf.GetFont(self.text, 'main', 'EditorWindow')
+        self._update_font(font)
+
+    def _update_font(self, font):
+        self.sidebar_text['font'] = font
+
+    def update_colors(self):
+        """Update the sidebar text colors, usually after config changes."""
+        colors = idleConf.GetHighlight(idleConf.CurrentTheme(), 'normal')
+        self._update_colors(foreground=colors['foreground'],
+                            background=colors['background'])
+
+    def _update_colors(self, foreground, background):
+        self.sidebar_text.config(
+            fg=foreground, bg=background,
+            selectforeground=foreground, selectbackground=background,
+            inactiveselectbackground=background,
+        )
+
+    def show_sidebar(self):
+        if not self.is_shown:
+            self.sidebar_text.grid(row=1, column=0, sticky=tk.NSEW)
+            self.is_shown = True
+
+    def hide_sidebar(self):
+        if self.is_shown:
+            self.sidebar_text.grid_forget()
+            self.is_shown = False
+
+    def redirect_yscroll_event(self, *args, **kwargs):
+        """Redirect vertical scrolling to the main editor text widget.
+
+        The scroll bar is also updated.
+        """
+        self.editwin.vbar.set(*args)
+        self.sidebar_text.yview_moveto(args[0])
+        return 'break'
+
+    def redirect_focusin_event(self, event):
+        """Redirect focus-in events to the main editor text widget."""
+        self.text.focus_set()
+        return 'break'
+
+    def redirect_mousebutton_event(self, event, event_name):
+        """Redirect mouse button events to the main editor text widget."""
+        self.text.focus_set()
+        self.text.event_generate(event_name, x=0, y=event.y)
+        return 'break'
+
+    def redirect_mousewheel_event(self, event):
+        """Redirect mouse wheel events to the editwin text widget."""
+        self.text.event_generate('<MouseWheel>',
+                                 x=0, y=event.y, delta=event.delta)
+        return 'break'
+
+
+class EndLineDelegator(Delegator):
+    """Generate callbacks with the current end line number after
+       insert or delete operations"""
+    def __init__(self, changed_callback):
+        """
+        changed_callback - Callable, will be called after insert
+                           or delete operations with the current
+                           end line number.
+        """
+        Delegator.__init__(self)
+        self.changed_callback = changed_callback
+
+    def insert(self, index, chars, tags=None):
+        self.delegate.insert(index, chars, tags)
+        self.changed_callback(get_end_linenumber(self.delegate))
+
+    def delete(self, index1, index2=None):
+        self.delegate.delete(index1, index2)
+        self.changed_callback(get_end_linenumber(self.delegate))
+
+
+class LineNumbers(BaseSideBar):
+    """Line numbers support for editor windows."""
+    def __init__(self, editwin):
+        BaseSideBar.__init__(self, editwin)
+        self.prev_end = 1
+        self._sidebar_width_type = type(self.sidebar_text['width'])
+        self.sidebar_text.config(state=tk.NORMAL)
+        self.sidebar_text.insert('insert', '1', 'linenumber')
+        self.sidebar_text.config(state=tk.DISABLED)
+        self.sidebar_text.config(takefocus=False, exportselection=False)
+        self.sidebar_text.tag_config('linenumber', justify=tk.RIGHT)
+
+        self.bind_events()
+
+        end = get_end_linenumber(self.text)
+        self.update_sidebar_text(end)
+
+        end_line_delegator = EndLineDelegator(self.update_sidebar_text)
+        # Insert the delegator after the undo delegator, so that line numbers
+        # are properly updated after undo and redo actions.
+        end_line_delegator.setdelegate(self.editwin.undo.delegate)
+        self.editwin.undo.setdelegate(end_line_delegator)
+        # Reset the delegator caches of the delegators "above" the
+        # end line delegator we just inserted.
+        delegator = self.editwin.per.top
+        while delegator is not end_line_delegator:
+            delegator.resetcache()
+            delegator = delegator.delegate
+
+        self.is_shown = False
+
+    def bind_events(self):
+        # Ensure focus is always redirected to the main editor text widget.
+        self.sidebar_text.bind('<FocusIn>', self.redirect_focusin_event)
+
+        # Redirect mouse scrolling to the main editor text widget.
+        #
+        # Note that without this, scrolling with the mouse only scrolls
+        # the line numbers.
+        self.sidebar_text.bind('<MouseWheel>', self.redirect_mousewheel_event)
+
+        # Redirect mouse button events to the main editor text widget,
+        # except for the left mouse button (1).
+        #
+        # Note: X-11 sends Button-4 and Button-5 events for the scroll wheel.
+        def bind_mouse_event(event_name, target_event_name):
+            handler = functools.partial(self.redirect_mousebutton_event,
+                                        event_name=target_event_name)
+            self.sidebar_text.bind(event_name, handler)
+
+        for button in [2, 3, 4, 5]:
+            for event_name in (f'<Button-{button}>',
+                               f'<ButtonRelease-{button}>',
+                               f'<B{button}-Motion>',
+                               ):
+                bind_mouse_event(event_name, target_event_name=event_name)
+
+            # Convert double- and triple-click events to normal click events,
+            # since event_generate() doesn't allow generating such events.
+            for event_name in (f'<Double-Button-{button}>',
+                               f'<Triple-Button-{button}>',
+                               ):
+                bind_mouse_event(event_name,
+                                 target_event_name=f'<Button-{button}>')
+
+        # This is set by b1_mousedown_handler() and read by
+        # drag_update_selection_and_insert_mark(), to know where dragging
+        # began.
+        start_line = None
+        # These are set by b1_motion_handler() and read by selection_handler().
+        # last_y is passed this way since the mouse Y-coordinate is not
+        # available on selection event objects.  last_yview is passed this way
+        # to recognize scrolling while the mouse isn't moving.
+        last_y = last_yview = None
+
+        def b1_mousedown_handler(event):
+            # select the entire line
+            lineno = int(float(self.sidebar_text.index(f"@0,{event.y}")))
+            self.text.tag_remove("sel", "1.0", "end")
+            self.text.tag_add("sel", f"{lineno}.0", f"{lineno+1}.0")
+            self.text.mark_set("insert", f"{lineno+1}.0")
+
+            # remember this line in case this is the beginning of dragging
+            nonlocal start_line
+            start_line = lineno
+        self.sidebar_text.bind('<Button-1>', b1_mousedown_handler)
+
+        def b1_mouseup_handler(event):
+            # On mouse up, we're no longer dragging.  Set the shared persistent
+            # variables to None to represent this.
+            nonlocal start_line
+            nonlocal last_y
+            nonlocal last_yview
+            start_line = None
+            last_y = None
+            last_yview = None
+        self.sidebar_text.bind('<ButtonRelease-1>', b1_mouseup_handler)
+
+        def drag_update_selection_and_insert_mark(y_coord):
+            """Helper function for drag and selection event handlers."""
+            lineno = int(float(self.sidebar_text.index(f"@0,{y_coord}")))
+            a, b = sorted([start_line, lineno])
+            self.text.tag_remove("sel", "1.0", "end")
+            self.text.tag_add("sel", f"{a}.0", f"{b+1}.0")
+            self.text.mark_set("insert",
+                               f"{lineno if lineno == a else lineno + 1}.0")
+
+        # Special handling of dragging with mouse button 1.  In "normal" text
+        # widgets this selects text, but the line numbers text widget has
+        # selection disabled.  Still, dragging triggers some selection-related
+        # functionality under the hood.  Specifically, dragging to above or
+        # below the text widget triggers scrolling, in a way that bypasses the
+        # other scrolling synchronization mechanisms.i
+        def b1_drag_handler(event, *args):
+            nonlocal last_y
+            nonlocal last_yview
+            last_y = event.y
+            last_yview = self.sidebar_text.yview()
+            if not 0 <= last_y <= self.sidebar_text.winfo_height():
+                self.text.yview_moveto(last_yview[0])
+            drag_update_selection_and_insert_mark(event.y)
+        self.sidebar_text.bind('<B1-Motion>', b1_drag_handler)
+
+        # With mouse-drag scrolling fixed by the above, there is still an edge-
+        # case we need to handle: When drag-scrolling, scrolling can continue
+        # while the mouse isn't moving, leading to the above fix not scrolling
+        # properly.
+        def selection_handler(event):
+            if last_yview is None:
+                # This logic is only needed while dragging.
+                return
+            yview = self.sidebar_text.yview()
+            if yview != last_yview:
+                self.text.yview_moveto(yview[0])
+                drag_update_selection_and_insert_mark(last_y)
+        self.sidebar_text.bind('<<Selection>>', selection_handler)
+
+    def update_colors(self):
+        """Update the sidebar text colors, usually after config changes."""
+        colors = idleConf.GetHighlight(idleConf.CurrentTheme(), 'linenumber')
+        self._update_colors(foreground=colors['foreground'],
+                            background=colors['background'])
+
+    def update_sidebar_text(self, end):
+        """
+        Perform the following action:
+        Each line sidebar_text contains the linenumber for that line
+        Synchronize with editwin.text so that both sidebar_text and
+        editwin.text contain the same number of lines"""
+        if end == self.prev_end:
+            return
+
+        width_difference = len(str(end)) - len(str(self.prev_end))
+        if width_difference:
+            cur_width = int(float(self.sidebar_text['width']))
+            new_width = cur_width + width_difference
+            self.sidebar_text['width'] = self._sidebar_width_type(new_width)
+
+        self.sidebar_text.config(state=tk.NORMAL)
+        if end > self.prev_end:
+            new_text = '\n'.join(itertools.chain(
+                [''],
+                map(str, range(self.prev_end + 1, end + 1)),
+            ))
+            self.sidebar_text.insert(f'end -1c', new_text, 'linenumber')
+        else:
+            self.sidebar_text.delete(f'{end+1}.0 -1c', 'end -1c')
+        self.sidebar_text.config(state=tk.DISABLED)
+
+        self.prev_end = end
+
+
+def _linenumbers_drag_scrolling(parent):  # htest #
+    from idlelib.idle_test.test_sidebar import Dummy_editwin
+
+    toplevel = tk.Toplevel(parent)
+    text_frame = tk.Frame(toplevel)
+    text_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
+    text_frame.rowconfigure(1, weight=1)
+    text_frame.columnconfigure(1, weight=1)
+
+    font = idleConf.GetFont(toplevel, 'main', 'EditorWindow')
+    text = tk.Text(text_frame, width=80, height=24, wrap=tk.NONE, font=font)
+    text.grid(row=1, column=1, sticky=tk.NSEW)
+
+    editwin = Dummy_editwin(text)
+    editwin.vbar = tk.Scrollbar(text_frame)
+
+    linenumbers = LineNumbers(editwin)
+    linenumbers.show_sidebar()
+
+    text.insert('1.0', '\n'.join('a'*i for i in range(1, 101)))
+
+
+if __name__ == '__main__':
+    from unittest import main
+    main('idlelib.idle_test.test_sidebar', verbosity=2, exit=False)
+
+    from idlelib.idle_test.htest import run
+    run(_linenumbers_drag_scrolling)
diff --git a/rootfs/usr/lib/python3.8/idlelib/squeezer.py b/rootfs/usr/lib/python3.8/idlelib/squeezer.py
new file mode 100644
index 0000000..3046d80
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/squeezer.py
@@ -0,0 +1,345 @@
+"""An IDLE extension to avoid having very long texts printed in the shell.
+
+A common problem in IDLE's interactive shell is printing of large amounts of
+text into the shell. This makes looking at the previous history difficult.
+Worse, this can cause IDLE to become very slow, even to the point of being
+completely unusable.
+
+This extension will automatically replace long texts with a small button.
+Double-clicking this button will remove it and insert the original text instead.
+Middle-clicking will copy the text to the clipboard. Right-clicking will open
+the text in a separate viewing window.
+
+Additionally, any output can be manually "squeezed" by the user. This includes
+output written to the standard error stream ("stderr"), such as exception
+messages and their tracebacks.
+"""
+import re
+
+import tkinter as tk
+from tkinter import messagebox
+
+from idlelib.config import idleConf
+from idlelib.textview import view_text
+from idlelib.tooltip import Hovertip
+from idlelib import macosx
+
+
+def count_lines_with_wrapping(s, linewidth=80):
+    """Count the number of lines in a given string.
+
+    Lines are counted as if the string was wrapped so that lines are never over
+    linewidth characters long.
+
+    Tabs are considered tabwidth characters long.
+    """
+    tabwidth = 8  # Currently always true in Shell.
+    pos = 0
+    linecount = 1
+    current_column = 0
+
+    for m in re.finditer(r"[\t\n]", s):
+        # Process the normal chars up to tab or newline.
+        numchars = m.start() - pos
+        pos += numchars
+        current_column += numchars
+
+        # Deal with tab or newline.
+        if s[pos] == '\n':
+            # Avoid the `current_column == 0` edge-case, and while we're
+            # at it, don't bother adding 0.
+            if current_column > linewidth:
+                # If the current column was exactly linewidth, divmod
+                # would give (1,0), even though a new line hadn't yet
+                # been started. The same is true if length is any exact
+                # multiple of linewidth. Therefore, subtract 1 before
+                # dividing a non-empty line.
+                linecount += (current_column - 1) // linewidth
+            linecount += 1
+            current_column = 0
+        else:
+            assert s[pos] == '\t'
+            current_column += tabwidth - (current_column % tabwidth)
+
+            # If a tab passes the end of the line, consider the entire
+            # tab as being on the next line.
+            if current_column > linewidth:
+                linecount += 1
+                current_column = tabwidth
+
+        pos += 1 # After the tab or newline.
+
+    # Process remaining chars (no more tabs or newlines).
+    current_column += len(s) - pos
+    # Avoid divmod(-1, linewidth).
+    if current_column > 0:
+        linecount += (current_column - 1) // linewidth
+    else:
+        # Text ended with newline; don't count an extra line after it.
+        linecount -= 1
+
+    return linecount
+
+
+class ExpandingButton(tk.Button):
+    """Class for the "squeezed" text buttons used by Squeezer
+
+    These buttons are displayed inside a Tk Text widget in place of text. A
+    user can then use the button to replace it with the original text, copy
+    the original text to the clipboard or view the original text in a separate
+    window.
+
+    Each button is tied to a Squeezer instance, and it knows to update the
+    Squeezer instance when it is expanded (and therefore removed).
+    """
+    def __init__(self, s, tags, numoflines, squeezer):
+        self.s = s
+        self.tags = tags
+        self.numoflines = numoflines
+        self.squeezer = squeezer
+        self.editwin = editwin = squeezer.editwin
+        self.text = text = editwin.text
+        # The base Text widget is needed to change text before iomark.
+        self.base_text = editwin.per.bottom
+
+        line_plurality = "lines" if numoflines != 1 else "line"
+        button_text = f"Squeezed text ({numoflines} {line_plurality})."
+        tk.Button.__init__(self, text, text=button_text,
+                           background="#FFFFC0", activebackground="#FFFFE0")
+
+        button_tooltip_text = (
+            "Double-click to expand, right-click for more options."
+        )
+        Hovertip(self, button_tooltip_text, hover_delay=80)
+
+        self.bind("<Double-Button-1>", self.expand)
+        if macosx.isAquaTk():
+            # AquaTk defines <2> as the right button, not <3>.
+            self.bind("<Button-2>", self.context_menu_event)
+        else:
+            self.bind("<Button-3>", self.context_menu_event)
+        self.selection_handle(  # X windows only.
+            lambda offset, length: s[int(offset):int(offset) + int(length)])
+
+        self.is_dangerous = None
+        self.after_idle(self.set_is_dangerous)
+
+    def set_is_dangerous(self):
+        dangerous_line_len = 50 * self.text.winfo_width()
+        self.is_dangerous = (
+            self.numoflines > 1000 or
+            len(self.s) > 50000 or
+            any(
+                len(line_match.group(0)) >= dangerous_line_len
+                for line_match in re.finditer(r'[^\n]+', self.s)
+            )
+        )
+
+    def expand(self, event=None):
+        """expand event handler
+
+        This inserts the original text in place of the button in the Text
+        widget, removes the button and updates the Squeezer instance.
+
+        If the original text is dangerously long, i.e. expanding it could
+        cause a performance degradation, ask the user for confirmation.
+        """
+        if self.is_dangerous is None:
+            self.set_is_dangerous()
+        if self.is_dangerous:
+            confirm = messagebox.askokcancel(
+                title="Expand huge output?",
+                message="\n\n".join([
+                    "The squeezed output is very long: %d lines, %d chars.",
+                    "Expanding it could make IDLE slow or unresponsive.",
+                    "It is recommended to view or copy the output instead.",
+                    "Really expand?"
+                ]) % (self.numoflines, len(self.s)),
+                default=messagebox.CANCEL,
+                parent=self.text)
+            if not confirm:
+                return "break"
+
+        self.base_text.insert(self.text.index(self), self.s, self.tags)
+        self.base_text.delete(self)
+        self.squeezer.expandingbuttons.remove(self)
+
+    def copy(self, event=None):
+        """copy event handler
+
+        Copy the original text to the clipboard.
+        """
+        self.clipboard_clear()
+        self.clipboard_append(self.s)
+
+    def view(self, event=None):
+        """view event handler
+
+        View the original text in a separate text viewer window.
+        """
+        view_text(self.text, "Squeezed Output Viewer", self.s,
+                  modal=False, wrap='none')
+
+    rmenu_specs = (
+        # Item structure: (label, method_name).
+        ('copy', 'copy'),
+        ('view', 'view'),
+    )
+
+    def context_menu_event(self, event):
+        self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
+        rmenu = tk.Menu(self.text, tearoff=0)
+        for label, method_name in self.rmenu_specs:
+            rmenu.add_command(label=label, command=getattr(self, method_name))
+        rmenu.tk_popup(event.x_root, event.y_root)
+        return "break"
+
+
+class Squeezer:
+    """Replace long outputs in the shell with a simple button.
+
+    This avoids IDLE's shell slowing down considerably, and even becoming
+    completely unresponsive, when very long outputs are written.
+    """
+    @classmethod
+    def reload(cls):
+        """Load class variables from config."""
+        cls.auto_squeeze_min_lines = idleConf.GetOption(
+            "main", "PyShell", "auto-squeeze-min-lines",
+            type="int", default=50,
+        )
+
+    def __init__(self, editwin):
+        """Initialize settings for Squeezer.
+
+        editwin is the shell's Editor window.
+        self.text is the editor window text widget.
+        self.base_test is the actual editor window Tk text widget, rather than
+            EditorWindow's wrapper.
+        self.expandingbuttons is the list of all buttons representing
+            "squeezed" output.
+        """
+        self.editwin = editwin
+        self.text = text = editwin.text
+
+        # Get the base Text widget of the PyShell object, used to change
+        # text before the iomark. PyShell deliberately disables changing
+        # text before the iomark via its 'text' attribute, which is
+        # actually a wrapper for the actual Text widget. Squeezer,
+        # however, needs to make such changes.
+        self.base_text = editwin.per.bottom
+
+        # Twice the text widget's border width and internal padding;
+        # pre-calculated here for the get_line_width() method.
+        self.window_width_delta = 2 * (
+            int(text.cget('border')) +
+            int(text.cget('padx'))
+        )
+
+        self.expandingbuttons = []
+
+        # Replace the PyShell instance's write method with a wrapper,
+        # which inserts an ExpandingButton instead of a long text.
+        def mywrite(s, tags=(), write=editwin.write):
+            # Only auto-squeeze text which has just the "stdout" tag.
+            if tags != "stdout":
+                return write(s, tags)
+
+            # Only auto-squeeze text with at least the minimum
+            # configured number of lines.
+            auto_squeeze_min_lines = self.auto_squeeze_min_lines
+            # First, a very quick check to skip very short texts.
+            if len(s) < auto_squeeze_min_lines:
+                return write(s, tags)
+            # Now the full line-count check.
+            numoflines = self.count_lines(s)
+            if numoflines < auto_squeeze_min_lines:
+                return write(s, tags)
+
+            # Create an ExpandingButton instance.
+            expandingbutton = ExpandingButton(s, tags, numoflines, self)
+
+            # Insert the ExpandingButton into the Text widget.
+            text.mark_gravity("iomark", tk.RIGHT)
+            text.window_create("iomark", window=expandingbutton,
+                               padx=3, pady=5)
+            text.see("iomark")
+            text.update()
+            text.mark_gravity("iomark", tk.LEFT)
+
+            # Add the ExpandingButton to the Squeezer's list.
+            self.expandingbuttons.append(expandingbutton)
+
+        editwin.write = mywrite
+
+    def count_lines(self, s):
+        """Count the number of lines in a given text.
+
+        Before calculation, the tab width and line length of the text are
+        fetched, so that up-to-date values are used.
+
+        Lines are counted as if the string was wrapped so that lines are never
+        over linewidth characters long.
+
+        Tabs are considered tabwidth characters long.
+        """
+        return count_lines_with_wrapping(s, self.editwin.width)
+
+    def squeeze_current_text_event(self, event):
+        """squeeze-current-text event handler
+
+        Squeeze the block of text inside which contains the "insert" cursor.
+
+        If the insert cursor is not in a squeezable block of text, give the
+        user a small warning and do nothing.
+        """
+        # Set tag_name to the first valid tag found on the "insert" cursor.
+        tag_names = self.text.tag_names(tk.INSERT)
+        for tag_name in ("stdout", "stderr"):
+            if tag_name in tag_names:
+                break
+        else:
+            # The insert cursor doesn't have a "stdout" or "stderr" tag.
+            self.text.bell()
+            return "break"
+
+        # Find the range to squeeze.
+        start, end = self.text.tag_prevrange(tag_name, tk.INSERT + "+1c")
+        s = self.text.get(start, end)
+
+        # If the last char is a newline, remove it from the range.
+        if len(s) > 0 and s[-1] == '\n':
+            end = self.text.index("%s-1c" % end)
+            s = s[:-1]
+
+        # Delete the text.
+        self.base_text.delete(start, end)
+
+        # Prepare an ExpandingButton.
+        numoflines = self.count_lines(s)
+        expandingbutton = ExpandingButton(s, tag_name, numoflines, self)
+
+        # insert the ExpandingButton to the Text
+        self.text.window_create(start, window=expandingbutton,
+                                padx=3, pady=5)
+
+        # Insert the ExpandingButton to the list of ExpandingButtons,
+        # while keeping the list ordered according to the position of
+        # the buttons in the Text widget.
+        i = len(self.expandingbuttons)
+        while i > 0 and self.text.compare(self.expandingbuttons[i-1],
+                                          ">", expandingbutton):
+            i -= 1
+        self.expandingbuttons.insert(i, expandingbutton)
+
+        return "break"
+
+
+Squeezer.reload()
+
+
+if __name__ == "__main__":
+    from unittest import main
+    main('idlelib.idle_test.test_squeezer', verbosity=2, exit=False)
+
+    # Add htest.
diff --git a/rootfs/usr/lib/python3.8/idlelib/stackviewer.py b/rootfs/usr/lib/python3.8/idlelib/stackviewer.py
new file mode 100644
index 0000000..94ffb4e
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/stackviewer.py
@@ -0,0 +1,155 @@
+import linecache
+import os
+import sys
+
+import tkinter as tk
+
+from idlelib.debugobj import ObjectTreeItem, make_objecttreeitem
+from idlelib.tree import TreeNode, TreeItem, ScrolledCanvas
+
+def StackBrowser(root, flist=None, tb=None, top=None):
+    global sc, item, node  # For testing.
+    if top is None:
+        top = tk.Toplevel(root)
+    sc = ScrolledCanvas(top, bg="white", highlightthickness=0)
+    sc.frame.pack(expand=1, fill="both")
+    item = StackTreeItem(flist, tb)
+    node = TreeNode(sc.canvas, None, item)
+    node.expand()
+
+
+class StackTreeItem(TreeItem):
+
+    def __init__(self, flist=None, tb=None):
+        self.flist = flist
+        self.stack = self.get_stack(tb)
+        self.text = self.get_exception()
+
+    def get_stack(self, tb):
+        if tb is None:
+            tb = sys.last_traceback
+        stack = []
+        if tb and tb.tb_frame is None:
+            tb = tb.tb_next
+        while tb is not None:
+            stack.append((tb.tb_frame, tb.tb_lineno))
+            tb = tb.tb_next
+        return stack
+
+    def get_exception(self):
+        type = sys.last_type
+        value = sys.last_value
+        if hasattr(type, "__name__"):
+            type = type.__name__
+        s = str(type)
+        if value is not None:
+            s = s + ": " + str(value)
+        return s
+
+    def GetText(self):
+        return self.text
+
+    def GetSubList(self):
+        sublist = []
+        for info in self.stack:
+            item = FrameTreeItem(info, self.flist)
+            sublist.append(item)
+        return sublist
+
+
+class FrameTreeItem(TreeItem):
+
+    def __init__(self, info, flist):
+        self.info = info
+        self.flist = flist
+
+    def GetText(self):
+        frame, lineno = self.info
+        try:
+            modname = frame.f_globals["__name__"]
+        except:
+            modname = "?"
+        code = frame.f_code
+        filename = code.co_filename
+        funcname = code.co_name
+        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)
+        return item
+
+    def GetSubList(self):
+        frame, lineno = self.info
+        sublist = []
+        if frame.f_globals is not frame.f_locals:
+            item = VariablesTreeItem("<locals>", frame.f_locals, self.flist)
+            sublist.append(item)
+        item = VariablesTreeItem("<globals>", frame.f_globals, self.flist)
+        sublist.append(item)
+        return sublist
+
+    def OnDoubleClick(self):
+        if self.flist:
+            frame, lineno = self.info
+            filename = frame.f_code.co_filename
+            if os.path.isfile(filename):
+                self.flist.gotofileline(filename, lineno)
+
+
+class VariablesTreeItem(ObjectTreeItem):
+
+    def GetText(self):
+        return self.labeltext
+
+    def GetLabelText(self):
+        return None
+
+    def IsExpandable(self):
+        return len(self.object) > 0
+
+    def GetSubList(self):
+        sublist = []
+        for key in self.object.keys():
+            try:
+                value = self.object[key]
+            except KeyError:
+                continue
+            def setfunction(value, key=key, object=self.object):
+                object[key] = value
+            item = make_objecttreeitem(key + " =", value, setfunction)
+            sublist.append(item)
+        return sublist
+
+
+def _stack_viewer(parent):  # htest #
+    from idlelib.pyshell import PyShellFileList
+    top = tk.Toplevel(parent)
+    top.title("Test StackViewer")
+    x, y = map(int, parent.geometry().split('+')[1:])
+    top.geometry("+%d+%d" % (x + 50, y + 175))
+    flist = PyShellFileList(top)
+    try: # to obtain a traceback object
+        intentional_name_error
+    except NameError:
+        exc_type, exc_value, exc_tb = sys.exc_info()
+    # inject stack trace to sys
+    sys.last_type = exc_type
+    sys.last_value = exc_value
+    sys.last_traceback = exc_tb
+
+    StackBrowser(top, flist=flist, top=top, tb=exc_tb)
+
+    # restore sys to original state
+    del sys.last_type
+    del sys.last_value
+    del sys.last_traceback
+
+if __name__ == '__main__':
+    from unittest import main
+    main('idlelib.idle_test.test_stackviewer', verbosity=2, exit=False)
+
+    from idlelib.idle_test.htest import run
+    run(_stack_viewer)
diff --git a/rootfs/usr/lib/python3.8/idlelib/statusbar.py b/rootfs/usr/lib/python3.8/idlelib/statusbar.py
new file mode 100644
index 0000000..755fafb
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/statusbar.py
@@ -0,0 +1,50 @@
+from tkinter.ttk import Label, Frame
+
+
+class MultiStatusBar(Frame):
+
+    def __init__(self, master, **kw):
+        Frame.__init__(self, master, **kw)
+        self.labels = {}
+
+    def set_label(self, name, text='', side='left', width=0):
+        if name not in self.labels:
+            label = Label(self, borderwidth=0, anchor='w')
+            label.pack(side=side, pady=0, padx=4)
+            self.labels[name] = label
+        else:
+            label = self.labels[name]
+        if width != 0:
+            label.config(width=width)
+        label.config(text=text)
+
+
+def _multistatus_bar(parent):  # htest #
+    from tkinter import Toplevel, Text
+    from tkinter.ttk import Frame, Button
+    top = Toplevel(parent)
+    x, y = map(int, parent.geometry().split('+')[1:])
+    top.geometry("+%d+%d" %(x, y + 175))
+    top.title("Test multistatus bar")
+    frame = Frame(top)
+    text = Text(frame, height=5, width=40)
+    text.pack()
+    msb = MultiStatusBar(frame)
+    msb.set_label("one", "hello")
+    msb.set_label("two", "world")
+    msb.pack(side='bottom', fill='x')
+
+    def change():
+        msb.set_label("one", "foo")
+        msb.set_label("two", "bar")
+
+    button = Button(top, text="Update status", command=change)
+    button.pack(side='bottom')
+    frame.pack()
+
+if __name__ == '__main__':
+    from unittest import main
+    main('idlelib.idle_test.test_statusbar', verbosity=2, exit=False)
+
+    from idlelib.idle_test.htest import run
+    run(_multistatus_bar)
diff --git a/rootfs/usr/lib/python3.8/idlelib/textview.py b/rootfs/usr/lib/python3.8/idlelib/textview.py
new file mode 100644
index 0000000..a66c1a4
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/textview.py
@@ -0,0 +1,193 @@
+"""Simple text browser for IDLE
+
+"""
+from tkinter import Toplevel, Text, TclError,\
+    HORIZONTAL, VERTICAL, NS, EW, NSEW, NONE, WORD, SUNKEN
+from tkinter.ttk import Frame, Scrollbar, Button
+from tkinter.messagebox import showerror
+
+from idlelib.colorizer import color_config
+
+
+class AutoHideScrollbar(Scrollbar):
+    """A scrollbar that is automatically hidden when not needed.
+
+    Only the grid geometry manager is supported.
+    """
+    def set(self, lo, hi):
+        if float(lo) > 0.0 or float(hi) < 1.0:
+            self.grid()
+        else:
+            self.grid_remove()
+        super().set(lo, hi)
+
+    def pack(self, **kwargs):
+        raise TclError(f'{self.__class__.__name__} does not support "pack"')
+
+    def place(self, **kwargs):
+        raise TclError(f'{self.__class__.__name__} does not support "place"')
+
+
+class ScrollableTextFrame(Frame):
+    """Display text with scrollbar(s)."""
+
+    def __init__(self, master, wrap=NONE, **kwargs):
+        """Create a frame for Textview.
+
+        master - master widget for this frame
+        wrap - type of text wrapping to use ('word', 'char' or 'none')
+
+        All parameters except for 'wrap' are passed to Frame.__init__().
+
+        The Text widget is accessible via the 'text' attribute.
+
+        Note: Changing the wrapping mode of the text widget after
+        instantiation is not supported.
+        """
+        super().__init__(master, **kwargs)
+
+        text = self.text = Text(self, wrap=wrap)
+        text.grid(row=0, column=0, sticky=NSEW)
+        self.grid_rowconfigure(0, weight=1)
+        self.grid_columnconfigure(0, weight=1)
+
+        # vertical scrollbar
+        self.yscroll = AutoHideScrollbar(self, orient=VERTICAL,
+                                         takefocus=False,
+                                         command=text.yview)
+        self.yscroll.grid(row=0, column=1, sticky=NS)
+        text['yscrollcommand'] = self.yscroll.set
+
+        # horizontal scrollbar - only when wrap is set to NONE
+        if wrap == NONE:
+            self.xscroll = AutoHideScrollbar(self, orient=HORIZONTAL,
+                                             takefocus=False,
+                                             command=text.xview)
+            self.xscroll.grid(row=1, column=0, sticky=EW)
+            text['xscrollcommand'] = self.xscroll.set
+        else:
+            self.xscroll = None
+
+
+class ViewFrame(Frame):
+    "Display TextFrame and Close button."
+    def __init__(self, parent, contents, wrap='word'):
+        """Create a frame for viewing text with a "Close" button.
+
+        parent - parent widget for this frame
+        contents - text to display
+        wrap - type of text wrapping to use ('word', 'char' or 'none')
+
+        The Text widget is accessible via the 'text' attribute.
+        """
+        super().__init__(parent)
+        self.parent = parent
+        self.bind('<Return>', self.ok)
+        self.bind('<Escape>', self.ok)
+        self.textframe = ScrollableTextFrame(self, relief=SUNKEN, height=700)
+
+        text = self.text = self.textframe.text
+        text.insert('1.0', contents)
+        text.configure(wrap=wrap, highlightthickness=0, state='disabled')
+        color_config(text)
+        text.focus_set()
+
+        self.button_ok = button_ok = Button(
+                self, text='Close', command=self.ok, takefocus=False)
+        self.textframe.pack(side='top', expand=True, fill='both')
+        button_ok.pack(side='bottom')
+
+    def ok(self, event=None):
+        """Dismiss text viewer dialog."""
+        self.parent.destroy()
+
+
+class ViewWindow(Toplevel):
+    "A simple text viewer dialog for IDLE."
+
+    def __init__(self, parent, title, contents, modal=True, wrap=WORD,
+                 *, _htest=False, _utest=False):
+        """Show the given text in a scrollable window with a 'close' button.
+
+        If modal is left True, users cannot interact with other windows
+        until the textview window is closed.
+
+        parent - parent of this dialog
+        title - string which is title of popup dialog
+        contents - text to display in dialog
+        wrap - type of text wrapping to use ('word', 'char' or 'none')
+        _htest - bool; change box location when running htest.
+        _utest - bool; don't wait_window when running unittest.
+        """
+        super().__init__(parent)
+        self['borderwidth'] = 5
+        # Place dialog below parent if running htest.
+        x = parent.winfo_rootx() + 10
+        y = parent.winfo_rooty() + (10 if not _htest else 100)
+        self.geometry(f'=750x500+{x}+{y}')
+
+        self.title(title)
+        self.viewframe = ViewFrame(self, contents, wrap=wrap)
+        self.protocol("WM_DELETE_WINDOW", self.ok)
+        self.button_ok = button_ok = Button(self, text='Close',
+                                            command=self.ok, takefocus=False)
+        self.viewframe.pack(side='top', expand=True, fill='both')
+
+        self.is_modal = modal
+        if self.is_modal:
+            self.transient(parent)
+            self.grab_set()
+            if not _utest:
+                self.wait_window()
+
+    def ok(self, event=None):
+        """Dismiss text viewer dialog."""
+        if self.is_modal:
+            self.grab_release()
+        self.destroy()
+
+
+def view_text(parent, title, contents, modal=True, wrap='word', _utest=False):
+    """Create text viewer for given text.
+
+    parent - parent of this dialog
+    title - string which is the title of popup dialog
+    contents - text to display in this dialog
+    wrap - type of text wrapping to use ('word', 'char' or 'none')
+    modal - controls if users can interact with other windows while this
+            dialog is displayed
+    _utest - bool; controls wait_window on unittest
+    """
+    return ViewWindow(parent, title, contents, modal, wrap=wrap, _utest=_utest)
+
+
+def view_file(parent, title, filename, encoding, modal=True, wrap='word',
+              _utest=False):
+    """Create text viewer for text in filename.
+
+    Return error message if file cannot be read.  Otherwise calls view_text
+    with contents of the file.
+    """
+    try:
+        with open(filename, 'r', encoding=encoding) as file:
+            contents = file.read()
+    except OSError:
+        showerror(title='File Load Error',
+                  message=f'Unable to load file {filename!r} .',
+                  parent=parent)
+    except UnicodeDecodeError as err:
+        showerror(title='Unicode Decode Error',
+                  message=str(err),
+                  parent=parent)
+    else:
+        return view_text(parent, title, contents, modal, wrap=wrap,
+                         _utest=_utest)
+    return None
+
+
+if __name__ == '__main__':
+    from unittest import main
+    main('idlelib.idle_test.test_textview', verbosity=2, exit=False)
+
+    from idlelib.idle_test.htest import run
+    run(ViewWindow)
diff --git a/rootfs/usr/lib/python3.8/idlelib/tooltip.py b/rootfs/usr/lib/python3.8/idlelib/tooltip.py
new file mode 100644
index 0000000..d714318
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/tooltip.py
@@ -0,0 +1,186 @@
+"""Tools for displaying tool-tips.
+
+This includes:
+ * an abstract base-class for different kinds of tooltips
+ * a simple text-only Tooltip class
+"""
+from tkinter import *
+
+
+class TooltipBase:
+    """abstract base class for tooltips"""
+
+    def __init__(self, anchor_widget):
+        """Create a tooltip.
+
+        anchor_widget: the widget next to which the tooltip will be shown
+
+        Note that a widget will only be shown when showtip() is called.
+        """
+        self.anchor_widget = anchor_widget
+        self.tipwindow = None
+
+    def __del__(self):
+        self.hidetip()
+
+    def showtip(self):
+        """display the tooltip"""
+        if self.tipwindow:
+            return
+        self.tipwindow = tw = Toplevel(self.anchor_widget)
+        # show no border on the top level window
+        tw.wm_overrideredirect(1)
+        try:
+            # This command is only needed and available on Tk >= 8.4.0 for OSX.
+            # Without it, call tips intrude on the typing process by grabbing
+            # the focus.
+            tw.tk.call("::tk::unsupported::MacWindowStyle", "style", tw._w,
+                       "help", "noActivates")
+        except TclError:
+            pass
+
+        self.position_window()
+        self.showcontents()
+        self.tipwindow.update_idletasks()  # Needed on MacOS -- see #34275.
+        self.tipwindow.lift()  # work around bug in Tk 8.5.18+ (issue #24570)
+
+    def position_window(self):
+        """(re)-set the tooltip's screen position"""
+        x, y = self.get_position()
+        root_x = self.anchor_widget.winfo_rootx() + x
+        root_y = self.anchor_widget.winfo_rooty() + y
+        self.tipwindow.wm_geometry("+%d+%d" % (root_x, root_y))
+
+    def get_position(self):
+        """choose a screen position for the tooltip"""
+        # The tip window must be completely outside the anchor widget;
+        # otherwise when the mouse enters the tip window we get
+        # a leave event and it disappears, and then we get an enter
+        # event and it reappears, and so on forever :-(
+        #
+        # Note: This is a simplistic implementation; sub-classes will likely
+        # want to override this.
+        return 20, self.anchor_widget.winfo_height() + 1
+
+    def showcontents(self):
+        """content display hook for sub-classes"""
+        # See ToolTip for an example
+        raise NotImplementedError
+
+    def hidetip(self):
+        """hide the tooltip"""
+        # Note: This is called by __del__, so careful when overriding/extending
+        tw = self.tipwindow
+        self.tipwindow = None
+        if tw:
+            try:
+                tw.destroy()
+            except TclError:  # pragma: no cover
+                pass
+
+
+class OnHoverTooltipBase(TooltipBase):
+    """abstract base class for tooltips, with delayed on-hover display"""
+
+    def __init__(self, anchor_widget, hover_delay=1000):
+        """Create a tooltip with a mouse hover delay.
+
+        anchor_widget: the widget next to which the tooltip will be shown
+        hover_delay: time to delay before showing the tooltip, in milliseconds
+
+        Note that a widget will only be shown when showtip() is called,
+        e.g. after hovering over the anchor widget with the mouse for enough
+        time.
+        """
+        super(OnHoverTooltipBase, self).__init__(anchor_widget)
+        self.hover_delay = hover_delay
+
+        self._after_id = None
+        self._id1 = self.anchor_widget.bind("<Enter>", self._show_event)
+        self._id2 = self.anchor_widget.bind("<Leave>", self._hide_event)
+        self._id3 = self.anchor_widget.bind("<Button>", self._hide_event)
+
+    def __del__(self):
+        try:
+            self.anchor_widget.unbind("<Enter>", self._id1)
+            self.anchor_widget.unbind("<Leave>", self._id2)  # pragma: no cover
+            self.anchor_widget.unbind("<Button>", self._id3) # pragma: no cover
+        except TclError:
+            pass
+        super(OnHoverTooltipBase, self).__del__()
+
+    def _show_event(self, event=None):
+        """event handler to display the tooltip"""
+        if self.hover_delay:
+            self.schedule()
+        else:
+            self.showtip()
+
+    def _hide_event(self, event=None):
+        """event handler to hide the tooltip"""
+        self.hidetip()
+
+    def schedule(self):
+        """schedule the future display of the tooltip"""
+        self.unschedule()
+        self._after_id = self.anchor_widget.after(self.hover_delay,
+                                                  self.showtip)
+
+    def unschedule(self):
+        """cancel the future display of the tooltip"""
+        after_id = self._after_id
+        self._after_id = None
+        if after_id:
+            self.anchor_widget.after_cancel(after_id)
+
+    def hidetip(self):
+        """hide the tooltip"""
+        try:
+            self.unschedule()
+        except TclError:  # pragma: no cover
+            pass
+        super(OnHoverTooltipBase, self).hidetip()
+
+
+class Hovertip(OnHoverTooltipBase):
+    "A tooltip that pops up when a mouse hovers over an anchor widget."
+    def __init__(self, anchor_widget, text, hover_delay=1000):
+        """Create a text tooltip with a mouse hover delay.
+
+        anchor_widget: the widget next to which the tooltip will be shown
+        hover_delay: time to delay before showing the tooltip, in milliseconds
+
+        Note that a widget will only be shown when showtip() is called,
+        e.g. after hovering over the anchor widget with the mouse for enough
+        time.
+        """
+        super(Hovertip, self).__init__(anchor_widget, hover_delay=hover_delay)
+        self.text = text
+
+    def showcontents(self):
+        label = Label(self.tipwindow, text=self.text, justify=LEFT,
+                      background="#ffffe0", relief=SOLID, borderwidth=1)
+        label.pack()
+
+
+def _tooltip(parent):  # htest #
+    top = Toplevel(parent)
+    top.title("Test tooltip")
+    x, y = map(int, parent.geometry().split('+')[1:])
+    top.geometry("+%d+%d" % (x, y + 150))
+    label = Label(top, text="Place your mouse over buttons")
+    label.pack()
+    button1 = Button(top, text="Button 1 -- 1/2 second hover delay")
+    button1.pack()
+    Hovertip(button1, "This is tooltip text for button1.", hover_delay=500)
+    button2 = Button(top, text="Button 2 -- no hover delay")
+    button2.pack()
+    Hovertip(button2, "This is tooltip\ntext for button2.", hover_delay=None)
+
+
+if __name__ == '__main__':
+    from unittest import main
+    main('idlelib.idle_test.test_tooltip', verbosity=2, exit=False)
+
+    from idlelib.idle_test.htest import run
+    run(_tooltip)
diff --git a/rootfs/usr/lib/python3.8/idlelib/tree.py b/rootfs/usr/lib/python3.8/idlelib/tree.py
new file mode 100644
index 0000000..5947268
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/tree.py
@@ -0,0 +1,500 @@
+# XXX TO DO:
+# - popup menu
+# - support partial or total redisplay
+# - key bindings (instead of quick-n-dirty bindings on Canvas):
+#   - up/down arrow keys to move focus around
+#   - ditto for page up/down, home/end
+#   - left/right arrows to expand/collapse & move out/in
+# - more doc strings
+# - add icons for "file", "module", "class", "method"; better "python" icon
+# - callback for selection???
+# - multiple-item selection
+# - tooltips
+# - redo geometry without magic numbers
+# - keep track of object ids to allow more careful cleaning
+# - optimize tree redraw after expand of subnode
+
+import os
+
+from tkinter import *
+from tkinter.ttk import Frame, Scrollbar
+
+from idlelib.config import idleConf
+from idlelib import zoomheight
+
+ICONDIR = "Icons"
+
+# Look for Icons subdirectory in the same directory as this module
+try:
+    _icondir = os.path.join(os.path.dirname(__file__), ICONDIR)
+except NameError:
+    _icondir = ICONDIR
+if os.path.isdir(_icondir):
+    ICONDIR = _icondir
+elif not os.path.isdir(ICONDIR):
+    raise RuntimeError("can't find icon directory (%r)" % (ICONDIR,))
+
+def listicons(icondir=ICONDIR):
+    """Utility to display the available icons."""
+    root = Tk()
+    import glob
+    list = glob.glob(os.path.join(glob.escape(icondir), "*.gif"))
+    list.sort()
+    images = []
+    row = column = 0
+    for file in list:
+        name = os.path.splitext(os.path.basename(file))[0]
+        image = PhotoImage(file=file, master=root)
+        images.append(image)
+        label = Label(root, image=image, bd=1, relief="raised")
+        label.grid(row=row, column=column)
+        label = Label(root, text=name)
+        label.grid(row=row+1, column=column)
+        column = column + 1
+        if column >= 10:
+            row = row+2
+            column = 0
+    root.images = images
+
+def wheel_event(event, widget=None):
+    """Handle scrollwheel event.
+
+    For wheel up, event.delta = 120*n on Windows, -1*n on darwin,
+    where n can be > 1 if one scrolls fast.  Flicking the wheel
+    generates up to maybe 20 events with n up to 10 or more 1.
+    Macs use wheel down (delta = 1*n) to scroll up, so positive
+    delta means to scroll up on both systems.
+
+    X-11 sends Control-Button-4,5 events instead.
+
+    The widget parameter is needed so browser label bindings can pass
+    the underlying canvas.
+
+    This function depends on widget.yview to not be overridden by
+    a subclass.
+    """
+    up = {EventType.MouseWheel: event.delta > 0,
+          EventType.ButtonPress: event.num == 4}
+    lines = -5 if up[event.type] else 5
+    widget = event.widget if widget is None else widget
+    widget.yview(SCROLL, lines, 'units')
+    return 'break'
+
+
+class TreeNode:
+
+    def __init__(self, canvas, parent, item):
+        self.canvas = canvas
+        self.parent = parent
+        self.item = item
+        self.state = 'collapsed'
+        self.selected = False
+        self.children = []
+        self.x = self.y = None
+        self.iconimages = {} # cache of PhotoImage instances for icons
+
+    def destroy(self):
+        for c in self.children[:]:
+            self.children.remove(c)
+            c.destroy()
+        self.parent = None
+
+    def geticonimage(self, name):
+        try:
+            return self.iconimages[name]
+        except KeyError:
+            pass
+        file, ext = os.path.splitext(name)
+        ext = ext or ".gif"
+        fullname = os.path.join(ICONDIR, file + ext)
+        image = PhotoImage(master=self.canvas, file=fullname)
+        self.iconimages[name] = image
+        return image
+
+    def select(self, event=None):
+        if self.selected:
+            return
+        self.deselectall()
+        self.selected = True
+        self.canvas.delete(self.image_id)
+        self.drawicon()
+        self.drawtext()
+
+    def deselect(self, event=None):
+        if not self.selected:
+            return
+        self.selected = False
+        self.canvas.delete(self.image_id)
+        self.drawicon()
+        self.drawtext()
+
+    def deselectall(self):
+        if self.parent:
+            self.parent.deselectall()
+        else:
+            self.deselecttree()
+
+    def deselecttree(self):
+        if self.selected:
+            self.deselect()
+        for child in self.children:
+            child.deselecttree()
+
+    def flip(self, event=None):
+        if self.state == 'expanded':
+            self.collapse()
+        else:
+            self.expand()
+        self.item.OnDoubleClick()
+        return "break"
+
+    def expand(self, event=None):
+        if not self.item._IsExpandable():
+            return
+        if self.state != 'expanded':
+            self.state = 'expanded'
+            self.update()
+            self.view()
+
+    def collapse(self, event=None):
+        if self.state != 'collapsed':
+            self.state = 'collapsed'
+            self.update()
+
+    def view(self):
+        top = self.y - 2
+        bottom = self.lastvisiblechild().y + 17
+        height = bottom - top
+        visible_top = self.canvas.canvasy(0)
+        visible_height = self.canvas.winfo_height()
+        visible_bottom = self.canvas.canvasy(visible_height)
+        if visible_top <= top and bottom <= visible_bottom:
+            return
+        x0, y0, x1, y1 = self.canvas._getints(self.canvas['scrollregion'])
+        if top >= visible_top and height <= visible_height:
+            fraction = top + height - visible_height
+        else:
+            fraction = top
+        fraction = float(fraction) / y1
+        self.canvas.yview_moveto(fraction)
+
+    def lastvisiblechild(self):
+        if self.children and self.state == 'expanded':
+            return self.children[-1].lastvisiblechild()
+        else:
+            return self
+
+    def update(self):
+        if self.parent:
+            self.parent.update()
+        else:
+            oldcursor = self.canvas['cursor']
+            self.canvas['cursor'] = "watch"
+            self.canvas.update()
+            self.canvas.delete(ALL)     # XXX could be more subtle
+            self.draw(7, 2)
+            x0, y0, x1, y1 = self.canvas.bbox(ALL)
+            self.canvas.configure(scrollregion=(0, 0, x1, y1))
+            self.canvas['cursor'] = oldcursor
+
+    def draw(self, x, y):
+        # XXX This hard-codes too many geometry constants!
+        dy = 20
+        self.x, self.y = x, y
+        self.drawicon()
+        self.drawtext()
+        if self.state != 'expanded':
+            return y + dy
+        # draw children
+        if not self.children:
+            sublist = self.item._GetSubList()
+            if not sublist:
+                # _IsExpandable() was mistaken; that's allowed
+                return y+17
+            for item in sublist:
+                child = self.__class__(self.canvas, self, item)
+                self.children.append(child)
+        cx = x+20
+        cy = y + dy
+        cylast = 0
+        for child in self.children:
+            cylast = cy
+            self.canvas.create_line(x+9, cy+7, cx, cy+7, fill="gray50")
+            cy = child.draw(cx, cy)
+            if child.item._IsExpandable():
+                if child.state == 'expanded':
+                    iconname = "minusnode"
+                    callback = child.collapse
+                else:
+                    iconname = "plusnode"
+                    callback = child.expand
+                image = self.geticonimage(iconname)
+                id = self.canvas.create_image(x+9, cylast+7, image=image)
+                # XXX This leaks bindings until canvas is deleted:
+                self.canvas.tag_bind(id, "<1>", callback)
+                self.canvas.tag_bind(id, "<Double-1>", lambda x: None)
+        id = self.canvas.create_line(x+9, y+10, x+9, cylast+7,
+            ##stipple="gray50",     # XXX Seems broken in Tk 8.0.x
+            fill="gray50")
+        self.canvas.tag_lower(id) # XXX .lower(id) before Python 1.5.2
+        return cy
+
+    def drawicon(self):
+        if self.selected:
+            imagename = (self.item.GetSelectedIconName() or
+                         self.item.GetIconName() or
+                         "openfolder")
+        else:
+            imagename = self.item.GetIconName() or "folder"
+        image = self.geticonimage(imagename)
+        id = self.canvas.create_image(self.x, self.y, anchor="nw", image=image)
+        self.image_id = id
+        self.canvas.tag_bind(id, "<1>", self.select)
+        self.canvas.tag_bind(id, "<Double-1>", self.flip)
+
+    def drawtext(self):
+        textx = self.x+20-1
+        texty = self.y-4
+        labeltext = self.item.GetLabelText()
+        if labeltext:
+            id = self.canvas.create_text(textx, texty, anchor="nw",
+                                         text=labeltext)
+            self.canvas.tag_bind(id, "<1>", self.select)
+            self.canvas.tag_bind(id, "<Double-1>", self.flip)
+            x0, y0, x1, y1 = self.canvas.bbox(id)
+            textx = max(x1, 200) + 10
+        text = self.item.GetText() or "<no text>"
+        try:
+            self.entry
+        except AttributeError:
+            pass
+        else:
+            self.edit_finish()
+        try:
+            self.label
+        except AttributeError:
+            # padding carefully selected (on Windows) to match Entry widget:
+            self.label = Label(self.canvas, text=text, bd=0, padx=2, pady=2)
+        theme = idleConf.CurrentTheme()
+        if self.selected:
+            self.label.configure(idleConf.GetHighlight(theme, 'hilite'))
+        else:
+            self.label.configure(idleConf.GetHighlight(theme, 'normal'))
+        id = self.canvas.create_window(textx, texty,
+                                       anchor="nw", window=self.label)
+        self.label.bind("<1>", self.select_or_edit)
+        self.label.bind("<Double-1>", self.flip)
+        self.label.bind("<MouseWheel>", lambda e: wheel_event(e, self.canvas))
+        self.label.bind("<Button-4>", lambda e: wheel_event(e, self.canvas))
+        self.label.bind("<Button-5>", lambda e: wheel_event(e, self.canvas))
+        self.text_id = id
+
+    def select_or_edit(self, event=None):
+        if self.selected and self.item.IsEditable():
+            self.edit(event)
+        else:
+            self.select(event)
+
+    def edit(self, event=None):
+        self.entry = Entry(self.label, bd=0, highlightthickness=1, width=0)
+        self.entry.insert(0, self.label['text'])
+        self.entry.selection_range(0, END)
+        self.entry.pack(ipadx=5)
+        self.entry.focus_set()
+        self.entry.bind("<Return>", self.edit_finish)
+        self.entry.bind("<Escape>", self.edit_cancel)
+
+    def edit_finish(self, event=None):
+        try:
+            entry = self.entry
+            del self.entry
+        except AttributeError:
+            return
+        text = entry.get()
+        entry.destroy()
+        if text and text != self.item.GetText():
+            self.item.SetText(text)
+        text = self.item.GetText()
+        self.label['text'] = text
+        self.drawtext()
+        self.canvas.focus_set()
+
+    def edit_cancel(self, event=None):
+        try:
+            entry = self.entry
+            del self.entry
+        except AttributeError:
+            return
+        entry.destroy()
+        self.drawtext()
+        self.canvas.focus_set()
+
+
+class TreeItem:
+
+    """Abstract class representing tree items.
+
+    Methods should typically be overridden, otherwise a default action
+    is used.
+
+    """
+
+    def __init__(self):
+        """Constructor.  Do whatever you need to do."""
+
+    def GetText(self):
+        """Return text string to display."""
+
+    def GetLabelText(self):
+        """Return label text string to display in front of text (if any)."""
+
+    expandable = None
+
+    def _IsExpandable(self):
+        """Do not override!  Called by TreeNode."""
+        if self.expandable is None:
+            self.expandable = self.IsExpandable()
+        return self.expandable
+
+    def IsExpandable(self):
+        """Return whether there are subitems."""
+        return 1
+
+    def _GetSubList(self):
+        """Do not override!  Called by TreeNode."""
+        if not self.IsExpandable():
+            return []
+        sublist = self.GetSubList()
+        if not sublist:
+            self.expandable = 0
+        return sublist
+
+    def IsEditable(self):
+        """Return whether the item's text may be edited."""
+
+    def SetText(self, text):
+        """Change the item's text (if it is editable)."""
+
+    def GetIconName(self):
+        """Return name of icon to be displayed normally."""
+
+    def GetSelectedIconName(self):
+        """Return name of icon to be displayed when selected."""
+
+    def GetSubList(self):
+        """Return list of items forming sublist."""
+
+    def OnDoubleClick(self):
+        """Called on a double-click on the item."""
+
+
+# Example application
+
+class FileTreeItem(TreeItem):
+
+    """Example TreeItem subclass -- browse the file system."""
+
+    def __init__(self, path):
+        self.path = path
+
+    def GetText(self):
+        return os.path.basename(self.path) or self.path
+
+    def IsEditable(self):
+        return os.path.basename(self.path) != ""
+
+    def SetText(self, text):
+        newpath = os.path.dirname(self.path)
+        newpath = os.path.join(newpath, text)
+        if os.path.dirname(newpath) != os.path.dirname(self.path):
+            return
+        try:
+            os.rename(self.path, newpath)
+            self.path = newpath
+        except OSError:
+            pass
+
+    def GetIconName(self):
+        if not self.IsExpandable():
+            return "python" # XXX wish there was a "file" icon
+
+    def IsExpandable(self):
+        return os.path.isdir(self.path)
+
+    def GetSubList(self):
+        try:
+            names = os.listdir(self.path)
+        except OSError:
+            return []
+        names.sort(key = os.path.normcase)
+        sublist = []
+        for name in names:
+            item = FileTreeItem(os.path.join(self.path, name))
+            sublist.append(item)
+        return sublist
+
+
+# A canvas widget with scroll bars and some useful bindings
+
+class ScrolledCanvas:
+
+    def __init__(self, master, **opts):
+        if 'yscrollincrement' not in opts:
+            opts['yscrollincrement'] = 17
+        self.master = master
+        self.frame = Frame(master)
+        self.frame.rowconfigure(0, weight=1)
+        self.frame.columnconfigure(0, weight=1)
+        self.canvas = Canvas(self.frame, **opts)
+        self.canvas.grid(row=0, column=0, sticky="nsew")
+        self.vbar = Scrollbar(self.frame, name="vbar")
+        self.vbar.grid(row=0, column=1, sticky="nse")
+        self.hbar = Scrollbar(self.frame, name="hbar", orient="horizontal")
+        self.hbar.grid(row=1, column=0, sticky="ews")
+        self.canvas['yscrollcommand'] = self.vbar.set
+        self.vbar['command'] = self.canvas.yview
+        self.canvas['xscrollcommand'] = self.hbar.set
+        self.hbar['command'] = self.canvas.xview
+        self.canvas.bind("<Key-Prior>", self.page_up)
+        self.canvas.bind("<Key-Next>", self.page_down)
+        self.canvas.bind("<Key-Up>", self.unit_up)
+        self.canvas.bind("<Key-Down>", self.unit_down)
+        self.canvas.bind("<MouseWheel>", wheel_event)
+        self.canvas.bind("<Button-4>", wheel_event)
+        self.canvas.bind("<Button-5>", wheel_event)
+        #if isinstance(master, Toplevel) or isinstance(master, Tk):
+        self.canvas.bind("<Alt-Key-2>", self.zoom_height)
+        self.canvas.focus_set()
+    def page_up(self, event):
+        self.canvas.yview_scroll(-1, "page")
+        return "break"
+    def page_down(self, event):
+        self.canvas.yview_scroll(1, "page")
+        return "break"
+    def unit_up(self, event):
+        self.canvas.yview_scroll(-1, "unit")
+        return "break"
+    def unit_down(self, event):
+        self.canvas.yview_scroll(1, "unit")
+        return "break"
+    def zoom_height(self, event):
+        zoomheight.zoom_height(self.master)
+        return "break"
+
+
+def _tree_widget(parent):  # htest #
+    top = Toplevel(parent)
+    x, y = map(int, parent.geometry().split('+')[1:])
+    top.geometry("+%d+%d" % (x+50, y+175))
+    sc = ScrolledCanvas(top, bg="white", highlightthickness=0, takefocus=1)
+    sc.frame.pack(expand=1, fill="both", side=LEFT)
+    item = FileTreeItem(ICONDIR)
+    node = TreeNode(sc.canvas, None, item)
+    node.expand()
+
+if __name__ == '__main__':
+    from unittest import main
+    main('idlelib.idle_test.test_tree', verbosity=2, exit=False)
+
+    from idlelib.idle_test.htest import run
+    run(_tree_widget)
diff --git a/rootfs/usr/lib/python3.8/idlelib/undo.py b/rootfs/usr/lib/python3.8/idlelib/undo.py
new file mode 100644
index 0000000..85ecffe
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/undo.py
@@ -0,0 +1,366 @@
+import string
+
+from idlelib.delegator import Delegator
+
+# tkinter import not needed because module does not create widgets,
+# although many methods operate on text widget arguments.
+
+#$ event <<redo>>
+#$ win <Control-y>
+#$ unix <Alt-z>
+
+#$ event <<undo>>
+#$ win <Control-z>
+#$ unix <Control-z>
+
+#$ event <<dump-undo-state>>
+#$ win <Control-backslash>
+#$ unix <Control-backslash>
+
+
+class UndoDelegator(Delegator):
+
+    max_undo = 1000
+
+    def __init__(self):
+        Delegator.__init__(self)
+        self.reset_undo()
+
+    def setdelegate(self, delegate):
+        if self.delegate is not None:
+            self.unbind("<<undo>>")
+            self.unbind("<<redo>>")
+            self.unbind("<<dump-undo-state>>")
+        Delegator.setdelegate(self, delegate)
+        if delegate is not None:
+            self.bind("<<undo>>", self.undo_event)
+            self.bind("<<redo>>", self.redo_event)
+            self.bind("<<dump-undo-state>>", self.dump_event)
+
+    def dump_event(self, event):
+        from pprint import pprint
+        pprint(self.undolist[:self.pointer])
+        print("pointer:", self.pointer, end=' ')
+        print("saved:", self.saved, end=' ')
+        print("can_merge:", self.can_merge, end=' ')
+        print("get_saved():", self.get_saved())
+        pprint(self.undolist[self.pointer:])
+        return "break"
+
+    def reset_undo(self):
+        self.was_saved = -1
+        self.pointer = 0
+        self.undolist = []
+        self.undoblock = 0  # or a CommandSequence instance
+        self.set_saved(1)
+
+    def set_saved(self, flag):
+        if flag:
+            self.saved = self.pointer
+        else:
+            self.saved = -1
+        self.can_merge = False
+        self.check_saved()
+
+    def get_saved(self):
+        return self.saved == self.pointer
+
+    saved_change_hook = None
+
+    def set_saved_change_hook(self, hook):
+        self.saved_change_hook = hook
+
+    was_saved = -1
+
+    def check_saved(self):
+        is_saved = self.get_saved()
+        if is_saved != self.was_saved:
+            self.was_saved = is_saved
+            if self.saved_change_hook:
+                self.saved_change_hook()
+
+    def insert(self, index, chars, tags=None):
+        self.addcmd(InsertCommand(index, chars, tags))
+
+    def delete(self, index1, index2=None):
+        self.addcmd(DeleteCommand(index1, index2))
+
+    # Clients should call undo_block_start() and undo_block_stop()
+    # around a sequence of editing cmds to be treated as a unit by
+    # undo & redo.  Nested matching calls are OK, and the inner calls
+    # then act like nops.  OK too if no editing cmds, or only one
+    # editing cmd, is issued in between:  if no cmds, the whole
+    # sequence has no effect; and if only one cmd, that cmd is entered
+    # directly into the undo list, as if undo_block_xxx hadn't been
+    # called.  The intent of all that is to make this scheme easy
+    # to use:  all the client has to worry about is making sure each
+    # _start() call is matched by a _stop() call.
+
+    def undo_block_start(self):
+        if self.undoblock == 0:
+            self.undoblock = CommandSequence()
+        self.undoblock.bump_depth()
+
+    def undo_block_stop(self):
+        if self.undoblock.bump_depth(-1) == 0:
+            cmd = self.undoblock
+            self.undoblock = 0
+            if len(cmd) > 0:
+                if len(cmd) == 1:
+                    # no need to wrap a single cmd
+                    cmd = cmd.getcmd(0)
+                # this blk of cmds, or single cmd, has already
+                # been done, so don't execute it again
+                self.addcmd(cmd, 0)
+
+    def addcmd(self, cmd, execute=True):
+        if execute:
+            cmd.do(self.delegate)
+        if self.undoblock != 0:
+            self.undoblock.append(cmd)
+            return
+        if self.can_merge and self.pointer > 0:
+            lastcmd = self.undolist[self.pointer-1]
+            if lastcmd.merge(cmd):
+                return
+        self.undolist[self.pointer:] = [cmd]
+        if self.saved > self.pointer:
+            self.saved = -1
+        self.pointer = self.pointer + 1
+        if len(self.undolist) > self.max_undo:
+            ##print "truncating undo list"
+            del self.undolist[0]
+            self.pointer = self.pointer - 1
+            if self.saved >= 0:
+                self.saved = self.saved - 1
+        self.can_merge = True
+        self.check_saved()
+
+    def undo_event(self, event):
+        if self.pointer == 0:
+            self.bell()
+            return "break"
+        cmd = self.undolist[self.pointer - 1]
+        cmd.undo(self.delegate)
+        self.pointer = self.pointer - 1
+        self.can_merge = False
+        self.check_saved()
+        return "break"
+
+    def redo_event(self, event):
+        if self.pointer >= len(self.undolist):
+            self.bell()
+            return "break"
+        cmd = self.undolist[self.pointer]
+        cmd.redo(self.delegate)
+        self.pointer = self.pointer + 1
+        self.can_merge = False
+        self.check_saved()
+        return "break"
+
+
+class Command:
+    # Base class for Undoable commands
+
+    tags = None
+
+    def __init__(self, index1, index2, chars, tags=None):
+        self.marks_before = {}
+        self.marks_after = {}
+        self.index1 = index1
+        self.index2 = index2
+        self.chars = chars
+        if tags:
+            self.tags = tags
+
+    def __repr__(self):
+        s = self.__class__.__name__
+        t = (self.index1, self.index2, self.chars, self.tags)
+        if self.tags is None:
+            t = t[:-1]
+        return s + repr(t)
+
+    def do(self, text):
+        pass
+
+    def redo(self, text):
+        pass
+
+    def undo(self, text):
+        pass
+
+    def merge(self, cmd):
+        return 0
+
+    def save_marks(self, text):
+        marks = {}
+        for name in text.mark_names():
+            if name != "insert" and name != "current":
+                marks[name] = text.index(name)
+        return marks
+
+    def set_marks(self, text, marks):
+        for name, index in marks.items():
+            text.mark_set(name, index)
+
+
+class InsertCommand(Command):
+    # Undoable insert command
+
+    def __init__(self, index1, chars, tags=None):
+        Command.__init__(self, index1, None, chars, tags)
+
+    def do(self, text):
+        self.marks_before = self.save_marks(text)
+        self.index1 = text.index(self.index1)
+        if text.compare(self.index1, ">", "end-1c"):
+            # Insert before the final newline
+            self.index1 = text.index("end-1c")
+        text.insert(self.index1, self.chars, self.tags)
+        self.index2 = text.index("%s+%dc" % (self.index1, len(self.chars)))
+        self.marks_after = self.save_marks(text)
+        ##sys.__stderr__.write("do: %s\n" % self)
+
+    def redo(self, text):
+        text.mark_set('insert', self.index1)
+        text.insert(self.index1, self.chars, self.tags)
+        self.set_marks(text, self.marks_after)
+        text.see('insert')
+        ##sys.__stderr__.write("redo: %s\n" % self)
+
+    def undo(self, text):
+        text.mark_set('insert', self.index1)
+        text.delete(self.index1, self.index2)
+        self.set_marks(text, self.marks_before)
+        text.see('insert')
+        ##sys.__stderr__.write("undo: %s\n" % self)
+
+    def merge(self, cmd):
+        if self.__class__ is not cmd.__class__:
+            return False
+        if self.index2 != cmd.index1:
+            return False
+        if self.tags != cmd.tags:
+            return False
+        if len(cmd.chars) != 1:
+            return False
+        if self.chars and \
+           self.classify(self.chars[-1]) != self.classify(cmd.chars):
+            return False
+        self.index2 = cmd.index2
+        self.chars = self.chars + cmd.chars
+        return True
+
+    alphanumeric = string.ascii_letters + string.digits + "_"
+
+    def classify(self, c):
+        if c in self.alphanumeric:
+            return "alphanumeric"
+        if c == "\n":
+            return "newline"
+        return "punctuation"
+
+
+class DeleteCommand(Command):
+    # Undoable delete command
+
+    def __init__(self, index1, index2=None):
+        Command.__init__(self, index1, index2, None, None)
+
+    def do(self, text):
+        self.marks_before = self.save_marks(text)
+        self.index1 = text.index(self.index1)
+        if self.index2:
+            self.index2 = text.index(self.index2)
+        else:
+            self.index2 = text.index(self.index1 + " +1c")
+        if text.compare(self.index2, ">", "end-1c"):
+            # Don't delete the final newline
+            self.index2 = text.index("end-1c")
+        self.chars = text.get(self.index1, self.index2)
+        text.delete(self.index1, self.index2)
+        self.marks_after = self.save_marks(text)
+        ##sys.__stderr__.write("do: %s\n" % self)
+
+    def redo(self, text):
+        text.mark_set('insert', self.index1)
+        text.delete(self.index1, self.index2)
+        self.set_marks(text, self.marks_after)
+        text.see('insert')
+        ##sys.__stderr__.write("redo: %s\n" % self)
+
+    def undo(self, text):
+        text.mark_set('insert', self.index1)
+        text.insert(self.index1, self.chars)
+        self.set_marks(text, self.marks_before)
+        text.see('insert')
+        ##sys.__stderr__.write("undo: %s\n" % self)
+
+
+class CommandSequence(Command):
+    # Wrapper for a sequence of undoable cmds to be undone/redone
+    # as a unit
+
+    def __init__(self):
+        self.cmds = []
+        self.depth = 0
+
+    def __repr__(self):
+        s = self.__class__.__name__
+        strs = []
+        for cmd in self.cmds:
+            strs.append("    %r" % (cmd,))
+        return s + "(\n" + ",\n".join(strs) + "\n)"
+
+    def __len__(self):
+        return len(self.cmds)
+
+    def append(self, cmd):
+        self.cmds.append(cmd)
+
+    def getcmd(self, i):
+        return self.cmds[i]
+
+    def redo(self, text):
+        for cmd in self.cmds:
+            cmd.redo(text)
+
+    def undo(self, text):
+        cmds = self.cmds[:]
+        cmds.reverse()
+        for cmd in cmds:
+            cmd.undo(text)
+
+    def bump_depth(self, incr=1):
+        self.depth = self.depth + incr
+        return self.depth
+
+
+def _undo_delegator(parent):  # htest #
+    from tkinter import Toplevel, Text, Button
+    from idlelib.percolator import Percolator
+    undowin = Toplevel(parent)
+    undowin.title("Test UndoDelegator")
+    x, y = map(int, parent.geometry().split('+')[1:])
+    undowin.geometry("+%d+%d" % (x, y + 175))
+
+    text = Text(undowin, height=10)
+    text.pack()
+    text.focus_set()
+    p = Percolator(text)
+    d = UndoDelegator()
+    p.insertfilter(d)
+
+    undo = Button(undowin, text="Undo", command=lambda:d.undo_event(None))
+    undo.pack(side='left')
+    redo = Button(undowin, text="Redo", command=lambda:d.redo_event(None))
+    redo.pack(side='left')
+    dump = Button(undowin, text="Dump", command=lambda:d.dump_event(None))
+    dump.pack(side='left')
+
+if __name__ == "__main__":
+    from unittest import main
+    main('idlelib.idle_test.test_undo', verbosity=2, exit=False)
+
+    from idlelib.idle_test.htest import run
+    run(_undo_delegator)
diff --git a/rootfs/usr/lib/python3.8/idlelib/window.py b/rootfs/usr/lib/python3.8/idlelib/window.py
new file mode 100644
index 0000000..460d5b6
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/window.py
@@ -0,0 +1,98 @@
+from tkinter import Toplevel, TclError
+import sys
+
+
+class WindowList:
+
+    def __init__(self):
+        self.dict = {}
+        self.callbacks = []
+
+    def add(self, window):
+        window.after_idle(self.call_callbacks)
+        self.dict[str(window)] = window
+
+    def delete(self, window):
+        try:
+            del self.dict[str(window)]
+        except KeyError:
+            # Sometimes, destroy() is called twice
+            pass
+        self.call_callbacks()
+
+    def add_windows_to_menu(self,  menu):
+        list = []
+        for key in self.dict:
+            window = self.dict[key]
+            try:
+                title = window.get_title()
+            except TclError:
+                continue
+            list.append((title, key, window))
+        list.sort()
+        for title, key, window in list:
+            menu.add_command(label=title, command=window.wakeup)
+
+    def register_callback(self, callback):
+        self.callbacks.append(callback)
+
+    def unregister_callback(self, callback):
+        try:
+            self.callbacks.remove(callback)
+        except ValueError:
+            pass
+
+    def call_callbacks(self):
+        for callback in self.callbacks:
+            try:
+                callback()
+            except:
+                t, v, tb = sys.exc_info()
+                print("warning: callback failed in WindowList", t, ":", v)
+
+
+registry = WindowList()
+
+add_windows_to_menu = registry.add_windows_to_menu
+register_callback = registry.register_callback
+unregister_callback = registry.unregister_callback
+
+
+class ListedToplevel(Toplevel):
+
+    def __init__(self, master, **kw):
+        Toplevel.__init__(self, master, kw)
+        registry.add(self)
+        self.focused_widget = self
+
+    def destroy(self):
+        registry.delete(self)
+        Toplevel.destroy(self)
+        # If this is Idle's last window then quit the mainloop
+        # (Needed for clean exit on Windows 98)
+        if not registry.dict:
+            self.quit()
+
+    def update_windowlist_registry(self, window):
+        registry.call_callbacks()
+
+    def get_title(self):
+        # Subclass can override
+        return self.wm_title()
+
+    def wakeup(self):
+        try:
+            if self.wm_state() == "iconic":
+                self.wm_withdraw()
+                self.wm_deiconify()
+            self.tkraise()
+            self.focused_widget.focus_set()
+        except TclError:
+            # This can happen when the Window menu was torn off.
+            # Simply ignore it.
+            pass
+
+
+if __name__ == "__main__":
+    from unittest import main
+    main('idlelib.idle_test.test_window', verbosity=2)
diff --git a/rootfs/usr/lib/python3.8/idlelib/zoomheight.py b/rootfs/usr/lib/python3.8/idlelib/zoomheight.py
new file mode 100644
index 0000000..cd50c91
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/zoomheight.py
@@ -0,0 +1,124 @@
+"Zoom a window to maximum height."
+
+import re
+import sys
+import tkinter
+
+
+class WmInfoGatheringError(Exception):
+    pass
+
+
+class ZoomHeight:
+    # Cached values for maximized window dimensions, one for each set
+    # of screen dimensions.
+    _max_height_and_y_coords = {}
+
+    def __init__(self, editwin):
+        self.editwin = editwin
+        self.top = self.editwin.top
+
+    def zoom_height_event(self, event=None):
+        zoomed = self.zoom_height()
+
+        if zoomed is None:
+            self.top.bell()
+        else:
+            menu_status = 'Restore' if zoomed else 'Zoom'
+            self.editwin.update_menu_label(menu='options', index='* Height',
+                                           label=f'{menu_status} Height')
+
+        return "break"
+
+    def zoom_height(self):
+        top = self.top
+
+        width, height, x, y = get_window_geometry(top)
+
+        if top.wm_state() != 'normal':
+            # Can't zoom/restore window height for windows not in the 'normal'
+            # state, e.g. maximized and full-screen windows.
+            return None
+
+        try:
+            maxheight, maxy = self.get_max_height_and_y_coord()
+        except WmInfoGatheringError:
+            return None
+
+        if height != maxheight:
+            # Maximize the window's height.
+            set_window_geometry(top, (width, maxheight, x, maxy))
+            return True
+        else:
+            # Restore the window's height.
+            #
+            # .wm_geometry('') makes the window revert to the size requested
+            # by the widgets it contains.
+            top.wm_geometry('')
+            return False
+
+    def get_max_height_and_y_coord(self):
+        top = self.top
+
+        screen_dimensions = (top.winfo_screenwidth(),
+                             top.winfo_screenheight())
+        if screen_dimensions not in self._max_height_and_y_coords:
+            orig_state = top.wm_state()
+
+            # Get window geometry info for maximized windows.
+            try:
+                top.wm_state('zoomed')
+            except tkinter.TclError:
+                # The 'zoomed' state is not supported by some esoteric WMs,
+                # such as Xvfb.
+                raise WmInfoGatheringError(
+                    'Failed getting geometry of maximized windows, because ' +
+                    'the "zoomed" window state is unavailable.')
+            top.update()
+            maxwidth, maxheight, maxx, maxy = get_window_geometry(top)
+            if sys.platform == 'win32':
+                # On Windows, the returned Y coordinate is the one before
+                # maximizing, so we use 0 which is correct unless a user puts
+                # their dock on the top of the screen (very rare).
+                maxy = 0
+            maxrooty = top.winfo_rooty()
+
+            # Get the "root y" coordinate for non-maximized windows with their
+            # y coordinate set to that of maximized windows.  This is needed
+            # to properly handle different title bar heights for non-maximized
+            # vs. maximized windows, as seen e.g. in Windows 10.
+            top.wm_state('normal')
+            top.update()
+            orig_geom = get_window_geometry(top)
+            max_y_geom = orig_geom[:3] + (maxy,)
+            set_window_geometry(top, max_y_geom)
+            top.update()
+            max_y_geom_rooty = top.winfo_rooty()
+
+            # Adjust the maximum window height to account for the different
+            # title bar heights of non-maximized vs. maximized windows.
+            maxheight += maxrooty - max_y_geom_rooty
+
+            self._max_height_and_y_coords[screen_dimensions] = maxheight, maxy
+
+            set_window_geometry(top, orig_geom)
+            top.wm_state(orig_state)
+
+        return self._max_height_and_y_coords[screen_dimensions]
+
+
+def get_window_geometry(top):
+    geom = top.wm_geometry()
+    m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
+    return tuple(map(int, m.groups()))
+
+
+def set_window_geometry(top, geometry):
+    top.wm_geometry("{:d}x{:d}+{:d}+{:d}".format(*geometry))
+
+
+if __name__ == "__main__":
+    from unittest import main
+    main('idlelib.idle_test.test_zoomheight', verbosity=2, exit=False)
+
+    # Add htest?
diff --git a/rootfs/usr/lib/python3.8/idlelib/zzdummy.py b/rootfs/usr/lib/python3.8/idlelib/zzdummy.py
new file mode 100644
index 0000000..1247e8f
--- /dev/null
+++ b/rootfs/usr/lib/python3.8/idlelib/zzdummy.py
@@ -0,0 +1,73 @@
+"""Example extension, also used for testing.
+
+See extend.txt for more details on creating an extension.
+See config-extension.def for configuring an extension.
+"""
+
+from idlelib.config import idleConf
+from functools import wraps
+
+
+def format_selection(format_line):
+    "Apply a formatting function to all of the selected lines."
+
+    @wraps(format_line)
+    def apply(self, event=None):
+        head, tail, chars, lines = self.formatter.get_region()
+        for pos in range(len(lines) - 1):
+            line = lines[pos]
+            lines[pos] = format_line(self, line)
+        self.formatter.set_region(head, tail, chars, lines)
+        return 'break'
+
+    return apply
+
+
+class ZzDummy:
+    """Prepend or remove initial text from selected lines."""
+
+    # Extend the format menu.
+    menudefs = [
+        ('format', [
+            ('Z in', '<<z-in>>'),
+            ('Z out', '<<z-out>>'),
+        ] )
+    ]
+
+    def __init__(self, editwin):
+        "Initialize the settings for this extension."
+        self.editwin = editwin
+        self.text = editwin.text
+        self.formatter = editwin.fregion
+
+    @classmethod
+    def reload(cls):
+        "Load class variables from config."
+        cls.ztext = idleConf.GetOption('extensions', 'ZzDummy', 'z-text')
+
+    @format_selection
+    def z_in_event(self, line):
+        """Insert text at the beginning of each selected line.
+
+        This is bound to the <<z-in>> virtual event when the extensions
+        are loaded.
+        """
+        return f'{self.ztext}{line}'
+
+    @format_selection
+    def z_out_event(self, line):
+        """Remove specific text from the beginning of each selected line.
+
+        This is bound to the <<z-out>> virtual event when the extensions
+        are loaded.
+        """
+        zlength = 0 if not line.startswith(self.ztext) else len(self.ztext)
+        return line[zlength:]
+
+
+ZzDummy.reload()
+
+
+if __name__ == "__main__":
+    import unittest
+    unittest.main('idlelib.idle_test.test_zzdummy', verbosity=2, exit=False)