-

24.6. IDLE

-

IDLE is Python’s Integrated Development and Learning Environment.

+

IDLE

+

Source code: Lib/idlelib/

+
+

IDLE is Python’s Integrated Development and Learning Environment.

IDLE has the following features:

    -
  • coded in 100% pure Python, using the tkinter GUI toolkit
  • -
  • cross-platform: works mostly the same on Windows, Unix, and Mac OS X
  • -
  • Python shell window (interactive interpreter) with colorizing -of code input, output, and error messages
  • -
  • multi-window text editor with multiple undo, Python colorizing, -smart indent, call tips, auto completion, and other features
  • -
  • search within any window, replace within editor windows, and search -through multiple files (grep)
  • -
  • debugger with persistent breakpoints, stepping, and viewing -of global and local namespaces
  • -
  • configuration, browsers, and other dialogs
  • +
  • coded in 100% pure Python, using the tkinter GUI toolkit

  • +
  • cross-platform: works mostly the same on Windows, Unix, and macOS

  • +
  • Python shell window (interactive interpreter) with colorizing +of code input, output, and error messages

  • +
  • multi-window text editor with multiple undo, Python colorizing, +smart indent, call tips, auto completion, and other features

  • +
  • search within any window, replace within editor windows, and search +through multiple files (grep)

  • +
  • debugger with persistent breakpoints, stepping, and viewing +of global and local namespaces

  • +
  • configuration, browsers, and other dialogs

-

24.6.2. Editing and navigation

-

In this section, ‘C’ refers to the Control key on Windows and Unix and -the Command key on Mac OSX.

+

Editing and navigation

+
+

Editor windows

+

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.

+

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.

+

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.

+
+
+

Key bindings

+

In this section, ‘C’ refers to the Control key on Windows and Unix and +the Command key on macOS.

    -
  • Backspace deletes to the left; Del deletes to the right

    -
  • -
  • C-Backspace delete word left; C-Del delete word to the right

    -
  • -
  • Arrow keys and Page Up/Page Down to move around

    -
  • -
  • C-LeftArrow and C-RightArrow moves by words

    -
  • -
  • Home/End go to begin/end of line

    -
  • -
  • C-Home/C-End go to begin/end of file

    -
  • -
  • Some useful Emacs bindings are inherited from Tcl/Tk:

    +
  • Backspace deletes to the left; Del deletes to the right

  • +
  • C-Backspace delete word left; C-Del delete word to the right

  • +
  • Arrow keys and Page Up/Page Down to move around

  • +
  • C-LeftArrow and C-RightArrow moves by words

  • +
  • Home/End go to begin/end of line

  • +
  • C-Home/C-End go to begin/end of file

  • +
  • Some useful Emacs bindings are inherited from Tcl/Tk:

      -
    • C-a beginning of line
    • -
    • C-e end of line
    • -
    • C-k kill line (but doesn’t put it in clipboard)
    • -
    • C-l center window around the insertion point
    • -
    • C-b go backwards one character without deleting (usually you can -also use the cursor key for this)
    • -
    • C-f go forward one character without deleting (usually you can -also use the cursor key for this)
    • -
    • C-p go up one line (usually you can also use the cursor key for -this)
    • -
    • C-d delete next character
    • +
    • C-a beginning of line

    • +
    • C-e end of line

    • +
    • C-k kill line (but doesn’t put it in clipboard)

    • +
    • C-l center window around the insertion point

    • +
    • C-b go backward one character without deleting (usually you can +also use the cursor key for this)

    • +
    • C-f go forward one character without deleting (usually you can +also use the cursor key for this)

    • +
    • C-p go up one line (usually you can also use the cursor key for +this)

    • +
    • C-d delete next character

-

Standard keybindings (like C-c to copy and C-v to paste) +

Standard keybindings (like C-c to copy and C-v to paste) may work. Keybindings are selected in the Configure IDLE dialog.

+
-

24.6.2.1. Automatic indentation

+

Automatic indentation

After a block-opening statement, the next line is indented by 4 spaces (in the Python Shell window by one tab). After certain keywords (break, return etc.) -the next line is dedented. In leading indentation, Backspace deletes up -to 4 spaces if they are there. Tab inserts spaces (in the Python -Shell window one tab), number depends on Indent width. Currently tabs +the next line is dedented. In leading indentation, Backspace deletes up +to 4 spaces if they are there. Tab inserts spaces (in the Python +Shell window one tab), number depends on Indent width. Currently, tabs are restricted to four spaces due to Tcl/Tk limitations.

-

See also the indent/dedent region commands in the edit menu.

+

See also the indent/dedent region commands on the +Format menu.

-

24.6.2.2. Completions

+

Completions

Completions are supplied for functions, classes, and attributes of classes, both built-in and user-defined. Completions are also provided for filenames.

The AutoCompleteWindow (ACW) will open after a predefined delay (default is -two seconds) after a ‘.’ or (in a string) an os.sep is typed. If after one +two seconds) after a ‘.’ or (in a string) an os.sep is typed. If after one of those characters (plus zero or more other characters) a tab is typed the ACW will open immediately if a possible continuation is found.

If there is only one possible completion for the characters entered, a -Tab will supply that completion without opening the ACW.

-

‘Show Completions’ will force open a completions window, by default the -C-space will open a completions window. In an empty +Tab will supply that completion without opening the ACW.

+

‘Show Completions’ will force open a completions window, by default the +C-space will open a completions window. In an empty string, this will contain the files in the current directory. On a blank line, it will contain the built-in and user-defined functions and -classes in the current name spaces, plus any modules imported. If some +classes in the current namespaces, plus any modules imported. If some characters have been entered, the ACW will attempt to be more specific.

If a string of characters is typed, the ACW selection will jump to the -entry most closely matching those characters. Entering a tab will +entry most closely matching those characters. Entering a tab will cause the longest non-ambiguous match to be entered in the Editor window or -Shell. Two tab in a row will supply the current ACW selection, as +Shell. Two tab in a row will supply the current ACW selection, as will return or a double click. Cursor keys, Page Up/Down, mouse selection, and the scroll wheel all operate on the ACW.

-

“Hidden” attributes can be accessed by typing the beginning of hidden -name after a ‘.’, e.g. ‘_’. This allows access to modules with -__all__ set, or to class-private attributes.

-

Completions and the ‘Expand Word’ facility can save a lot of typing!

+

“Hidden” attributes can be accessed by typing the beginning of hidden +name after a ‘.’, e.g. ‘_’. This allows access to modules with +__all__ set, or to class-private attributes.

+

Completions and the ‘Expand Word’ facility can save a lot of typing!

Completions are currently limited to those in the namespaces. Names in -an Editor window which are not via __main__ and sys.modules will +an Editor window which are not via __main__ and sys.modules will not be found. Run the module once with your imports to correct this situation. Note that IDLE itself places quite a few modules in sys.modules, so much can be found by default, e.g. the re module.

-

If you don’t like the ACW popping up unbidden, simply make the delay +

If you don’t like the ACW popping up unbidden, simply make the delay longer or disable the extension.

-

24.6.2.3. Calltips

-

A calltip is shown when one types ( after the name of an acccessible +

Calltips

+

A calltip is shown when one types ( after the name of an accessible function. A name expression may include dots and subscripts. A calltip remains until it is clicked, the cursor is moved out of the argument area, -or ) is typed. When the cursor is in the argument part of a definition, +or ) is typed. When the cursor is in the argument part of a definition, the menu or shortcut display a calltip.

A calltip consists of the function signature and the first line of the docstring. For builtins without an accessible signature, the calltip @@ -435,40 +510,61 @@

24.6.2.3. Calltipsitertools.count(. A calltip +

For example, restart the Shell and enter itertools.count(. A calltip appears because Idle imports itertools into the user process for its own use. -(This could change.) Enter turtle.write( and nothing appears. Idle does +(This could change.) Enter turtle.write( and nothing appears. Idle does not import turtle. The menu or shortcut do nothing either. Enter -import turtle and then turtle.write( will work.

+import turtle and then turtle.write( will work.

In an editor, import statements have no effect until one runs the file. One might want to run a file after writing the import statements at the top, or immediately run an existing file before editing.

+
+

Code Context

+

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 +class, def, or if 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.

+

The text and background colors for the context pane can be configured under +the Highlights tab in the Configure IDLE dialog.

+
-

24.6.2.4. Python Shell window

+

Python Shell window

+

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.

+

When one pastes code into Shell, it is not compiled and possibly executed +until one hits Return. One may edit pasted code first. +If one pastes more that one statement into Shell, the result will be a +SyntaxError when multiple statements are compiled as if they were one.

+

The editing features described in previous subsections work when entering +code interactively. IDLE’s Shell window also responds to the following keys.

    -
  • C-c interrupts executing command

    -
  • -
  • C-d sends end-of-file; closes window if typed at a >>> prompt

    -
  • -
  • Alt-/ (Expand word) is also useful to reduce typing

    +
  • C-c interrupts executing command

  • +
  • C-d sends end-of-file; closes window if typed at a >>> prompt

  • +
  • Alt-/ (Expand word) is also useful to reduce typing

    Command history

      -
    • Alt-p retrieves previous command matching what you have typed. On -OS X use C-p.
    • -
    • Alt-n retrieves next. On OS X use C-n.
    • -
    • Return while on any previous command retrieves that command
    • +
    • Alt-p retrieves previous command matching what you have typed. On +macOS use C-p.

    • +
    • Alt-n retrieves next. On macOS use C-n.

    • +
    • Return while on any previous command retrieves that command

-

24.6.2.5. Text colors

+

Text colors

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 class and -def, strings, and comments. For any text window, these are the cursor (when +keywords, builtin class and function names, names following class and +def, strings, and comments. For any text window, these are the cursor (when present), found text (when possible), and selected text.

Text coloring is done in the background, so uncolorized text is occasionally visible. To change the color scheme, use the Configure IDLE dialog @@ -477,22 +573,22 @@

24.6.2.5. Text colors -

24.6.3. Startup and code execution

-

Upon startup with the -s option, IDLE will execute the file referenced by -the environment variables IDLESTARTUP or PYTHONSTARTUP. -IDLE first checks for IDLESTARTUP; if IDLESTARTUP is present the file -referenced is run. If IDLESTARTUP is not present, IDLE checks for -PYTHONSTARTUP. Files referenced by these environment variables are +

Startup and code execution

+

Upon startup with the -s option, IDLE will execute the file referenced by +the environment variables IDLESTARTUP or PYTHONSTARTUP. +IDLE first checks for IDLESTARTUP; if IDLESTARTUP is present the file +referenced is run. If IDLESTARTUP is not present, IDLE checks for +PYTHONSTARTUP. 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.

-

In addition, Tk also loads a startup file if it is present. Note that the -Tk file is loaded unconditionally. This additional file is .Idle.py and is -looked for in the user’s home directory. Statements in this file will be +

In addition, Tk also loads a startup file if it is present. Note that the +Tk file is loaded unconditionally. This additional file is .Idle.py 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.

+functions to be used from IDLE’s Python shell.

-

24.6.3.1. Command line usage

-
idle.py [-c command] [-d] [-e] [-h] [-i] [-r file] [-s] [-t title] [-] [arg] ...
+

Command line usage

+
-
-

24.6.3.2. IDLE-console differences

-

As much as possible, the result of executing Python code with IDLE is the -same as executing the same code in a console window. However, the different -interface and operation occasionally affects visible results. For instance, -sys.modules starts with more entries.

-

IDLE also replaces sys.stdin, sys.stdout, and sys.stderr with -objects that get input from and send output to the Shell window. -When this window has the focus, it controls the keyboard and screen. -This is normally transparent, but functions that directly access the keyboard -and screen will not work. If sys is reset with reload(sys), -IDLE’s changes are lost and things like input, raw_input, and -print will not work correctly.

-

With IDLE’s Shell, one enters, edits, and recalls complete statements. -Some consoles only work with a single physical line at a time. IDLE uses -exec to run each statement. As a result, '__builtins__' is always -defined for each statement.

+
+

Startup failure

+

IDLE uses a socket to communicate between the IDLE GUI process and the user +code execution process. A connection must be established whenever the Shell +starts or restarts. (The latter is indicated by a divider line that says +‘RESTART’). If the user process fails to connect to the GUI process, it +displays a Tk error box with a ‘cannot connect’ message that directs the +user here. It then exits.

+

A common cause of failure is a user-written file with the same name as a +standard library module, such as random.py and tkinter.py. 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.

+

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.

+

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.

+

A zombie pythonw.exe process could be a problem. On Windows, use Task +Manager to detect and stop one. Sometimes a restart initiated by a program +crash or Keyboard Interrupt (control-C) may fail to connect. Dismissing +the error box or Restart Shell on the Shell menu may fix a temporary problem.

+

When IDLE first starts, it attempts to read user configuration files in +~/.idlerc/ (~ 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, using the configuration +dialog, under Options, instead Options. Once it happens, the solution may +be to delete one or more of the configuration files.

+

If IDLE quits with no message, and it was not started from a console, try +starting from a console (python -m idlelib) and see if a message appears.

+
+
+

Running user code

+

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, sys.modules starts with more entries, +and threading.activeCount() returns 2 instead of 1.

+

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 sys.stdin, sys.stdout, and sys.stderr +with objects that get input from and send output to the Shell window. +The original values stored in sys.__stdin__, sys.__stdout__, and +sys.__stderr__ are not touched, but may be None.

+

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.

+

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 input from sys.stdin +or print or write 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.

+

The IDLE code running in the execution process adds frames to the call stack +that would not be there otherwise. IDLE wraps sys.getrecursionlimit and +sys.setrecursionlimit to reduce the effect of the additional stack frames.

+

If sys is reset by user code, such as with importlib.reload(sys), +IDLE’s changes are lost and input from the keyboard and output to the screen +will not work correctly.

+

When user code raises SystemExit either directly or by calling sys.exit, IDLE +returns to a Shell prompt instead of exiting.

+
+
+

User output in Shell

+

When a program outputs text, the result is determined by the +corresponding output device. When IDLE executes user code, sys.stdout +and sys.stderr 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.

+

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.

+

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.)

+
>>> s = 'a\tb\a<\x02><\r>\bc\nd'  # Enter 22 chars.
+>>> len(s)
+14
+>>> s  # Display repr(s)
+'a\tb\x07<\x02><\r>\x08c\nd'
+>>> print(s, end='')  # Display s as is.
+# Result varies by OS and font.  Try it.
+
+
+

The repr 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.

+

Normal and error output are generally kept separate (on separate lines) +from code input and each other. They each get different highlight colors.

+

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.

+

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.

+

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.

+
+
+

Developing tkinter applications

+

IDLE is intentionally different from standard Python in order to +facilitate development of tkinter programs. Enter import tkinter as tk; +root = tk.Tk() in standard Python and nothing appears. Enter the same +in IDLE and a tk window appears. In standard Python, one must also enter +root.update() to see the window. IDLE does the equivalent in the +background, about 20 times a second, which is about every 50 milliseconds. +Next enter b = tk.Button(root, text='button'); b.pack(). Again, +nothing visibly changes in standard Python until one enters root.update().

+

Most tkinter programs run root.mainloop(), which usually does not +return until the tk app is destroyed. If the program is run with +python -i or from an IDLE editor, a >>> shell prompt does not +appear until mainloop() returns, at which time there is nothing left +to interact with.

+

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.

-

24.6.3.3. Running without a subprocess

+

Running without a subprocess

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.

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 connecton or misconfiguration of +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.

If IDLE is started with the -n command line switch it will run in a @@ -555,44 +776,60 @@

24.6.3.3. Running without a subprocess -

Deprecated since version 3.4.

+

Deprecated since version 3.4.

-

24.6.4. Help and preferences

-
-

24.6.4.1. Additional help sources

-

IDLE includes a help menu entry called “Python Docs” that will open the -extensive sources of help, including tutorials, available at docs.python.org. -Selected URLs can be added or removed from the help menu at any time using the -Configure IDLE dialog. See the IDLE help option in the help menu of IDLE for -more information.

+

Help and preferences

+
+

Help sources

+

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.

+

Help menu entry “Python Docs” opens the extensive sources of help, +including tutorials, available at docs.python.org/x.y, where ‘x.y’ +is the currently running Python version. If your system +has an off-line copy of the docs (this may be an installation option), +that will be opened instead.

+

Selected URLs can be added or removed from the help menu at any time using the +General tab of the Configure IDLE dialog.

-

24.6.4.2. Setting preferences

+

Setting preferences

The font preferences, highlighting, keys, and general preferences can be -changed via Configure IDLE on the Option menu. Keys can be user defined; -IDLE ships with four built in key sets. In addition a user can create a -custom key set in the Configure IDLE dialog under the keys tab.

+changed via Configure IDLE on the Option menu. +Non-default user settings are saved in a .idlerc 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 .idlerc.

+

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.

+

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.

+
+
+

IDLE on macOS

+

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.

-

24.6.4.3. Extensions

-

IDLE contains an extension facility. Peferences for extensions can be -changed with Configure Extensions. See the beginning of config-extensions.def -in the idlelib directory for further information. The default extensions -are currently:

-
    -
  • FormatParagraph
  • -
  • AutoExpand
  • -
  • ZoomHeight
  • -
  • ScriptBinding
  • -
  • CallTips
  • -
  • ParenMatch
  • -
  • AutoComplete
  • -
  • CodeContext
  • -
  • RstripExtension
  • -
+

Extensions

+

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.

@@ -603,40 +840,47 @@

24.6.4.3. Extensions
-

Table Of Contents

+

Table of Contents

@@ -683,32 +918,54 @@

Navigation

modules |
  • - next |
  • - previous |
  • -
  • -
  • Python »
  • -
  • - Python 2.7.12 documentation » -
  • - - - + +
  • +
  • Python »
  • + + +
  • + 3.9.0a1 Documentation » +
  • + + + +
  • + + + + + | +
  • +
    diff --git a/PythonLib/full/idlelib/help.py b/PythonLib/full/idlelib/help.py index 3ab48513..9f63ea0d 100644 --- a/PythonLib/full/idlelib/help.py +++ b/PythonLib/full/idlelib/help.py @@ -2,9 +2,9 @@ Contents are subject to revision at any time, without notice. -Help => About IDLE: diplay About Idle dialog +Help => About IDLE: display About Idle dialog - + Help => IDLE Help: Display help.html with proper formatting. @@ -24,16 +24,15 @@ show_idlehelp - Create HelpWindow. Called in EditorWindow.help_dialog. """ -from HTMLParser import HTMLParser -from os.path import abspath, dirname, isdir, isfile, join +from html.parser import HTMLParser +from os.path import abspath, dirname, isfile, join from platform import python_version -from Tkinter import Tk, Toplevel, Frame, Text, Scrollbar, Menu, Menubutton -import tkFont as tkfont -from idlelib.configHandler import idleConf -use_ttk = False # until available to import -if use_ttk: - from tkinter.ttk import Menubutton +from tkinter import Toplevel, Frame, Text, Menu +from tkinter.ttk import Menubutton, Scrollbar +from tkinter import font as tkfont + +from idlelib.config import idleConf ## About IDLE ## @@ -50,21 +49,23 @@ class HelpParser(HTMLParser): the handle_starttag and handle_endtags methods, might have to also. """ def __init__(self, text): - HTMLParser.__init__(self) - self.text = text # text widget we're rendering into - self.tags = '' # current block level text tags to apply - self.chartags = '' # current character level text tags - self.show = False # used so we exclude page navigation - self.hdrlink = False # used so we don't show header links - self.level = 0 # indentation level - self.pre = False # displaying preformatted text - self.hprefix = '' # prefix such as '25.5' to strip from headings - self.nested_dl = False # if we're in a nested
    - self.simplelist = False # simple list (no double spacing) - self.toc = [] # pair headers with text indexes for toc - self.header = '' # text within header tags for toc + 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
    ? + 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) @@ -76,11 +77,14 @@ def handle_starttag(self, tag, attrs): class_ = v s = '' if tag == 'div' and class_ == 'section': - self.show = True # start of main content + self.show = True # Start main content. elif tag == 'div' and class_ == 'sphinxsidebar': - self.show = False # end of main content - elif tag == 'p' and class_ != 'first': - s = '\n\n' + self.show = False # End main content. + elif tag == 'p' and self.prevtag and not self.prevtag[0]: + # Begin a new block for

    tags after a closed tag. + # Avoid extra lines, e.g. after

     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':
    @@ -100,7 +104,7 @@ def handle_starttag(self, tag, attrs):
             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
    +            s = '\n\n' if not self.nested_dl else '\n'  # Avoid extra line.
                 self.nested_dl = False
             elif tag == 'dd':
                 self.indent()
    @@ -121,13 +125,18 @@ def handle_starttag(self, tag, attrs):
                 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']:
    -            self.indent(0)  # clear tag, reset indent
    +            assert self.level == 0
                 if self.show:
    -                self.toc.append((self.header, self.text.index('insert')))
    +                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':
    @@ -136,24 +145,25 @@ def handle_endtag(self, tag):
                 self.pre = False
                 self.tags = ''
             elif tag in ['ul', 'dd', 'ol']:
    -            self.indent(amt=-1)
    +            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':
    -                self.hprefix = d[0:d.index(' ')]
    -            if self.tags in ['h1', 'h2', 'h3'] and self.hprefix != '':
    -                if d[0:len(self.hprefix)] == self.hprefix:
    -                    d = d[len(self.hprefix):].strip()
    -                self.header += d
    +                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))
     
    -    def handle_charref(self, name):
    -        if self.show:
    -            self.text.insert('end', unichr(int(name)))
    -
     
     class HelpText(Text):
         "Display help.html."
    @@ -161,7 +171,7 @@ 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
    +        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)
     
    @@ -181,8 +191,8 @@ def __init__(self, parent, filename):
             self.tag_configure('l4', lmargin1=100, lmargin2=100)
     
             self.parser = HelpParser(self)
    -        with open(filename) as f:
    -            contents = f.read().decode(encoding='utf-8')
    +        with open(filename, encoding='utf-8') as f:
    +            contents = f.read()
             self.parser.feed(contents)
             self['state'] = 'disabled'
     
    @@ -201,15 +211,17 @@ class HelpFrame(Frame):
         "Display html text, scrollbar, and toc."
         def __init__(self, parent, filename):
             Frame.__init__(self, parent)
    -        text = HelpText(self, filename)
    +        self.text = text = HelpText(self, filename)
             self['background'] = text['background']
    -        scroll = Scrollbar(self, command=text.yview)
    +        self.toc = toc = self.toc_menu(text)
    +        self.scroll = scroll = Scrollbar(self, command=text.yview)
             text['yscrollcommand'] = scroll.set
    +
             self.rowconfigure(0, weight=1)
    -        self.columnconfigure(1, weight=1)  # text
    -        self.toc_menu(text).grid(column=0, row=0, sticky='nw')
    -        text.grid(column=1, row=0, sticky='nsew')
    -        scroll.grid(column=2, row=0, sticky='ns')
    +        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."
    @@ -235,43 +247,46 @@ def __init__(self, parent, filename, title):
     def copy_strip():
         """Copy idle.html to idlelib/help.html, stripping trailing whitespace.
     
    -    Files with trailing whitespace cannot be pushed to the hg cpython
    +    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 in the earliest maintenance version, with
    +    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()"
    -    After refreshing TortoiseHG workshop to generate a diff,
    -    check  both the diff and displayed text.  Push the diff along with
    -    the idle.rst change and merge both into default (or an intermediate
    -    maintenance version).
    -
    -    When the 'earlist' version gets its final maintenance release,
    -    do an update as described above, without editing idle.rst, to
    -    rebase help.html on the next version of idle.rst.  Do not worry
    -    about version changes as version is not displayed.  Examine other
    -    changes and the result of Help -> IDLE Help.
    -
    -    If maintenance and default versions of idle.rst diverge, and
    -    merging does not go smoothly, then consider generating
    -    separate help.html files from separate idle.htmls.
    +    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')
    +            'Doc', 'build', 'html', 'library', 'idle.html')
         dst = join(abspath(dirname(__file__)), 'help.html')
    -    with open(src, 'r') as inn,\
    -         open(dst, 'w') as out:
    +    with open(src, 'rb') as inn,\
    +         open(dst, 'wb') as out:
             for line in inn:
    -            out.write(line.rstrip() + '\n')
    -    print('idle.html copied to help.html')
    +            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
    +        # 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/PythonLib/full/idlelib/help.txt b/PythonLib/full/idlelib/help.txt
    deleted file mode 100644
    index 296c78bd..00000000
    --- a/PythonLib/full/idlelib/help.txt
    +++ /dev/null
    @@ -1,302 +0,0 @@
    -This file, idlelib/help.txt is out-of-date and no longer used by Idle.
    -It is deprecated and will be removed in the future, possibly in 3.6
    -----------------------------------------------------------------------
    -
    -[See the end of this file for ** TIPS ** on using IDLE !!]
    -
    -File Menu:
    -
    -	New File         -- Create a new editing window
    -	Open...          -- Open an existing file
    -	Recent Files...  -- Open a list of recent files
    -	Open Module...   -- Open an existing module (searches sys.path)
    -	Class Browser    -- Show classes and methods in current file
    -	Path Browser     -- Show sys.path directories, modules, classes
    -                            and methods
    -	---
    -	Save             -- Save current window to the associated file (unsaved
    -		            windows have a * before and after the window title)
    -
    -	Save As...       -- Save current window to new file, which becomes
    -		            the associated file
    -	Save Copy As...  -- Save current window to different file
    -		            without changing the associated file
    -	---
    -	Print Window     -- Print the current window
    -	---
    -	Close            -- Close current window (asks to save if unsaved)
    -	Exit             -- Close all windows, quit (asks to save if unsaved)
    -
    -Edit Menu:
    -
    -	Undo             -- Undo last change to current window
    -                            (A maximum of 1000 changes may be undone)
    -	Redo             -- Redo last undone change to current window
    -	---
    -	Cut              -- Copy a selection into system-wide clipboard,
    -                            then delete the selection
    -	Copy             -- Copy selection into system-wide clipboard
    -	Paste            -- Insert system-wide clipboard into window
    -	Select All       -- Select the entire contents of the edit buffer
    -	---
    -	Find...          -- Open a search dialog box with many options
    -	Find Again       -- Repeat last search
    -	Find Selection   -- Search for the string in the selection
    -	Find in Files... -- Open a search dialog box for searching files
    -	Replace...       -- Open a search-and-replace dialog box
    -	Go to Line       -- Ask for a line number and show that line
    -	Show Calltip     -- Open a small window with function param hints
    -	Show Completions -- Open a scroll window allowing selection keywords
    -			    and attributes. (see '*TIPS*', below)
    -	Show Parens	 -- Highlight the surrounding parenthesis
    -	Expand Word      -- Expand the word you have typed to match another
    -		            word in the same buffer; repeat to get a
    -                            different expansion
    -
    -Format Menu (only in Edit window):
    -
    -	Indent Region       -- Shift selected lines right 4 spaces
    -	Dedent Region       -- Shift selected lines left 4 spaces
    -	Comment Out Region  -- Insert ## in front of selected lines
    -	Uncomment Region    -- Remove leading # or ## from selected lines
    -	Tabify Region       -- Turns *leading* stretches of spaces into tabs
    -		(Note: We recommend using 4 space blocks to indent Python code.)
    -	Untabify Region     -- Turn *all* tabs into the right number of spaces
    -	New Indent Width... -- Open dialog to change indent width
    -	Format Paragraph    -- Reformat the current blank-line-separated
    -                               paragraph
    -
    -Run Menu (only in Edit window):
    -
    -	Python Shell -- Open or wake up the Python shell window
    -	---
    -	Check Module -- Run a syntax check on the module
    -	Run Module   -- Execute the current file in the __main__ namespace
    -
    -Shell Menu (only in Shell window):
    -
    -	View Last Restart -- Scroll the shell window to the last restart
    -	Restart Shell     -- Restart the interpreter with a fresh environment
    -
    -Debug Menu (only in Shell window):
    -
    -	Go to File/Line   -- look around the insert point for a filename
    -		             and line number, open the file, and show the line
    -	Debugger (toggle) -- Run commands in the shell under the debugger
    -	Stack Viewer      -- Show the stack traceback of the last exception
    -	Auto-open Stack Viewer (toggle) -- Open stack viewer on traceback
    -
    -Options Menu:
    -
    -	Configure IDLE -- Open a configuration dialog.  Fonts, indentation,
    -                          keybindings, and color themes may be altered.
    -                          Startup Preferences may be set, and Additional Help
    -                          Sources can be specified.  On OS X, open the
    -                          configuration dialog by selecting Preferences
    -                          in the application menu.
    -	---
    -	Code Context --	  Open a pane at the top of the edit window which
    -			  shows the block context of the section of code
    -			  which is scrolling off the top or the window.
    -			  (Not present in Shell window.)
    -
    -Window Menu:
    -
    -	Zoom Height -- toggles the window between configured size
    -	and maximum height.
    -	---
    -	The rest of this menu lists the names of all open windows;
    -	select one to bring it to the foreground (deiconifying it if
    -	necessary).
    -
    -Help Menu:
    -
    -	About IDLE  -- Version, copyright, license, credits
    -	IDLE Readme -- Background discussion and change details
    -	---
    -	IDLE Help   -- Display this file
    -	Python Docs -- Access local Python documentation, if
    -		       installed.  Otherwise, access www.python.org.
    -	---
    -	(Additional Help Sources may be added here)
    -
    -Edit context menu (Right-click / Control-click on OS X in Edit window):
    -
    -    	Cut              -- Copy a selection into system-wide clipboard,
    -                            then delete the selection
    -	Copy             -- Copy selection into system-wide clipboard
    -	Paste            -- Insert system-wide clipboard into window
    -	Set Breakpoint   -- Sets a breakpoint (when debugger open)
    -	Clear Breakpoint -- Clears the breakpoint on that line
    -
    -Shell context menu (Right-click / Control-click on OS X in Shell window):
    -
    -	Cut              -- Copy a selection into system-wide clipboard,
    -                            then delete the selection
    -	Copy             -- Copy selection into system-wide clipboard
    -	Paste            -- Insert system-wide clipboard into window
    -        ---
    -	Go to file/line  -- Same as in Debug menu
    -
    -
    -** TIPS **
    -==========
    -
    -Additional Help Sources:
    -
    -	Windows users can Google on zopeshelf.chm to access Zope help files in
    -	the Windows help format.  The Additional Help Sources feature of the
    -	configuration GUI supports .chm, along with any other filetypes
    -	supported by your browser.  Supply a Menu Item title, and enter the
    -	location in the Help File Path slot of the New Help Source dialog.  Use
    -	http:// and/or www. to identify external URLs, or download the file and
    -	browse for its path on your machine using the Browse button.
    -
    -	All users can access the extensive sources of help, including
    -	tutorials, available at www.python.org/doc.  Selected URLs can be added
    -	or removed from the Help menu at any time using Configure IDLE.
    -
    -Basic editing and navigation:
    -
    -	Backspace deletes char to the left; DEL deletes char to the right.
    -	Control-backspace deletes word left, Control-DEL deletes word right.
    -	Arrow keys and Page Up/Down move around.
    -	Control-left/right Arrow moves by words in a strange but useful way.
    -	Home/End go to begin/end of line.
    -	Control-Home/End go to begin/end of file.
    -	Some useful Emacs bindings are inherited from Tcl/Tk:
    -		Control-a     beginning of line
    -		Control-e     end of line
    -		Control-k     kill line (but doesn't put it in clipboard)
    -		Control-l     center window around the insertion point
    -	Standard Windows bindings may work on that platform.
    -	Keybindings are selected in the Settings Dialog, look there.
    -
    -Automatic indentation:
    -
    -	After a block-opening statement, the next line is indented by 4 spaces
    -	(in the Python Shell window by one tab).  After certain keywords
    -	(break, return etc.) the next line is dedented.  In leading
    -	indentation, Backspace deletes up to 4 spaces if they are there.  Tab
    -	inserts spaces (in the Python Shell window one tab), number depends on
    -	Indent Width.  (N.B. Currently tabs are restricted to four spaces due
    -	to Tcl/Tk issues.)
    -
    -        See also the indent/dedent region commands in the edit menu.
    -
    -Completions:
    -
    -	Completions are supplied for functions, classes, and attributes of
    -	classes, both built-in and user-defined.  Completions are also provided
    -	for filenames.
    -
    -	The AutoCompleteWindow (ACW) will open after a predefined delay
    -	(default is two seconds) after a '.' or (in a string) an os.sep is
    -	typed.  If after one of those characters (plus zero or more other
    -	characters) you type a Tab the ACW will open immediately if a possible
    -	continuation is found.
    -
    -	If there is only one possible completion for the characters entered, a
    -	Tab will supply that completion without opening the ACW.
    -
    -	'Show Completions' will force open a completions window.  In an empty
    -	string, this will contain the files in the current directory.  On a
    -	blank line, it will contain the built-in and user-defined functions and
    -	classes in the current name spaces, plus any modules imported.  If some
    -	characters have been entered, the ACW will attempt to be more specific.
    -
    -	If string of characters is typed, the ACW selection will jump to the
    -	entry most closely matching those characters. Entering a Tab will cause
    -	the longest non-ambiguous match to be entered in the Edit window or
    -	Shell.  Two Tabs in a row will supply the current ACW selection, as
    -	will Return or a double click.  Cursor keys, Page Up/Down, mouse
    -	selection, and the scrollwheel all operate on the ACW.
    -
    -	'Hidden' attributes can be accessed by typing the beginning of hidden
    -	name after a '.'.  e.g. '_'.  This allows access to modules with
    -	'__all__' set, or to class-private attributes.
    -
    -	Completions and the 'Expand Word' facility can save a lot of typing!
    -
    -	Completions are currently limited to those in the namespaces.  Names in
    -	an Edit window which are not via __main__ or sys.modules will not be
    -	found.  Run the module once with your imports to correct this
    -	situation.  Note that IDLE itself places quite a few modules in
    -	sys.modules, so much can be found by default, e.g. the re module.
    -
    -	If you don't like the ACW popping up unbidden, simply make the delay
    -	longer or disable the extension.  OTOH, you could make the delay zero.
    -
    -	You could also switch off the CallTips extension.  (We will be adding
    -	a delay to the call tip window.)
    -
    -Python Shell window:
    -
    -	Control-c interrupts executing command.
    -	Control-d sends end-of-file; closes window if typed at >>> prompt.
    -
    -    Command history:
    -
    -	Alt-p retrieves previous command matching what you have typed.
    -	Alt-n retrieves next.
    -	      (These are Control-p, Control-n on OS X)
    -	Return while cursor is on a previous command retrieves that command.
    -	Expand word is also useful to reduce typing.
    -
    -    Syntax colors:
    -
    -	The coloring is applied in a background "thread", so you may
    -	occasionally see uncolorized text.  To change the color
    -	scheme, use the Configure IDLE / Highlighting dialog.
    -
    -    Python default syntax colors:
    -
    -	Keywords	orange
    -	Builtins	royal purple
    -	Strings		green
    -	Comments	red
    -	Definitions	blue
    -
    -    Shell default colors:
    -
    -	Console output	brown
    -	stdout		blue
    -	stderr		red
    -	stdin		black
    -
    -Other preferences:
    -
    -	The font preferences, keybinding, and startup preferences can
    -	be changed using the Settings dialog.
    -
    -Command line usage:
    -
    -	Enter idle -h at the command prompt to get a usage message.
    -
    -Running without a subprocess:
    -
    -	If IDLE is started with the -n command line switch it will run in a
    -	single process and will not create the subprocess which runs the RPC
    -	Python execution server.  This can be useful if Python cannot create
    -	the subprocess or the RPC socket interface on your platform.  However,
    -	in this mode user code is not isolated from IDLE itself.  Also, the
    -	environment is not restarted when Run/Run Module (F5) is selected.  If
    -	your code has been modified, you must reload() the affected modules and
    -	re-import any specific items (e.g. from foo import baz) if the changes
    -	are to take effect.  For these reasons, it is preferable to run IDLE
    -	with the default subprocess if at all possible.
    -
    -Extensions:
    -
    -	IDLE contains an extension facility.  See the beginning of
    -	config-extensions.def in the idlelib directory for further information.
    -	The default extensions are currently:
    -
    -		FormatParagraph
    -		AutoExpand
    -		ZoomHeight
    -		ScriptBinding
    -		CallTips
    -		ParenMatch
    -		AutoComplete
    -		CodeContext
    diff --git a/PythonLib/full/idlelib/help_about.py b/PythonLib/full/idlelib/help_about.py
    new file mode 100644
    index 00000000..64b13ac2
    --- /dev/null
    +++ b/PythonLib/full/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('', self.ok)  # dismiss dialog
    +        self.bind('', 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/PythonLib/full/idlelib/history.py b/PythonLib/full/idlelib/history.py
    new file mode 100644
    index 00000000..ad44a96a
    --- /dev/null
    +++ b/PythonLib/full/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 <> event (default Alt-N).
    +    history_prev - Bound to <> 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("<>", self.history_prev)
    +        text.bind("<>", 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/PythonLib/full/idlelib/hyperparser.py b/PythonLib/full/idlelib/hyperparser.py
    new file mode 100644
    index 00000000..77baca78
    --- /dev/null
    +++ b/PythonLib/full/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/PythonLib/full/idlelib/idle.py b/PythonLib/full/idlelib/idle.py
    index 141534df..485d5a75 100644
    --- a/PythonLib/full/idlelib/idle.py
    +++ b/PythonLib/full/idlelib/idle.py
    @@ -1,6 +1,7 @@
     import os.path
     import sys
     
    +
     # Enable running IDLE with idlelib in a non-standard location.
     # This was once used to run development versions of IDLE.
     # Because PEP 434 declared idle.py a public interface,
    @@ -9,5 +10,5 @@
     if idlelib_dir not in sys.path:
         sys.path.insert(0, idlelib_dir)
     
    -from idlelib.PyShell import main  # This is subject to change
    +from idlelib.pyshell import main  # This is subject to change
     main()
    diff --git a/PythonLib/full/idlelib/idle.pyw b/PythonLib/full/idlelib/idle.pyw
    index 9ce4c9f8..e73c049b 100644
    --- a/PythonLib/full/idlelib/idle.pyw
    +++ b/PythonLib/full/idlelib/idle.pyw
    @@ -1,10 +1,10 @@
     try:
    -    import idlelib.PyShell
    +    import idlelib.pyshell
     except ImportError:
    -    # IDLE is not installed, but maybe PyShell is on sys.path:
    -    import PyShell
    +    # IDLE is not installed, but maybe pyshell is on sys.path:
    +    from . import pyshell
         import os
    -    idledir = os.path.dirname(os.path.abspath(PyShell.__file__))
    +    idledir = os.path.dirname(os.path.abspath(pyshell.__file__))
         if idledir != os.getcwd():
             # We're not in the IDLE directory, help the subprocess find run.py
             pypath = os.environ.get('PYTHONPATH', '')
    @@ -12,6 +12,6 @@ except ImportError:
                 os.environ['PYTHONPATH'] = pypath + ':' + idledir
             else:
                 os.environ['PYTHONPATH'] = idledir
    -    PyShell.main()
    +    pyshell.main()
     else:
    -    idlelib.PyShell.main()
    +    idlelib.pyshell.main()
    diff --git a/PythonLib/full/idlelib/idle_test/README.txt b/PythonLib/full/idlelib/idle_test/README.txt
    index 6967d705..566bfd17 100644
    --- a/PythonLib/full/idlelib/idle_test/README.txt
    +++ b/PythonLib/full/idlelib/idle_test/README.txt
    @@ -2,12 +2,12 @@ README FOR IDLE TESTS IN IDLELIB.IDLE_TEST
     
     0. Quick Start
     
    -Automated unit tests were added in 2.7 for Python 2.x and 3.3 for Python 3.x.
    +Automated unit tests were added in 3.3 for Python 3.x.
     To run the tests from a command line:
     
     python -m test.test_idle
     
    -Human-mediated tests were added later in 2.7 and in 3.4.
    +Human-mediated tests were added later in 3.4.
     
     python -m idlelib.idle_test.htest
     
    @@ -15,46 +15,53 @@ python -m idlelib.idle_test.htest
     1. Test Files
     
     The idle directory, idlelib, has over 60 xyz.py files. The idle_test
    -subdirectory should contain a test_xyz.py for each, where 'xyz' is lowercased
    -even if xyz.py is not. Here is a possible template, with the blanks after
    -'.' and 'as', and before and after '_' to be filled in.
    +subdirectory contains test_xyz.py for each implementation file xyz.py.
    +To add a test for abc.py, open idle_test/template.py and immediately
    +Save As test_abc.py.  Insert 'abc' on the first line, and replace
    +'zzdummy' with 'abc.
     
    -import unittest
    -from test.support import requires
    -import idlelib. as
    -
    -class _Test(unittest.TestCase):
    +Remove the imports of requires and tkinter if not needed.  Otherwise,
    +add to the tkinter imports as needed.
     
    -    def test_(self):
    +Add a prefix to 'Test' for the initial test class.  The template class
    +contains code needed or possibly needed for gui tests.  See the next
    +section if doing gui tests.  If not, and not needed for further classes,
    +this code can be removed.
     
    -if __name__ == '__main__':
    -    unittest.main(verbosity=2)
    -
    -Add the following at the end of xyy.py, with the appropriate name added after
    -'test_'. Some files already have something like this for htest.  If so, insert
    -the import and unittest.main lines before the htest lines.
    +Add the following at the end of abc.py.  If an htest was added first,
    +insert the import and main lines before the htest lines.
     
     if __name__ == "__main__":
    -    import unittest
    -    unittest.main('idlelib.idle_test.test_', verbosity=2, exit=False)
    +    from unittest import main
    +    main('idlelib.idle_test.test_abc', verbosity=2, exit=False)
    +
    +The ', exit=False' is only needed if an htest follows.
     
     
     
     2. GUI Tests
     
     When run as part of the Python test suite, Idle GUI tests need to run
    -test.test_support.requires('gui') (test.support in 3.x).  A test is a GUI test
    -if it creates a Tk root or master object either directly or indirectly by
    -instantiating a tkinter or idle class.  For the benefit of test processes that
    -either have no graphical environment available or are not allowed to use it, GUI
    -tests must be 'guarded' by "requires('gui')" in a setUp function or method.
    -This will typically be setUpClass.
    -
    -To avoid interfering with other GUI tests, all GUI objects must be destroyed and
    -deleted by the end of the test.  The Tk root created in a setUpX function should
    -be destroyed in the corresponding tearDownX and the module or class attribute
    -deleted.  Others widgets should descend from the single root and the attributes
    -deleted BEFORE root is destroyed.  See https://bugs.python.org/issue20567.
    +test.support.requires('gui').  A test is a GUI test if it creates a
    +tkinter.Tk root or master object either directly or indirectly by
    +instantiating a tkinter or idle class.  GUI tests cannot run in test
    +processes that either have no graphical environment available or are not
    +allowed to use it.
    +
    +To guard a module consisting entirely of GUI tests, start with
    +
    +from test.support import requires
    +requires('gui')
    +
    +To guard a test class, put "requires('gui')" in its setUpClass function.
    +The template.py file does this.
    +
    +To avoid interfering with other GUI tests, all GUI objects must be
    +destroyed and deleted by the end of the test.  The Tk root created in a
    +setUpX function should be destroyed in the corresponding tearDownX and
    +the module or class attribute deleted.  Others widgets should descend
    +from the single root and the attributes deleted BEFORE root is
    +destroyed.  See https://bugs.python.org/issue20567.
     
         @classmethod
         def setUpClass(cls):
    @@ -65,48 +72,64 @@ deleted BEFORE root is destroyed.  See https://bugs.python.org/issue20567.
         @classmethod
         def tearDownClass(cls):
             del cls.text
    +        cls.root.update_idletasks()
             cls.root.destroy()
             del cls.root
     
    -WARNING: In 2.7, "requires('gui') MUST NOT be called at module scope.
    -See https://bugs.python.org/issue18910
    +The update_idletasks call is sometimes needed to prevent the following
    +warning either when running a test alone or as part of the test suite
    +(#27196).  It should not hurt if not needed.
    +
    +  can't invoke "event" command: application has been destroyed
    +  ...
    +  "ttk::ThemeChanged"
    +
    +If a test creates instance 'e' of EditorWindow, call 'e._close()' before
    +or as the first part of teardown.  The effect of omitting this depends
    +on the later shutdown.  Then enable the after_cancel loop in the
    +template.  This prevents messages like the following.
    +
    +bgerror failed to handle background error.
    +    Original error: invalid command name "106096696timer_event"
    +    Error in bgerror: can't invoke "tk" command: application has been destroyed
     
     Requires('gui') causes the test(s) it guards to be skipped if any of
     these conditions are met:
     
    - - The tests are being run by regrtest.py, and it was started without enabling
    -   the "gui" resource with the "-u" command line option.
    + - The tests are being run by regrtest.py, and it was started without
    +   enabling the "gui" resource with the "-u" command line option.
     
    - - The tests are being run on Windows by a service that is not allowed to
    -   interact with the graphical environment.
    + - The tests are being run on Windows by a service that is not allowed
    +   to interact with the graphical environment.
     
      - The tests are being run on Linux and X Windows is not available.
     
    - - The tests are being run on Mac OSX in a process that cannot make a window
    -   manager connection.
    + - The tests are being run on Mac OSX in a process that cannot make a
    +   window manager connection.
     
      - tkinter.Tk cannot be successfully instantiated for some reason.
     
      - test.support.use_resources has been set by something other than
        regrtest.py and does not contain "gui".
     
    -Tests of non-GUI operations should avoid creating tk widgets. Incidental uses of
    -tk variables and messageboxes can be replaced by the mock classes in
    -idle_test/mock_tk.py. The mock text handles some uses of the tk Text widget.
    +Tests of non-GUI operations should avoid creating tk widgets. Incidental
    +uses of tk variables and messageboxes can be replaced by the mock
    +classes in idle_test/mock_tk.py. The mock text handles some uses of the
    +tk Text widget.
     
     
     3. Running Unit Tests
     
     Assume that xyz.py and test_xyz.py both end with a unittest.main() call.
    -Running either from an Idle editor runs all tests in the test_xyz file with the
    -version of Python running Idle.  Test output appears in the Shell window.  The
    -'verbosity=2' option lists all test methods in the file, which is appropriate
    -when developing tests. The 'exit=False' option is needed in xyx.py files when an
    -htest follows.
    +Running either from an Idle editor runs all tests in the test_xyz file
    +with the version of Python running Idle.  Test output appears in the
    +Shell window.  The 'verbosity=2' option lists all test methods in the
    +file, which is appropriate when developing tests. The 'exit=False'
    +option is needed in xyx.py files when an htest follows.
     
     The following command lines also run all test methods, including
    -GUI tests, in test_xyz.py. (Both '-m idlelib' and '-m idlelib.idle' start
    -Idle and so cannot run tests.)
    +GUI tests, in test_xyz.py. (Both '-m idlelib' and '-m idlelib.idle'
    +start Idle and so cannot run tests.)
     
     python -m idlelib.xyz
     python -m idlelib.idle_test.test_xyz
    @@ -116,35 +139,100 @@ The following runs all idle_test/test_*.py tests interactively.
     >>> import unittest
     >>> unittest.main('idlelib.idle_test', verbosity=2)
     
    -The following run all Idle tests at a command line.  Option '-v' is the same as
    -'verbosity=2'.  (For 2.7, replace 'test' in the second line with
    -'test.regrtest'.)
    +The following run all Idle tests at a command line.  Option '-v' is the
    +same as 'verbosity=2'.
     
     python -m unittest -v idlelib.idle_test
     python -m test -v -ugui test_idle
     python -m test.test_idle
     
    -The idle tests are 'discovered' by idlelib.idle_test.__init__.load_tests,
    -which is also imported into test.test_idle. Normally, neither file should be
    -changed when working on individual test modules. The third command runs
    -unittest indirectly through regrtest. The same happens when the entire test
    -suite is run with 'python -m test'. So that command must work for buildbots
    -to stay green. Idle tests must not disturb the environment in a way that
    -makes other tests fail (issue 18081).
    -
    -To run an individual Testcase or test method, extend the dotted name given to
    -unittest on the command line.
    +The idle tests are 'discovered' by
    +idlelib.idle_test.__init__.load_tests, which is also imported into
    +test.test_idle. Normally, neither file should be changed when working on
    +individual test modules. The third command runs unittest indirectly
    +through regrtest. The same happens when the entire test suite is run
    +with 'python -m test'. So that command must work for buildbots to stay
    +green. Idle tests must not disturb the environment in a way that makes
    +other tests fail (issue 18081).
    +
    +To run an individual Testcase or test method, extend the dotted name
    +given to unittest on the command line or use the test -m option.  The
    +latter allows use of other regrtest options.  When using the latter,
    +all components of the pattern must be present, but any can be replaced
    +by '*'.
     
     python -m unittest -v idlelib.idle_test.test_xyz.Test_case.test_meth
    +python -m test -m idlelib.idle_test.text_xyz.Test_case.test_meth test_idle
    +
    +The test suite can be run in an IDLE user process from Shell.
    +>>> import test.autotest  # Issue 25588, 2017/10/13, 3.6.4, 3.7.0a2.
    +There are currently failures not usually present, and this does not
    +work when run from the editor.
     
     
     4. Human-mediated Tests
     
    -Human-mediated tests are widget tests that cannot be automated but need human
    -verification. They are contained in idlelib/idle_test/htest.py, which has
    -instructions.  (Some modules need an auxiliary function, identified with # htest
    -# on the header line.)  The set is about complete, though some tests need
    -improvement. To run all htests, run the htest file from an editor or from the
    -command line with:
    +Human-mediated tests are widget tests that cannot be automated but need
    +human verification. They are contained in idlelib/idle_test/htest.py,
    +which has instructions.  (Some modules need an auxiliary function,
    +identified with "# htest # on the header line.)  The set is about
    +complete, though some tests need improvement. To run all htests, run the
    +htest file from an editor or from the command line with:
     
     python -m idlelib.idle_test.htest
    +
    +
    +5. Test Coverage
    +
    +Install the coverage package into your Python 3.6 site-packages
    +directory.  (Its exact location depends on the OS).
    +> python3 -m pip install coverage
    +(On Windows, replace 'python3 with 'py -3.6' or perhaps just 'python'.)
    +
    +The problem with running coverage with repository python is that
    +coverage uses absolute imports for its submodules, hence it needs to be
    +in a directory in sys.path.  One solution: copy the package to the
    +directory containing the cpython repository.  Call it 'dev'.  Then run
    +coverage either directly or from a script in that directory so that
    +'dev' is prepended to sys.path.
    +
    +Either edit or add dev/.coveragerc so it looks something like this.
    +---
    +# .coveragerc sets coverage options.
    +[run]
    +branch = True
    +
    +[report]
    +# Regexes for lines to exclude from consideration
    +exclude_lines =
    +    # Don't complain if non-runnable code isn't run:
    +    if 0:
    +    if __name__ == .__main__.:
    +
    +    .*# htest #
    +    if not _utest:
    +    if _htest:
    +---
    +The additions for IDLE are 'branch = True', to test coverage both ways,
    +and the last three exclude lines, to exclude things peculiar to IDLE
    +that are not executed during tests.
    +
    +A script like the following cover.bat (for Windows) is very handy.
    +---
    +@echo off
    +rem Usage: cover filename [test_ suffix] # proper case required by coverage
    +rem filename without .py, 2nd parameter if test is not test_filename
    +setlocal
    +set py=f:\dev\3x\pcbuild\win32\python_d.exe
    +set src=idlelib.%1
    +if "%2" EQU "" set tst=f:/dev/3x/Lib/idlelib/idle_test/test_%1.py
    +if "%2" NEQ "" set tst=f:/dev/ex/Lib/idlelib/idle_test/test_%2.py
    +
    +%py% -m coverage run --pylib --source=%src% %tst%
    +%py% -m coverage report --show-missing
    +%py% -m coverage html
    +start htmlcov\3x_Lib_idlelib_%1_py.html
    +rem Above opens new report; htmlcov\index.html displays report index
    +---
    +The second parameter was added for tests of module x not named test_x.
    +(There were several before modules were renamed, now only one is left.)
    diff --git a/PythonLib/full/idlelib/idle_test/__init__.py b/PythonLib/full/idlelib/idle_test/__init__.py
    index 845c92d3..ad067b40 100644
    --- a/PythonLib/full/idlelib/idle_test/__init__.py
    +++ b/PythonLib/full/idlelib/idle_test/__init__.py
    @@ -1,6 +1,8 @@
     '''idlelib.idle_test is a private implementation of test.test_idle,
     which tests the IDLE application as part of the stdlib test suite.
     Run IDLE tests alone with "python -m test.test_idle".
    +Starting with Python 3.6, IDLE requires tcl/tk 8.5 or later.
    +
     This package and its contained modules are subject to change and
     any direct use is at your own risk.
     '''
    diff --git a/PythonLib/full/idlelib/idle_test/htest.py b/PythonLib/full/idlelib/idle_test/htest.py
    index 9e2ddd2c..1373b764 100644
    --- a/PythonLib/full/idlelib/idle_test/htest.py
    +++ b/PythonLib/full/idlelib/idle_test/htest.py
    @@ -8,11 +8,11 @@
     end the test.
     
     In a tested module, let X be a global name bound to a callable (class
    -or function) whose .__name__ attrubute is also X (the usual situation).
    +or function) whose .__name__ attribute is also X (the usual situation).
     The first parameter of X must be 'parent'.  When called, the parent
     argument will be the root window.  X must create a child Toplevel
     window (or subclass thereof).  The Toplevel may be a test widget or
    -dialog, in which case the callable is the corresonding class.  Or the
    +dialog, in which case the callable is the corresponding class.  Or the
     Toplevel may contain the widget to be tested or set up a context in
     which a test widget is invoked.  In this latter case, the callable is a
     wrapper function that sets up the Toplevel and other objects.  Wrapper
    @@ -59,34 +59,40 @@ def _wrapper(parent):  # htest #
     
     
     Modules and classes not being tested at the moment:
    -PyShell.PyShellEditorWindow
    -Debugger.Debugger
    -AutoCompleteWindow.AutoCompleteWindow
    -OutputWindow.OutputWindow (indirectly being tested with grep test)
    +pyshell.PyShellEditorWindow
    +debugger.Debugger
    +autocomplete_w.AutoCompleteWindow
    +outwin.OutputWindow (indirectly being tested with grep test)
     '''
     
    +import idlelib.pyshell  # Set Windows DPI awareness before Tk().
     from importlib import import_module
    -from idlelib.macosxSupport import _initializeTkVariantTests
    -import Tkinter as tk
    +import textwrap
    +import tkinter as tk
    +from tkinter.ttk import Scrollbar
    +tk.NoDefaultRoot()
     
     AboutDialog_spec = {
    -    'file': 'aboutDialog',
    -    'kwds': {'title': 'aboutDialog test',
    +    'file': 'help_about',
    +    'kwds': {'title': 'help_about test',
                  '_htest': True,
                  },
         'msg': "Test every button. Ensure Python, TK and IDLE versions "
                "are correctly displayed.\n [Close] to exit.",
         }
     
    +# TODO implement ^\; adding '' to function does not work.
     _calltip_window_spec = {
    -    'file': 'CallTipWindow',
    +    'file': 'calltip_w',
         'kwds': {},
         'msg': "Typing '(' should display a calltip.\n"
                "Typing ') should hide the calltip.\n"
    +           "So should moving cursor out of argument area.\n"
    +           "Force-open-calltip does not work here.\n"
         }
     
    -_class_browser_spec = {
    -    'file': 'ClassBrowser',
    +_module_browser_spec = {
    +    'file': 'browser',
         'kwds': {},
         'msg': "Inspect names of module, class(with superclass if "
                "applicable), methods and functions.\nToggle nested items.\n"
    @@ -95,7 +101,7 @@ def _wrapper(parent):  # htest #
         }
     
     _color_delegator_spec = {
    -    'file': 'ColorDelegator',
    +    'file': 'colorizer',
         'kwds': {},
         'msg': "The text is sample Python code.\n"
                "Ensure components like comments, keywords, builtins,\n"
    @@ -103,8 +109,18 @@ def _wrapper(parent):  # htest #
                "The default color scheme is in idlelib/config-highlight.def"
         }
     
    +CustomRun_spec = {
    +    'file': 'query',
    +    'kwds': {'title': 'Customize query.py Run',
    +             '_htest': True},
    +    'msg': "Enter with  or [Run].  Print valid entry to Shell\n"
    +           "Arguments are parsed into a list\n"
    +           "Mode is currently restart True or False\n"
    +           "Close dialog with valid entry, , [Cancel], [X]"
    +    }
    +
     ConfigDialog_spec = {
    -    'file': 'configDialog',
    +    'file': 'configdialog',
         'kwds': {'title': 'ConfigDialogTest',
                  '_htest': True,},
         'msg': "IDLE preferences dialog.\n"
    @@ -121,7 +137,7 @@ def _wrapper(parent):  # htest #
     
     # TODO Improve message
     _dyn_option_menu_spec = {
    -    'file': 'dynOptionMenuWidget',
    +    'file': 'dynoption',
         'kwds': {},
         'msg': "Select one of the many options in the 'old option set'.\n"
                "Click the button to change the option set.\n"
    @@ -130,64 +146,55 @@ def _wrapper(parent):  # htest #
     
     # TODO edit wrapper
     _editor_window_spec = {
    -   'file': 'EditorWindow',
    +   'file': 'editor',
         'kwds': {},
         'msg': "Test editor functions of interest.\n"
                "Best to close editor first."
         }
     
    -GetCfgSectionNameDialog_spec = {
    -    'file': 'configSectionNameDialog',
    -    'kwds': {'title':'Get Name',
    -             'message':'Enter something',
    -             'used_names': {'abc'},
    -             '_htest': True},
    -    'msg': "After the text entered with [Ok] is stripped, , "
    -           "'abc', or more that 30 chars are errors.\n"
    -           "Close 'Get Name' with a valid entry (printed to Shell), "
    -           "[Cancel], or [X]",
    -    }
    -
    -GetHelpSourceDialog_spec = {
    -    'file': 'configHelpSourceEdit',
    -    'kwds': {'title': 'Get helpsource',
    -             '_htest': True},
    -    'msg': "Enter menu item name and help file path\n "
    -           " and more than 30 chars are invalid menu item names.\n"
    -           ", file does not exist are invalid path items.\n"
    -           "Test for incomplete web address for help file path.\n"
    -           "A valid entry will be printed to shell with [0k].\n"
    -           "[Cancel] will print None to shell",
    -    }
    -
    -# Update once issue21519 is resolved.
     GetKeysDialog_spec = {
    -    'file': 'keybindingDialog',
    +    'file': 'config_key',
         'kwds': {'title': 'Test keybindings',
                  'action': 'find-again',
    -             'currentKeySequences': [''] ,
    +             'current_key_sequences': [['', '', '']],
                  '_htest': True,
                  },
         'msg': "Test for different key modifier sequences.\n"
                " is invalid.\n"
                "No modifier key is invalid.\n"
                "Shift key with [a-z],[0-9], function key, move key, tab, space "
    -           "is invalid.\nNo validitity checking if advanced key binding "
    +           "is invalid.\nNo validity checking if advanced key binding "
                "entry is used."
         }
     
     _grep_dialog_spec = {
    -    'file': 'GrepDialog',
    +    'file': 'grep',
         'kwds': {},
         'msg': "Click the 'Show GrepDialog' button.\n"
                "Test the various 'Find-in-files' functions.\n"
                "The results should be displayed in a new '*Output*' window.\n"
    -           "'Right-click'->'Goto file/line' anywhere in the search results "
    +           "'Right-click'->'Go to file/line' anywhere in the search results "
                "should open that file \nin a new EditorWindow."
         }
     
    +HelpSource_spec = {
    +    'file': 'query',
    +    'kwds': {'title': 'Help name and source',
    +             'menuitem': 'test',
    +             'filepath': __file__,
    +             'used_names': {'abc'},
    +             '_htest': True},
    +    'msg': "Enter menu item name and help file path\n"
    +           "'', > than 30 chars, and 'abc' are invalid menu item names.\n"
    +           "'' and file does not exist are invalid path items.\n"
    +           "Any url ('www...', 'http...') is accepted.\n"
    +           "Test Browse with and without path, as cannot unittest.\n"
    +           "[Ok] or  prints valid entry to shell\n"
    +           "[Cancel] or  prints None to shell"
    +    }
    +
     _io_binding_spec = {
    -    'file': 'IOBinding',
    +    'file': 'iomenu',
         'kwds': {},
         'msg': "Test the following bindings.\n"
                " to open file from dialog.\n"
    @@ -199,8 +206,28 @@ def _wrapper(parent):  # htest #
                "Check that changes were saved by opening the file elsewhere."
         }
     
    +_linenumbers_drag_scrolling_spec = {
    +    'file': 'sidebar',
    +    'kwds': {},
    +    'msg': textwrap.dedent("""\
    +        1. Click on the line numbers and drag down below the edge of the
    +        window, moving the mouse a bit and then leaving it there for a while.
    +        The text and line numbers should gradually scroll down, with the
    +        selection updated continuously.
    +
    +        2. With the lines still selected, click on a line number above the
    +        selected lines. Only the line whose number was clicked should be
    +        selected.
    +
    +        3. Repeat step #1, dragging to above the window. The text and line
    +        numbers should gradually scroll up, with the selection updated
    +        continuously.
    +
    +        4. Repeat step #2, clicking a line number below the selection."""),
    +    }
    +
     _multi_call_spec = {
    -    'file': 'MultiCall',
    +    'file': 'multicall',
         'kwds': {},
         'msg': "The following actions should trigger a print to console or IDLE"
                " Shell.\nEntering and leaving the text area, key entry, "
    @@ -210,14 +237,14 @@ def _wrapper(parent):  # htest #
         }
     
     _multistatus_bar_spec = {
    -    'file': 'MultiStatusBar',
    +    'file': 'statusbar',
         'kwds': {},
         'msg': "Ensure presence of multi-status bar below text area.\n"
                "Click 'Update Status' to change the multi-status text"
         }
     
     _object_browser_spec = {
    -    'file': 'ObjectBrowser',
    +    'file': 'debugobj',
         'kwds': {},
         'msg': "Double click on items upto the lowest level.\n"
                "Attributes of the objects and related information "
    @@ -225,7 +252,7 @@ def _wrapper(parent):  # htest #
         }
     
     _path_browser_spec = {
    -    'file': 'PathBrowser',
    +    'file': 'pathbrowser',
         'kwds': {},
         'msg': "Test for correct display of all paths in sys.path.\n"
                "Toggle nested items upto the lowest level.\n"
    @@ -234,7 +261,7 @@ def _wrapper(parent):  # htest #
         }
     
     _percolator_spec = {
    -    'file': 'Percolator',
    +    'file': 'percolator',
         'kwds': {},
         'msg': "There are two tracers which can be toggled using a checkbox.\n"
                "Toggling a tracer 'on' by checking it should print tracer "
    @@ -244,8 +271,20 @@ def _wrapper(parent):  # htest #
                "Test for actions like text entry, and removal."
         }
     
    +Query_spec = {
    +    'file': 'query',
    +    'kwds': {'title': 'Query',
    +             'message': 'Enter something',
    +             'text0': 'Go',
    +             '_htest': True},
    +    'msg': "Enter with  or [Ok].  Print valid entry to Shell\n"
    +           "Blank line, after stripping, is ignored\n"
    +           "Close dialog with valid entry, , [Cancel], [X]"
    +    }
    +
    +
     _replace_dialog_spec = {
    -    'file': 'ReplaceDialog',
    +    'file': 'replace',
         'kwds': {},
         'msg': "Click the 'Replace' button.\n"
                "Test various replace options in the 'Replace dialog'.\n"
    @@ -253,15 +292,22 @@ def _wrapper(parent):  # htest #
         }
     
     _search_dialog_spec = {
    -    'file': 'SearchDialog',
    +    'file': 'search',
         'kwds': {},
         'msg': "Click the 'Search' button.\n"
                "Test various search options in the 'Search dialog'.\n"
                "Click [Close] or [X] to close the 'Search Dialog'."
         }
     
    +_searchbase_spec = {
    +    'file': 'searchbase',
    +    'kwds': {},
    +    'msg': "Check the appearance of the base search dialog\n"
    +           "Its only action is to close."
    +    }
    +
     _scrolled_list_spec = {
    -    'file': 'ScrolledList',
    +    'file': 'scrolledlist',
         'kwds': {},
         'msg': "You should see a scrollable list of items\n"
                "Selecting (clicking) or double clicking an item "
    @@ -277,48 +323,29 @@ def _wrapper(parent):  # htest #
         }
     
     _stack_viewer_spec = {
    -    'file': 'StackViewer',
    +    'file': 'stackviewer',
         'kwds': {},
         'msg': "A stacktrace for a NameError exception.\n"
                "Expand 'idlelib ...' and ''.\n"
                "Check that exc_value, exc_tb, and exc_type are correct.\n"
         }
     
    -_tabbed_pages_spec = {
    -    'file': 'tabbedpages',
    -    'kwds': {},
    -    'msg': "Toggle between the two tabs 'foo' and 'bar'\n"
    -           "Add a tab by entering a suitable name for it.\n"
    -           "Remove an existing tab by entering its name.\n"
    -           "Remove all existing tabs.\n"
    -           " is an invalid add page and remove page name.\n"
    -    }
    -
    -TextViewer_spec = {
    -    'file': 'textView',
    -    'kwds': {'title': 'Test textView',
    -             'text':'The quick brown fox jumps over the lazy dog.\n'*35,
    -             '_htest': True},
    -    'msg': "Test for read-only property of text.\n"
    -           "Text is selectable. Window is scrollable.",
    -     }
    -
     _tooltip_spec = {
    -    'file': 'ToolTip',
    +    'file': 'tooltip',
         'kwds': {},
         'msg': "Place mouse cursor over both the buttons\n"
                "A tooltip should appear with some text."
         }
     
     _tree_widget_spec = {
    -    'file': 'TreeWidget',
    +    'file': 'tree',
         'kwds': {},
         'msg': "The canvas is scrollable.\n"
                "Click on folders upto to the lowest level."
         }
     
     _undo_delegator_spec = {
    -    'file': 'UndoDelegator',
    +    'file': 'undo',
         'kwds': {},
         'msg': "Click [Undo] to undo any action.\n"
                "Click [Redo] to redo any action.\n"
    @@ -326,8 +353,17 @@ def _wrapper(parent):  # htest #
                "by printing to the console or the IDLE shell.\n"
         }
     
    +ViewWindow_spec = {
    +    'file': 'textview',
    +    'kwds': {'title': 'Test textview',
    +             'contents': 'The quick brown fox jumps over the lazy dog.\n'*35,
    +             '_htest': True},
    +    'msg': "Test for read-only property of text.\n"
    +           "Select text, scroll window, close"
    +     }
    +
     _widget_redirector_spec = {
    -    'file': 'WidgetRedirector',
    +    'file': 'redirector',
         'kwds': {},
         'msg': "Every text insert should be printed to the console "
                "or the IDLE shell."
    @@ -337,14 +373,13 @@ def run(*tests):
         root = tk.Tk()
         root.title('IDLE htest')
         root.resizable(0, 0)
    -    _initializeTkVariantTests(root)
     
         # a scrollable Label like constant width text widget.
         frameLabel = tk.Frame(root, padx=10)
         frameLabel.pack()
         text = tk.Text(frameLabel, wrap='word')
         text.configure(bg=root.cget('bg'), relief='flat', height=4, width=70)
    -    scrollbar = tk.Scrollbar(frameLabel, command=text.yview)
    +    scrollbar = Scrollbar(frameLabel, command=text.yview)
         text.config(yscrollcommand=scrollbar.set)
         scrollbar.pack(side='right', fill='y', expand=False)
         text.pack(side='left', fill='both', expand=True)
    @@ -365,38 +400,45 @@ def run(*tests):
                     test = getattr(mod, test_name)
                     test_list.append((test_spec, test))
     
    -    test_name = [tk.StringVar('')]
    -    callable_object = [None]
    -    test_kwds = [None]
    +    test_name = tk.StringVar(root)
    +    callable_object = None
    +    test_kwds = None
     
    +    def next_test():
     
    -    def next():
    +        nonlocal test_name, callable_object, test_kwds
             if len(test_list) == 1:
                 next_button.pack_forget()
    -        test_spec, callable_object[0] = test_list.pop()
    -        test_kwds[0] = test_spec['kwds']
    -        test_kwds[0]['parent'] = root
    -        test_name[0].set('Test ' + test_spec['name'])
    +        test_spec, callable_object = test_list.pop()
    +        test_kwds = test_spec['kwds']
    +        test_kwds['parent'] = root
    +        test_name.set('Test ' + test_spec['name'])
     
             text.configure(state='normal') # enable text editing
             text.delete('1.0','end')
             text.insert("1.0",test_spec['msg'])
             text.configure(state='disabled') # preserve read-only property
     
    -    def run_test():
    -        widget = callable_object[0](**test_kwds[0])
    +    def run_test(_=None):
    +        widget = callable_object(**test_kwds)
             try:
                 print(widget.result)
             except AttributeError:
                 pass
     
    -    button = tk.Button(root, textvariable=test_name[0], command=run_test)
    +    def close(_=None):
    +        root.destroy()
    +
    +    button = tk.Button(root, textvariable=test_name,
    +                       default='active', command=run_test)
    +    next_button = tk.Button(root, text="Next", command=next_test)
         button.pack()
    -    next_button = tk.Button(root, text="Next", command=next)
         next_button.pack()
    +    next_button.focus_set()
    +    root.bind('', run_test)
    +    root.bind('', close)
     
    -    next()
    -
    +    next_test()
         root.mainloop()
     
     if __name__ == '__main__':
    diff --git a/PythonLib/full/idlelib/idle_test/mock_idle.py b/PythonLib/full/idlelib/idle_test/mock_idle.py
    index 7b09f836..71fa480c 100644
    --- a/PythonLib/full/idlelib/idle_test/mock_idle.py
    +++ b/PythonLib/full/idlelib/idle_test/mock_idle.py
    @@ -5,38 +5,44 @@
     
     from idlelib.idle_test.mock_tk import Text
     
    -class Func(object):
    -    '''Mock function captures args and returns result set by test.
    -
    -    Attributes:
    -    self.called - records call even if no args, kwds passed.
    -    self.result - set by init, returned by call.
    -    self.args - captures positional arguments.
    -    self.kwds - captures keyword arguments.
    -
    -    Most common use will probably be to mock methods.
    +class Func:
    +    '''Record call, capture args, return/raise result set by test.
    +
    +    When mock function is called, set or use attributes:
    +    self.called - increment call number even if no args, kwds passed.
    +    self.args - capture positional arguments.
    +    self.kwds - capture keyword arguments.
    +    self.result - return or raise value set in __init__.
    +    self.return_self - return self instead, to mock query class return.
    +
    +    Most common use will probably be to mock instance methods.
    +    Given class instance, can set and delete as instance attribute.
         Mock_tk.Var and Mbox_func are special variants of this.
         '''
    -    def __init__(self, result=None):
    -        self.called = False
    +    def __init__(self, result=None, return_self=False):
    +        self.called = 0
             self.result = result
    +        self.return_self = return_self
             self.args = None
             self.kwds = None
         def __call__(self, *args, **kwds):
    -        self.called = True
    +        self.called += 1
             self.args = args
             self.kwds = kwds
             if isinstance(self.result, BaseException):
                 raise self.result
    +        elif self.return_self:
    +            return self
             else:
                 return self.result
     
     
    -class Editor(object):
    -    '''Minimally imitate EditorWindow.EditorWindow class.
    +class Editor:
    +    '''Minimally imitate editor.EditorWindow class.
         '''
    -    def __init__(self, flist=None, filename=None, key=None, root=None):
    -        self.text = Text()
    +    def __init__(self, flist=None, filename=None, key=None, root=None,
    +                 text=None):  # Allow real Text with mock Editor.
    +        self.text = text or Text()
             self.undo = UndoDelegator()
     
         def get_selection_indices(self):
    @@ -45,8 +51,8 @@ def get_selection_indices(self):
             return first, last
     
     
    -class UndoDelegator(object):
    -    '''Minimally imitate UndoDelegator,UndoDelegator class.
    +class UndoDelegator:
    +    '''Minimally imitate undo.UndoDelegator class.
         '''
         # A real undo block is only needed for user interaction.
         def undo_block_start(*args):
    diff --git a/PythonLib/full/idlelib/idle_test/mock_tk.py b/PythonLib/full/idlelib/idle_test/mock_tk.py
    index 56ca8769..576f7d5d 100644
    --- a/PythonLib/full/idlelib/idle_test/mock_tk.py
    +++ b/PythonLib/full/idlelib/idle_test/mock_tk.py
    @@ -4,7 +4,7 @@
     typically required in spite of what the doc strings say.
     """
     
    -class Event(object):
    +class Event:
         '''Minimal mock with attributes for testing event handlers.
     
         This is not a gui object, but is used as an argument for callbacks
    @@ -22,7 +22,7 @@ def __init__(self, **kwds):
             "Create event with attributes needed for test"
             self.__dict__.update(kwds)
     
    -class Var(object):
    +class Var:
         "Use for String/Int/BooleanVar: incomplete"
         def __init__(self, master=None, value=None, name=None):
             self.master = master
    @@ -33,11 +33,11 @@ def set(self, value):
         def get(self):
             return self.value
     
    -class Mbox_func(object):
    +class Mbox_func:
         """Generic mock for messagebox functions, which all have the same signature.
     
         Instead of displaying a message box, the mock's call method saves the
    -    arguments as instance attributes, which test functions can then examime.
    +    arguments as instance attributes, which test functions can then examine.
         The test can set the result returned to ask function
         """
         def __init__(self, result=None):
    @@ -50,7 +50,7 @@ def __call__(self, title, message, *args, **kwds):
             self.kwds = kwds
             return self.result  # Set by tester for ask functions
     
    -class Mbox(object):
    +class Mbox:
         """Mock for tkinter.messagebox with an Mbox_func for each function.
     
         This module was 'tkMessageBox' in 2.x; hence the 'import as' in  3.x.
    @@ -87,7 +87,7 @@ def tearDownClass(cls):
     
     from _tkinter import TclError
     
    -class Text(object):
    +class Text:
         """A semi-functional non-gui replacement for tkinter.Text text editors.
     
         The mock's data model is that a text is a list of \n-terminated lines.
    @@ -116,7 +116,7 @@ def _decode(self, index, endflag=0):
             """Return a (line, char) tuple of int indexes into self.data.
     
             This implements .index without converting the result back to a string.
    -        The result is contrained by the number of lines and linelengths of
    +        The result is constrained by the number of lines and linelengths of
             self.data. For many indexes, the result is initially (1, 0).
     
             The input index may have any of several possible forms:
    @@ -133,7 +133,7 @@ def _decode(self, index, endflag=0):
             try:
                 index=index.lower()
             except AttributeError:
    -            raise TclError('bad text index "%s"' % index)
    +            raise TclError('bad text index "%s"' % index) from None
     
             lastline =  len(self.data) - 1  # same as number of text lines
             if index == 'insert':
    @@ -296,3 +296,8 @@ def see(self, index):
         def bind(sequence=None, func=None, add=None):
             "Bind to this widget at event sequence a call to function func."
             pass
    +
    +class Entry:
    +    "Mock for tkinter.Entry."
    +    def focus_set(self):
    +        pass
    diff --git a/PythonLib/full/idlelib/idle_test/template.py b/PythonLib/full/idlelib/idle_test/template.py
    new file mode 100644
    index 00000000..725a55b9
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/template.py
    @@ -0,0 +1,30 @@
    +"Test , coverage %."
    +
    +from idlelib import zzdummy
    +import unittest
    +from test.support import requires
    +from tkinter import Tk
    +
    +
    +class Test(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        requires('gui')
    +        cls.root = Tk()
    +        cls.root.withdraw()
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        cls.root.update_idletasks()
    +##        for id in cls.root.tk.call('after', 'info'):
    +##            cls.root.after_cancel(id)  # Need for EditorWindow.
    +        cls.root.destroy()
    +        del cls.root
    +
    +    def test_init(self):
    +        self.assertTrue(True)
    +
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_autocomplete.py b/PythonLib/full/idlelib/idle_test/test_autocomplete.py
    index 002751ef..2c478cd5 100644
    --- a/PythonLib/full/idlelib/idle_test/test_autocomplete.py
    +++ b/PythonLib/full/idlelib/idle_test/test_autocomplete.py
    @@ -1,15 +1,17 @@
    +"Test autocomplete, coverage 93%."
    +
     import unittest
    -from test.test_support import requires
    -from Tkinter import Tk, Text
    +from unittest.mock import Mock, patch
    +from test.support import requires
    +from tkinter import Tk, Text
    +import os
    +import __main__
     
    -import idlelib.AutoComplete as ac
    -import idlelib.AutoCompleteWindow as acw
    +import idlelib.autocomplete as ac
    +import idlelib.autocomplete_w as acw
     from idlelib.idle_test.mock_idle import Func
     from idlelib.idle_test.mock_tk import Event
     
    -class AutoCompleteWindow:
    -    def complete():
    -        return
     
     class DummyEditwin:
         def __init__(self, root, text):
    @@ -17,7 +19,7 @@ def __init__(self, root, text):
             self.text = text
             self.indentwidth = 8
             self.tabwidth = 8
    -        self.context_use_ps1 = True
    +        self.prompt_last_line = '>>>'  # Currently not used by autocomplete.
     
     
     class AutoCompleteTest(unittest.TestCase):
    @@ -26,114 +28,269 @@ class AutoCompleteTest(unittest.TestCase):
         def setUpClass(cls):
             requires('gui')
             cls.root = Tk()
    +        cls.root.withdraw()
             cls.text = Text(cls.root)
             cls.editor = DummyEditwin(cls.root, cls.text)
     
         @classmethod
         def tearDownClass(cls):
             del cls.editor, cls.text
    +        cls.root.update_idletasks()
             cls.root.destroy()
             del cls.root
     
         def setUp(self):
    -        self.editor.text.delete('1.0', 'end')
    +        self.text.delete('1.0', 'end')
             self.autocomplete = ac.AutoComplete(self.editor)
     
         def test_init(self):
             self.assertEqual(self.autocomplete.editwin, self.editor)
    +        self.assertEqual(self.autocomplete.text, self.text)
     
         def test_make_autocomplete_window(self):
             testwin = self.autocomplete._make_autocomplete_window()
             self.assertIsInstance(testwin, acw.AutoCompleteWindow)
     
         def test_remove_autocomplete_window(self):
    -        self.autocomplete.autocompletewindow = (
    -            self.autocomplete._make_autocomplete_window())
    -        self.autocomplete._remove_autocomplete_window()
    -        self.assertIsNone(self.autocomplete.autocompletewindow)
    +        acp = self.autocomplete
    +        acp.autocompletewindow = m = Mock()
    +        acp._remove_autocomplete_window()
    +        m.hide_window.assert_called_once()
    +        self.assertIsNone(acp.autocompletewindow)
     
         def test_force_open_completions_event(self):
    -        # Test that force_open_completions_event calls _open_completions
    -        o_cs = Func()
    -        self.autocomplete.open_completions = o_cs
    -        self.autocomplete.force_open_completions_event('event')
    -        self.assertEqual(o_cs.args, (True, False, True))
    +        # Call _open_completions and break.
    +        acp = self.autocomplete
    +        open_c = Func()
    +        acp.open_completions = open_c
    +        self.assertEqual(acp.force_open_completions_event('event'), 'break')
    +        self.assertEqual(open_c.args[0], ac.FORCE)
     
    -    def test_try_open_completions_event(self):
    +    def test_autocomplete_event(self):
             Equal = self.assertEqual
    -        autocomplete = self.autocomplete
    -        trycompletions = self.autocomplete.try_open_completions_event
    -        o_c_l = Func()
    -        autocomplete._open_completions_later = o_c_l
    +        acp = self.autocomplete
     
    -        # _open_completions_later should not be called with no text in editor
    -        trycompletions('event')
    -        Equal(o_c_l.args, None)
    +        # Result of autocomplete event: If modified tab, None.
    +        ev = Event(mc_state=True)
    +        self.assertIsNone(acp.autocomplete_event(ev))
    +        del ev.mc_state
     
    -        # _open_completions_later should be called with COMPLETE_ATTRIBUTES (1)
    +        # If tab after whitespace, None.
    +        self.text.insert('1.0', '        """Docstring.\n    ')
    +        self.assertIsNone(acp.autocomplete_event(ev))
    +        self.text.delete('1.0', 'end')
    +
    +        # If active autocomplete window, complete() and 'break'.
             self.text.insert('1.0', 're.')
    -        trycompletions('event')
    -        Equal(o_c_l.args, (False, False, False, 1))
    +        acp.autocompletewindow = mock = Mock()
    +        mock.is_active = Mock(return_value=True)
    +        Equal(acp.autocomplete_event(ev), 'break')
    +        mock.complete.assert_called_once()
    +        acp.autocompletewindow = None
     
    -        # _open_completions_later should be called with COMPLETE_FILES (2)
    -        self.text.delete('1.0', 'end')
    -        self.text.insert('1.0', '"./Lib/')
    -        trycompletions('event')
    -        Equal(o_c_l.args, (False, False, False, 2))
    +        # If no active autocomplete window, open_completions(), None/break.
    +        open_c = Func(result=False)
    +        acp.open_completions = open_c
    +        Equal(acp.autocomplete_event(ev), None)
    +        Equal(open_c.args[0], ac.TAB)
    +        open_c.result = True
    +        Equal(acp.autocomplete_event(ev), 'break')
    +        Equal(open_c.args[0], ac.TAB)
     
    -    def test_autocomplete_event(self):
    +    def test_try_open_completions_event(self):
             Equal = self.assertEqual
    -        autocomplete = self.autocomplete
    +        text = self.text
    +        acp = self.autocomplete
    +        trycompletions = acp.try_open_completions_event
    +        after = Func(result='after1')
    +        acp.text.after = after
     
    -        # Test that the autocomplete event is ignored if user is pressing a
    -        # modifier key in addition to the tab key
    -        ev = Event(mc_state=True)
    -        self.assertIsNone(autocomplete.autocomplete_event(ev))
    -        del ev.mc_state
    +        # If no text or trigger, after not called.
    +        trycompletions()
    +        Equal(after.called, 0)
    +        text.insert('1.0', 're')
    +        trycompletions()
    +        Equal(after.called, 0)
     
    -        # If autocomplete window is open, complete() method is called
    -        self.text.insert('1.0', 're.')
    -        # This must call autocomplete._make_autocomplete_window()
    -        Equal(self.autocomplete.autocomplete_event(ev), 'break')
    -
    -        # If autocomplete window is not active or does not exist,
    -        # open_completions is called. Return depends on its return.
    -        autocomplete._remove_autocomplete_window()
    -        o_cs = Func()  # .result = None
    -        autocomplete.open_completions = o_cs
    -        Equal(self.autocomplete.autocomplete_event(ev), None)
    -        Equal(o_cs.args, (False, True, True))
    -        o_cs.result = True
    -        Equal(self.autocomplete.autocomplete_event(ev), 'break')
    -        Equal(o_cs.args, (False, True, True))
    -
    -    def test_open_completions_later(self):
    -        # Test that autocomplete._delayed_completion_id is set
    -        pass
    +        # Attribute needed, no existing callback.
    +        text.insert('insert', ' re.')
    +        acp._delayed_completion_id = None
    +        trycompletions()
    +        Equal(acp._delayed_completion_index, text.index('insert'))
    +        Equal(after.args,
    +              (acp.popupwait, acp._delayed_open_completions, ac.TRY_A))
    +        cb1 = acp._delayed_completion_id
    +        Equal(cb1, 'after1')
    +
    +        # File needed, existing callback cancelled.
    +        text.insert('insert', ' "./Lib/')
    +        after.result = 'after2'
    +        cancel = Func()
    +        acp.text.after_cancel = cancel
    +        trycompletions()
    +        Equal(acp._delayed_completion_index, text.index('insert'))
    +        Equal(cancel.args, (cb1,))
    +        Equal(after.args,
    +              (acp.popupwait, acp._delayed_open_completions, ac.TRY_F))
    +        Equal(acp._delayed_completion_id, 'after2')
     
         def test_delayed_open_completions(self):
    -        # Test that autocomplete._delayed_completion_id set to None and that
    -        # open_completions only called if insertion index is the same as
    -        # _delayed_completion_index
    -        pass
    +        Equal = self.assertEqual
    +        acp = self.autocomplete
    +        open_c = Func()
    +        acp.open_completions = open_c
    +        self.text.insert('1.0', '"dict.')
    +
    +        # Set autocomplete._delayed_completion_id to None.
    +        # Text index changed, don't call open_completions.
    +        acp._delayed_completion_id = 'after'
    +        acp._delayed_completion_index = self.text.index('insert+1c')
    +        acp._delayed_open_completions('dummy')
    +        self.assertIsNone(acp._delayed_completion_id)
    +        Equal(open_c.called, 0)
    +
    +        # Text index unchanged, call open_completions.
    +        acp._delayed_completion_index = self.text.index('insert')
    +        acp._delayed_open_completions((1, 2, 3, ac.FILES))
    +        self.assertEqual(open_c.args[0], (1, 2, 3, ac.FILES))
    +
    +    def test_oc_cancel_comment(self):
    +        none = self.assertIsNone
    +        acp = self.autocomplete
    +
    +        # Comment is in neither code or string.
    +        acp._delayed_completion_id = 'after'
    +        after = Func(result='after')
    +        acp.text.after_cancel = after
    +        self.text.insert(1.0, '# comment')
    +        none(acp.open_completions(ac.TAB))  # From 'else' after 'elif'.
    +        none(acp._delayed_completion_id)
    +
    +    def test_oc_no_list(self):
    +        acp = self.autocomplete
    +        fetch = Func(result=([],[]))
    +        acp.fetch_completions = fetch
    +        self.text.insert('1.0', 'object')
    +        self.assertIsNone(acp.open_completions(ac.TAB))
    +        self.text.insert('insert', '.')
    +        self.assertIsNone(acp.open_completions(ac.TAB))
    +        self.assertEqual(fetch.called, 2)
    +
    +
    +    def test_open_completions_none(self):
    +        # Test other two None returns.
    +        none = self.assertIsNone
    +        acp = self.autocomplete
    +
    +        # No object for attributes or need call not allowed.
    +        self.text.insert(1.0, '.')
    +        none(acp.open_completions(ac.TAB))
    +        self.text.insert('insert', ' int().')
    +        none(acp.open_completions(ac.TAB))
    +
    +        # Blank or quote trigger 'if complete ...'.
    +        self.text.delete(1.0, 'end')
    +        self.assertFalse(acp.open_completions(ac.TAB))
    +        self.text.insert('1.0', '"')
    +        self.assertFalse(acp.open_completions(ac.TAB))
    +        self.text.delete('1.0', 'end')
    +
    +    class dummy_acw():
    +        __init__ = Func()
    +        show_window = Func(result=False)
    +        hide_window = Func()
     
         def test_open_completions(self):
    -        # Test completions of files and attributes as well as non-completion
    -        # of errors
    -        pass
    +        # Test completions of files and attributes.
    +        acp = self.autocomplete
    +        fetch = Func(result=(['tem'],['tem', '_tem']))
    +        acp.fetch_completions = fetch
    +        def make_acw(): return self.dummy_acw()
    +        acp._make_autocomplete_window = make_acw
    +
    +        self.text.insert('1.0', 'int.')
    +        acp.open_completions(ac.TAB)
    +        self.assertIsInstance(acp.autocompletewindow, self.dummy_acw)
    +        self.text.delete('1.0', 'end')
    +
    +        # Test files.
    +        self.text.insert('1.0', '"t')
    +        self.assertTrue(acp.open_completions(ac.TAB))
    +        self.text.delete('1.0', 'end')
     
         def test_fetch_completions(self):
             # Test that fetch_completions returns 2 lists:
             # For attribute completion, a large list containing all variables, and
             # a small list containing non-private variables.
             # For file completion, a large list containing all files in the path,
    -        # and a small list containing files that do not start with '.'
    -        pass
    +        # and a small list containing files that do not start with '.'.
    +        acp = self.autocomplete
    +        small, large = acp.fetch_completions(
    +                '', ac.ATTRS)
    +        if __main__.__file__ != ac.__file__:
    +            self.assertNotIn('AutoComplete', small)  # See issue 36405.
    +
    +        # Test attributes
    +        s, b = acp.fetch_completions('', ac.ATTRS)
    +        self.assertLess(len(small), len(large))
    +        self.assertTrue(all(filter(lambda x: x.startswith('_'), s)))
    +        self.assertTrue(any(filter(lambda x: x.startswith('_'), b)))
    +
    +        # Test smalll should respect to __all__.
    +        with patch.dict('__main__.__dict__', {'__all__': ['a', 'b']}):
    +            s, b = acp.fetch_completions('', ac.ATTRS)
    +            self.assertEqual(s, ['a', 'b'])
    +            self.assertIn('__name__', b)    # From __main__.__dict__
    +            self.assertIn('sum', b)         # From __main__.__builtins__.__dict__
    +
    +        # Test attributes with name entity.
    +        mock = Mock()
    +        mock._private = Mock()
    +        with patch.dict('__main__.__dict__', {'foo': mock}):
    +            s, b = acp.fetch_completions('foo', ac.ATTRS)
    +            self.assertNotIn('_private', s)
    +            self.assertIn('_private', b)
    +            self.assertEqual(s, [i for i in sorted(dir(mock)) if i[:1] != '_'])
    +            self.assertEqual(b, sorted(dir(mock)))
    +
    +        # Test files
    +        def _listdir(path):
    +            # This will be patch and used in fetch_completions.
    +            if path == '.':
    +                return ['foo', 'bar', '.hidden']
    +            return ['monty', 'python', '.hidden']
    +
    +        with patch.object(os, 'listdir', _listdir):
    +            s, b = acp.fetch_completions('', ac.FILES)
    +            self.assertEqual(s, ['bar', 'foo'])
    +            self.assertEqual(b, ['.hidden', 'bar', 'foo'])
    +
    +            s, b = acp.fetch_completions('~', ac.FILES)
    +            self.assertEqual(s, ['monty', 'python'])
    +            self.assertEqual(b, ['.hidden', 'monty', 'python'])
     
         def test_get_entity(self):
             # Test that a name is in the namespace of sys.modules and
    -        # __main__.__dict__
    -        pass
    +        # __main__.__dict__.
    +        acp = self.autocomplete
    +        Equal = self.assertEqual
    +
    +        Equal(acp.get_entity('int'), int)
    +
    +        # Test name from sys.modules.
    +        mock = Mock()
    +        with patch.dict('sys.modules', {'tempfile': mock}):
    +            Equal(acp.get_entity('tempfile'), mock)
    +
    +        # Test name from __main__.__dict__.
    +        di = {'foo': 10, 'bar': 20}
    +        with patch.dict('__main__.__dict__', {'d': di}):
    +            Equal(acp.get_entity('d'), di)
    +
    +        # Test name not in namespace.
    +        with patch.dict('__main__.__dict__', {}):
    +            with self.assertRaises(NameError):
    +                acp.get_entity('not_exist')
     
     
     if __name__ == '__main__':
    diff --git a/PythonLib/full/idlelib/idle_test/test_autocomplete_w.py b/PythonLib/full/idlelib/idle_test/test_autocomplete_w.py
    new file mode 100644
    index 00000000..b1bdc6c7
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_autocomplete_w.py
    @@ -0,0 +1,32 @@
    +"Test autocomplete_w, coverage 11%."
    +
    +import unittest
    +from test.support import requires
    +from tkinter import Tk, Text
    +
    +import idlelib.autocomplete_w as acw
    +
    +
    +class AutoCompleteWindowTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        requires('gui')
    +        cls.root = Tk()
    +        cls.root.withdraw()
    +        cls.text = Text(cls.root)
    +        cls.acw = acw.AutoCompleteWindow(cls.text)
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        del cls.text, cls.acw
    +        cls.root.update_idletasks()
    +        cls.root.destroy()
    +        del cls.root
    +
    +    def test_init(self):
    +        self.assertEqual(self.acw.widget, self.text)
    +
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_autoexpand.py b/PythonLib/full/idlelib/idle_test/test_autoexpand.py
    index 6be4fbf8..e734a8be 100644
    --- a/PythonLib/full/idlelib/idle_test/test_autoexpand.py
    +++ b/PythonLib/full/idlelib/idle_test/test_autoexpand.py
    @@ -1,12 +1,12 @@
    -"""Unit tests for idlelib.AutoExpand"""
    +"Test autoexpand, coverage 100%."
    +
    +from idlelib.autoexpand import AutoExpand
     import unittest
    -from test.test_support import requires
    -from Tkinter import Text, Tk
    -#from idlelib.idle_test.mock_tk import Text
    -from idlelib.AutoExpand import AutoExpand
    +from test.support import requires
    +from tkinter import Text, Tk
     
     
    -class Dummy_Editwin:
    +class DummyEditwin:
         # AutoExpand.__init__ only needs .text
         def __init__(self, text):
             self.text = text
    @@ -15,13 +15,26 @@ class AutoExpandTest(unittest.TestCase):
     
         @classmethod
         def setUpClass(cls):
    -        if 'Tkinter' in str(Text):
    -            requires('gui')
    -            cls.tk = Tk()
    -            cls.text = Text(cls.tk)
    -        else:
    -            cls.text = Text()
    -        cls.auto_expand = AutoExpand(Dummy_Editwin(cls.text))
    +        requires('gui')
    +        cls.tk = Tk()
    +        cls.text = Text(cls.tk)
    +        cls.auto_expand = AutoExpand(DummyEditwin(cls.text))
    +        cls.auto_expand.bell = lambda: None
    +
    +# If mock_tk.Text._decode understood indexes 'insert' with suffixed 'linestart',
    +# 'wordstart', and 'lineend', used by autoexpand, we could use the following
    +# to run these test on non-gui machines (but check bell).
    +##        try:
    +##            requires('gui')
    +##            #raise ResourceDenied()  # Uncomment to test mock.
    +##        except ResourceDenied:
    +##            from idlelib.idle_test.mock_tk import Text
    +##            cls.text = Text()
    +##            cls.text.bell = lambda: None
    +##        else:
    +##            from tkinter import Tk, Text
    +##            cls.tk = Tk()
    +##            cls.text = Text(cls.tk)
     
         @classmethod
         def tearDownClass(cls):
    @@ -78,7 +91,7 @@ def test_before_only(self):
             equal(previous(), 'a')
     
         def test_after_only(self):
    -        # Also add punctuation 'noise' that shoud be ignored.
    +        # Also add punctuation 'noise' that should be ignored.
             text = self.text
             previous = self.auto_expand.getprevword
             expand = self.auto_expand.expand_word_event
    @@ -137,5 +150,6 @@ def test_other_expand_cases(self):
             new_state = self.auto_expand.state
             self.assertNotEqual(initial_state, new_state)
     
    +
     if __name__ == '__main__':
         unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_browser.py b/PythonLib/full/idlelib/idle_test/test_browser.py
    new file mode 100644
    index 00000000..25d6dc66
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_browser.py
    @@ -0,0 +1,248 @@
    +"Test browser, coverage 90%."
    +
    +from idlelib import browser
    +from test.support import requires
    +import unittest
    +from unittest import mock
    +from idlelib.idle_test.mock_idle import Func
    +
    +from collections import deque
    +import os.path
    +import pyclbr
    +from tkinter import Tk
    +
    +from idlelib.tree import TreeNode
    +
    +
    +class ModuleBrowserTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        requires('gui')
    +        cls.root = Tk()
    +        cls.root.withdraw()
    +        cls.mb = browser.ModuleBrowser(cls.root, __file__, _utest=True)
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        cls.mb.close()
    +        cls.root.update_idletasks()
    +        cls.root.destroy()
    +        del cls.root, cls.mb
    +
    +    def test_init(self):
    +        mb = self.mb
    +        eq = self.assertEqual
    +        eq(mb.path, __file__)
    +        eq(pyclbr._modules, {})
    +        self.assertIsInstance(mb.node, TreeNode)
    +        self.assertIsNotNone(browser.file_open)
    +
    +    def test_settitle(self):
    +        mb = self.mb
    +        self.assertIn(os.path.basename(__file__), mb.top.title())
    +        self.assertEqual(mb.top.iconname(), 'Module Browser')
    +
    +    def test_rootnode(self):
    +        mb = self.mb
    +        rn = mb.rootnode()
    +        self.assertIsInstance(rn, browser.ModuleBrowserTreeItem)
    +
    +    def test_close(self):
    +        mb = self.mb
    +        mb.top.destroy = Func()
    +        mb.node.destroy = Func()
    +        mb.close()
    +        self.assertTrue(mb.top.destroy.called)
    +        self.assertTrue(mb.node.destroy.called)
    +        del mb.top.destroy, mb.node.destroy
    +
    +
    +# Nested tree same as in test_pyclbr.py except for supers on C0. C1.
    +mb = pyclbr
    +module, fname = 'test', 'test.py'
    +C0 = mb.Class(module, 'C0', ['base'], fname, 1)
    +F1 = mb._nest_function(C0, 'F1', 3)
    +C1 = mb._nest_class(C0, 'C1', 6, [''])
    +C2 = mb._nest_class(C1, 'C2', 7)
    +F3 = mb._nest_function(C2, 'F3', 9)
    +f0 = mb.Function(module, 'f0', fname, 11)
    +f1 = mb._nest_function(f0, 'f1', 12)
    +f2 = mb._nest_function(f1, 'f2', 13)
    +c1 = mb._nest_class(f0, 'c1', 15)
    +mock_pyclbr_tree = {'C0': C0, 'f0': f0}
    +
    +# Adjust C0.name, C1.name so tests do not depend on order.
    +browser.transform_children(mock_pyclbr_tree, 'test')  # C0(base)
    +browser.transform_children(C0.children)  # C1()
    +
    +# The class below checks that the calls above are correct
    +# and that duplicate calls have no effect.
    +
    +
    +class TransformChildrenTest(unittest.TestCase):
    +
    +    def test_transform_module_children(self):
    +        eq = self.assertEqual
    +        transform = browser.transform_children
    +        # Parameter matches tree module.
    +        tcl = list(transform(mock_pyclbr_tree, 'test'))
    +        eq(tcl, [C0, f0])
    +        eq(tcl[0].name, 'C0(base)')
    +        eq(tcl[1].name, 'f0')
    +        # Check that second call does not change suffix.
    +        tcl = list(transform(mock_pyclbr_tree, 'test'))
    +        eq(tcl[0].name, 'C0(base)')
    +        # Nothing to traverse if parameter name isn't same as tree module.
    +        tcl = list(transform(mock_pyclbr_tree, 'different name'))
    +        eq(tcl, [])
    +
    +    def test_transform_node_children(self):
    +        eq = self.assertEqual
    +        transform = browser.transform_children
    +        # Class with two children, one name altered.
    +        tcl = list(transform(C0.children))
    +        eq(tcl, [F1, C1])
    +        eq(tcl[0].name, 'F1')
    +        eq(tcl[1].name, 'C1()')
    +        tcl = list(transform(C0.children))
    +        eq(tcl[1].name, 'C1()')
    +        # Function with two children.
    +        eq(list(transform(f0.children)), [f1, c1])
    +
    +
    +class ModuleBrowserTreeItemTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        cls.mbt = browser.ModuleBrowserTreeItem(fname)
    +
    +    def test_init(self):
    +        self.assertEqual(self.mbt.file, fname)
    +
    +    def test_gettext(self):
    +        self.assertEqual(self.mbt.GetText(), fname)
    +
    +    def test_geticonname(self):
    +        self.assertEqual(self.mbt.GetIconName(), 'python')
    +
    +    def test_isexpandable(self):
    +        self.assertTrue(self.mbt.IsExpandable())
    +
    +    def test_listchildren(self):
    +        save_rex = browser.pyclbr.readmodule_ex
    +        save_tc = browser.transform_children
    +        browser.pyclbr.readmodule_ex = Func(result=mock_pyclbr_tree)
    +        browser.transform_children = Func(result=[f0, C0])
    +        try:
    +            self.assertEqual(self.mbt.listchildren(), [f0, C0])
    +        finally:
    +            browser.pyclbr.readmodule_ex = save_rex
    +            browser.transform_children = save_tc
    +
    +    def test_getsublist(self):
    +        mbt = self.mbt
    +        mbt.listchildren = Func(result=[f0, C0])
    +        sub0, sub1 = mbt.GetSubList()
    +        del mbt.listchildren
    +        self.assertIsInstance(sub0, browser.ChildBrowserTreeItem)
    +        self.assertIsInstance(sub1, browser.ChildBrowserTreeItem)
    +        self.assertEqual(sub0.name, 'f0')
    +        self.assertEqual(sub1.name, 'C0(base)')
    +
    +    @mock.patch('idlelib.browser.file_open')
    +    def test_ondoubleclick(self, fopen):
    +        mbt = self.mbt
    +
    +        with mock.patch('os.path.exists', return_value=False):
    +            mbt.OnDoubleClick()
    +            fopen.assert_not_called()
    +
    +        with mock.patch('os.path.exists', return_value=True):
    +            mbt.OnDoubleClick()
    +            fopen.assert_called()
    +            fopen.called_with(fname)
    +
    +
    +class ChildBrowserTreeItemTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        CBT = browser.ChildBrowserTreeItem
    +        cls.cbt_f1 = CBT(f1)
    +        cls.cbt_C1 = CBT(C1)
    +        cls.cbt_F1 = CBT(F1)
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        del cls.cbt_C1, cls.cbt_f1, cls.cbt_F1
    +
    +    def test_init(self):
    +        eq = self.assertEqual
    +        eq(self.cbt_C1.name, 'C1()')
    +        self.assertFalse(self.cbt_C1.isfunction)
    +        eq(self.cbt_f1.name, 'f1')
    +        self.assertTrue(self.cbt_f1.isfunction)
    +
    +    def test_gettext(self):
    +        self.assertEqual(self.cbt_C1.GetText(), 'class C1()')
    +        self.assertEqual(self.cbt_f1.GetText(), 'def f1(...)')
    +
    +    def test_geticonname(self):
    +        self.assertEqual(self.cbt_C1.GetIconName(), 'folder')
    +        self.assertEqual(self.cbt_f1.GetIconName(), 'python')
    +
    +    def test_isexpandable(self):
    +        self.assertTrue(self.cbt_C1.IsExpandable())
    +        self.assertTrue(self.cbt_f1.IsExpandable())
    +        self.assertFalse(self.cbt_F1.IsExpandable())
    +
    +    def test_getsublist(self):
    +        eq = self.assertEqual
    +        CBT = browser.ChildBrowserTreeItem
    +
    +        f1sublist = self.cbt_f1.GetSubList()
    +        self.assertIsInstance(f1sublist[0], CBT)
    +        eq(len(f1sublist), 1)
    +        eq(f1sublist[0].name, 'f2')
    +
    +        eq(self.cbt_F1.GetSubList(), [])
    +
    +    @mock.patch('idlelib.browser.file_open')
    +    def test_ondoubleclick(self, fopen):
    +        goto = fopen.return_value.gotoline = mock.Mock()
    +        self.cbt_F1.OnDoubleClick()
    +        fopen.assert_called()
    +        goto.assert_called()
    +        goto.assert_called_with(self.cbt_F1.obj.lineno)
    +        # Failure test would have to raise OSError or AttributeError.
    +
    +
    +class NestedChildrenTest(unittest.TestCase):
    +    "Test that all the nodes in a nested tree are added to the BrowserTree."
    +
    +    def test_nested(self):
    +        queue = deque()
    +        actual_names = []
    +        # The tree items are processed in breadth first order.
    +        # Verify that processing each sublist hits every node and
    +        # in the right order.
    +        expected_names = ['f0', 'C0(base)',
    +                          'f1', 'c1', 'F1', 'C1()',
    +                          'f2', 'C2',
    +                          'F3']
    +        CBT = browser.ChildBrowserTreeItem
    +        queue.extend((CBT(f0), CBT(C0)))
    +        while queue:
    +            cb = queue.popleft()
    +            sublist = cb.GetSubList()
    +            queue.extend(sublist)
    +            self.assertIn(cb.name, cb.GetText())
    +            self.assertIn(cb.GetIconName(), ('python', 'folder'))
    +            self.assertIs(cb.IsExpandable(), sublist != [])
    +            actual_names.append(cb.name)
    +        self.assertEqual(actual_names, expected_names)
    +
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_calltip.py b/PythonLib/full/idlelib/idle_test/test_calltip.py
    new file mode 100644
    index 00000000..886959b1
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_calltip.py
    @@ -0,0 +1,253 @@
    +"Test calltip, coverage 60%"
    +
    +from idlelib import calltip
    +import unittest
    +import textwrap
    +import types
    +import re
    +
    +
    +# Test Class TC is used in multiple get_argspec test methods
    +class TC():
    +    'doc'
    +    tip = "(ai=None, *b)"
    +    def __init__(self, ai=None, *b): 'doc'
    +    __init__.tip = "(self, ai=None, *b)"
    +    def t1(self): 'doc'
    +    t1.tip = "(self)"
    +    def t2(self, ai, b=None): 'doc'
    +    t2.tip = "(self, ai, b=None)"
    +    def t3(self, ai, *args): 'doc'
    +    t3.tip = "(self, ai, *args)"
    +    def t4(self, *args): 'doc'
    +    t4.tip = "(self, *args)"
    +    def t5(self, ai, b=None, *args, **kw): 'doc'
    +    t5.tip = "(self, ai, b=None, *args, **kw)"
    +    def t6(no, self): 'doc'
    +    t6.tip = "(no, self)"
    +    def __call__(self, ci): 'doc'
    +    __call__.tip = "(self, ci)"
    +    def nd(self): pass  # No doc.
    +    # attaching .tip to wrapped methods does not work
    +    @classmethod
    +    def cm(cls, a): 'doc'
    +    @staticmethod
    +    def sm(b): 'doc'
    +
    +
    +tc = TC()
    +default_tip = calltip._default_callable_argspec
    +get_spec = calltip.get_argspec
    +
    +
    +class Get_argspecTest(unittest.TestCase):
    +    # The get_spec function must return a string, even if blank.
    +    # Test a variety of objects to be sure that none cause it to raise
    +    # (quite aside from getting as correct an answer as possible).
    +    # The tests of builtins may break if inspect or the docstrings change,
    +    # but a red buildbot is better than a user crash (as has happened).
    +    # For a simple mismatch, change the expected output to the actual.
    +
    +    def test_builtins(self):
    +
    +        def tiptest(obj, out):
    +            self.assertEqual(get_spec(obj), out)
    +
    +        # Python class that inherits builtin methods
    +        class List(list): "List() doc"
    +
    +        # Simulate builtin with no docstring for default tip test
    +        class SB:  __call__ = None
    +
    +        if List.__doc__ is not None:
    +            tiptest(List,
    +                    f'(iterable=(), /){calltip._argument_positional}'
    +                    f'\n{List.__doc__}')
    +        tiptest(list.__new__,
    +              '(*args, **kwargs)\n'
    +              'Create and return a new object.  '
    +              'See help(type) for accurate signature.')
    +        tiptest(list.__init__,
    +              '(self, /, *args, **kwargs)'
    +              + calltip._argument_positional + '\n' +
    +              'Initialize self.  See help(type(self)) for accurate signature.')
    +        append_doc = (calltip._argument_positional
    +                      + "\nAppend object to the end of the list.")
    +        tiptest(list.append, '(self, object, /)' + append_doc)
    +        tiptest(List.append, '(self, object, /)' + append_doc)
    +        tiptest([].append, '(object, /)' + append_doc)
    +
    +        tiptest(types.MethodType, "method(function, instance)")
    +        tiptest(SB(), default_tip)
    +
    +        p = re.compile('')
    +        tiptest(re.sub, '''\
    +(pattern, repl, string, count=0, flags=0)
    +Return the string obtained by replacing the leftmost
    +non-overlapping occurrences of the pattern in string by the
    +replacement repl.  repl can be either a string or a callable;
    +if a string, backslash escapes in it are processed.  If it is
    +a callable, it's passed the Match object and must return''')
    +        tiptest(p.sub, '''\
    +(repl, string, count=0)
    +Return the string obtained by replacing the leftmost \
    +non-overlapping occurrences o...''')
    +
    +    def test_signature_wrap(self):
    +        if textwrap.TextWrapper.__doc__ is not None:
    +            self.assertEqual(get_spec(textwrap.TextWrapper), '''\
    +(width=70, initial_indent='', subsequent_indent='', expand_tabs=True,
    +    replace_whitespace=True, fix_sentence_endings=False, break_long_words=True,
    +    drop_whitespace=True, break_on_hyphens=True, tabsize=8, *, max_lines=None,
    +    placeholder=' [...]')''')
    +
    +    def test_properly_formated(self):
    +
    +        def foo(s='a'*100):
    +            pass
    +
    +        def bar(s='a'*100):
    +            """Hello Guido"""
    +            pass
    +
    +        def baz(s='a'*100, z='b'*100):
    +            pass
    +
    +        indent = calltip._INDENT
    +
    +        sfoo = "(s='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"\
    +               "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" + indent + "aaaaaaaaa"\
    +               "aaaaaaaaaa')"
    +        sbar = "(s='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"\
    +               "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" + indent + "aaaaaaaaa"\
    +               "aaaaaaaaaa')\nHello Guido"
    +        sbaz = "(s='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"\
    +               "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" + indent + "aaaaaaaaa"\
    +               "aaaaaaaaaa', z='bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"\
    +               "bbbbbbbbbbbbbbbbb\n" + indent + "bbbbbbbbbbbbbbbbbbbbbb"\
    +               "bbbbbbbbbbbbbbbbbbbbbb')"
    +
    +        for func,doc in [(foo, sfoo), (bar, sbar), (baz, sbaz)]:
    +            with self.subTest(func=func, doc=doc):
    +                self.assertEqual(get_spec(func), doc)
    +
    +    def test_docline_truncation(self):
    +        def f(): pass
    +        f.__doc__ = 'a'*300
    +        self.assertEqual(get_spec(f), f"()\n{'a'*(calltip._MAX_COLS-3) + '...'}")
    +
    +    def test_multiline_docstring(self):
    +        # Test fewer lines than max.
    +        self.assertEqual(get_spec(range),
    +                "range(stop) -> range object\n"
    +                "range(start, stop[, step]) -> range object")
    +
    +        # Test max lines
    +        self.assertEqual(get_spec(bytes), '''\
    +bytes(iterable_of_ints) -> bytes
    +bytes(string, encoding[, errors]) -> bytes
    +bytes(bytes_or_buffer) -> immutable copy of bytes_or_buffer
    +bytes(int) -> bytes object of size given by the parameter initialized with null bytes
    +bytes() -> empty bytes object''')
    +
    +        # Test more than max lines
    +        def f(): pass
    +        f.__doc__ = 'a\n' * 15
    +        self.assertEqual(get_spec(f), '()' + '\na' * calltip._MAX_LINES)
    +
    +    def test_functions(self):
    +        def t1(): 'doc'
    +        t1.tip = "()"
    +        def t2(a, b=None): 'doc'
    +        t2.tip = "(a, b=None)"
    +        def t3(a, *args): 'doc'
    +        t3.tip = "(a, *args)"
    +        def t4(*args): 'doc'
    +        t4.tip = "(*args)"
    +        def t5(a, b=None, *args, **kw): 'doc'
    +        t5.tip = "(a, b=None, *args, **kw)"
    +
    +        doc = '\ndoc' if t1.__doc__ is not None else ''
    +        for func in (t1, t2, t3, t4, t5, TC):
    +            with self.subTest(func=func):
    +                self.assertEqual(get_spec(func), func.tip + doc)
    +
    +    def test_methods(self):
    +        doc = '\ndoc' if TC.__doc__ is not None else ''
    +        for meth in (TC.t1, TC.t2, TC.t3, TC.t4, TC.t5, TC.t6, TC.__call__):
    +            with self.subTest(meth=meth):
    +                self.assertEqual(get_spec(meth), meth.tip + doc)
    +        self.assertEqual(get_spec(TC.cm), "(a)" + doc)
    +        self.assertEqual(get_spec(TC.sm), "(b)" + doc)
    +
    +    def test_bound_methods(self):
    +        # test that first parameter is correctly removed from argspec
    +        doc = '\ndoc' if TC.__doc__ is not None else ''
    +        for meth, mtip  in ((tc.t1, "()"), (tc.t4, "(*args)"),
    +                            (tc.t6, "(self)"), (tc.__call__, '(ci)'),
    +                            (tc, '(ci)'), (TC.cm, "(a)"),):
    +            with self.subTest(meth=meth, mtip=mtip):
    +                self.assertEqual(get_spec(meth), mtip + doc)
    +
    +    def test_starred_parameter(self):
    +        # test that starred first parameter is *not* removed from argspec
    +        class C:
    +            def m1(*args): pass
    +        c = C()
    +        for meth, mtip  in ((C.m1, '(*args)'), (c.m1, "(*args)"),):
    +            with self.subTest(meth=meth, mtip=mtip):
    +                self.assertEqual(get_spec(meth), mtip)
    +
    +    def test_invalid_method_get_spec(self):
    +        class C:
    +            def m2(**kwargs): pass
    +        class Test:
    +            def __call__(*, a): pass
    +
    +        mtip = calltip._invalid_method
    +        self.assertEqual(get_spec(C().m2), mtip)
    +        self.assertEqual(get_spec(Test()), mtip)
    +
    +    def test_non_ascii_name(self):
    +        # test that re works to delete a first parameter name that
    +        # includes non-ascii chars, such as various forms of A.
    +        uni = "(A\u0391\u0410\u05d0\u0627\u0905\u1e00\u3042, a)"
    +        assert calltip._first_param.sub('', uni) == '(a)'
    +
    +    def test_no_docstring(self):
    +        for meth, mtip in ((TC.nd, "(self)"), (tc.nd, "()")):
    +            with self.subTest(meth=meth, mtip=mtip):
    +                self.assertEqual(get_spec(meth), mtip)
    +
    +    def test_attribute_exception(self):
    +        class NoCall:
    +            def __getattr__(self, name):
    +                raise BaseException
    +        class CallA(NoCall):
    +            def __call__(oui, a, b, c):
    +                pass
    +        class CallB(NoCall):
    +            def __call__(self, ci):
    +                pass
    +
    +        for meth, mtip  in ((NoCall, default_tip), (CallA, default_tip),
    +                            (NoCall(), ''), (CallA(), '(a, b, c)'),
    +                            (CallB(), '(ci)')):
    +            with self.subTest(meth=meth, mtip=mtip):
    +                self.assertEqual(get_spec(meth), mtip)
    +
    +    def test_non_callables(self):
    +        for obj in (0, 0.0, '0', b'0', [], {}):
    +            with self.subTest(obj=obj):
    +                self.assertEqual(get_spec(obj), '')
    +
    +
    +class Get_entityTest(unittest.TestCase):
    +    def test_bad_entity(self):
    +        self.assertIsNone(calltip.get_entity('1/0'))
    +    def test_good_entity(self):
    +        self.assertIs(calltip.get_entity('int'), int)
    +
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_calltip_w.py b/PythonLib/full/idlelib/idle_test/test_calltip_w.py
    new file mode 100644
    index 00000000..a5ec76e1
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_calltip_w.py
    @@ -0,0 +1,29 @@
    +"Test calltip_w, coverage 18%."
    +
    +from idlelib import calltip_w
    +import unittest
    +from test.support import requires
    +from tkinter import Tk, Text
    +
    +
    +class CallTipWindowTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        requires('gui')
    +        cls.root = Tk()
    +        cls.root.withdraw()
    +        cls.text = Text(cls.root)
    +        cls.calltip = calltip_w.CalltipWindow(cls.text)
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        cls.root.update_idletasks()
    +        cls.root.destroy()
    +        del cls.text, cls.root
    +
    +    def test_init(self):
    +        self.assertEqual(self.calltip.anchor_widget, self.text)
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_calltips.py b/PythonLib/full/idlelib/idle_test/test_calltips.py
    deleted file mode 100644
    index 147119ce..00000000
    --- a/PythonLib/full/idlelib/idle_test/test_calltips.py
    +++ /dev/null
    @@ -1,185 +0,0 @@
    -import unittest
    -import idlelib.CallTips as ct
    -CTi = ct.CallTips()  # needed for get_entity test in 2.7
    -import textwrap
    -import types
    -import warnings
    -
    -default_tip = ''
    -
    -# Test Class TC is used in multiple get_argspec test methods
    -class TC(object):
    -    'doc'
    -    tip = "(ai=None, *args)"
    -    def __init__(self, ai=None, *b): 'doc'
    -    __init__.tip = "(self, ai=None, *args)"
    -    def t1(self): 'doc'
    -    t1.tip = "(self)"
    -    def t2(self, ai, b=None): 'doc'
    -    t2.tip = "(self, ai, b=None)"
    -    def t3(self, ai, *args): 'doc'
    -    t3.tip = "(self, ai, *args)"
    -    def t4(self, *args): 'doc'
    -    t4.tip = "(self, *args)"
    -    def t5(self, ai, b=None, *args, **kw): 'doc'
    -    t5.tip = "(self, ai, b=None, *args, **kwargs)"
    -    def t6(no, self): 'doc'
    -    t6.tip = "(no, self)"
    -    def __call__(self, ci): 'doc'
    -    __call__.tip = "(self, ci)"
    -    # attaching .tip to wrapped methods does not work
    -    @classmethod
    -    def cm(cls, a): 'doc'
    -    @staticmethod
    -    def sm(b): 'doc'
    -
    -tc = TC()
    -
    -signature = ct.get_arg_text  # 2.7 and 3.x use different functions
    -class Get_signatureTest(unittest.TestCase):
    -    # The signature function must return a string, even if blank.
    -    # Test a variety of objects to be sure that none cause it to raise
    -    # (quite aside from getting as correct an answer as possible).
    -    # The tests of builtins may break if the docstrings change,
    -    # but a red buildbot is better than a user crash (as has happened).
    -    # For a simple mismatch, change the expected output to the actual.
    -
    -    def test_builtins(self):
    -        # 2.7 puts '()\n' where 3.x does not, other minor differences
    -
    -        # Python class that inherits builtin methods
    -        class List(list): "List() doc"
    -        # Simulate builtin with no docstring for default argspec test
    -        class SB:  __call__ = None
    -
    -        def gtest(obj, out):
    -            self.assertEqual(signature(obj), out)
    -
    -        if List.__doc__ is not None:
    -            gtest(List, '()\n' + List.__doc__)
    -        gtest(list.__new__,
    -               'T.__new__(S, ...) -> a new object with type S, a subtype of T')
    -        gtest(list.__init__,
    -               'x.__init__(...) initializes x; see help(type(x)) for signature')
    -        append_doc =  "L.append(object) -- append object to end"
    -        gtest(list.append, append_doc)
    -        gtest([].append, append_doc)
    -        gtest(List.append, append_doc)
    -
    -        gtest(types.MethodType, '()\ninstancemethod(function, instance, class)')
    -        gtest(SB(), default_tip)
    -
    -    def test_signature_wrap(self):
    -        # This is also a test of an old-style class
    -        if textwrap.TextWrapper.__doc__ is not None:
    -            self.assertEqual(signature(textwrap.TextWrapper), '''\
    -(width=70, initial_indent='', subsequent_indent='', expand_tabs=True,
    -    replace_whitespace=True, fix_sentence_endings=False, break_long_words=True,
    -    drop_whitespace=True, break_on_hyphens=True)''')
    -
    -    def test_docline_truncation(self):
    -        def f(): pass
    -        f.__doc__ = 'a'*300
    -        self.assertEqual(signature(f), '()\n' + 'a' * (ct._MAX_COLS-3) + '...')
    -
    -    def test_multiline_docstring(self):
    -        # Test fewer lines than max.
    -        self.assertEqual(signature(list),
    -                "()\nlist() -> new empty list\n"
    -                "list(iterable) -> new list initialized from iterable's items")
    -
    -        # Test max lines and line (currently) too long.
    -        def f():
    -            pass
    -        s = 'a\nb\nc\nd\n'
    -        f.__doc__ = s + 300 * 'e' + 'f'
    -        self.assertEqual(signature(f),
    -                         '()\n' + s + (ct._MAX_COLS - 3) * 'e' + '...')
    -
    -    def test_functions(self):
    -        def t1(): 'doc'
    -        t1.tip = "()"
    -        def t2(a, b=None): 'doc'
    -        t2.tip = "(a, b=None)"
    -        def t3(a, *args): 'doc'
    -        t3.tip = "(a, *args)"
    -        def t4(*args): 'doc'
    -        t4.tip = "(*args)"
    -        def t5(a, b=None, *args, **kwds): 'doc'
    -        t5.tip = "(a, b=None, *args, **kwargs)"
    -
    -        doc = '\ndoc' if t1.__doc__ is not None else ''
    -        for func in (t1, t2, t3, t4, t5, TC):
    -            self.assertEqual(signature(func), func.tip + doc)
    -
    -    def test_methods(self):
    -        doc = '\ndoc' if TC.__doc__ is not None else ''
    -        for meth in (TC.t1, TC.t2, TC.t3, TC.t4, TC.t5, TC.t6, TC.__call__):
    -            self.assertEqual(signature(meth), meth.tip + doc)
    -        self.assertEqual(signature(TC.cm), "(a)" + doc)
    -        self.assertEqual(signature(TC.sm), "(b)" + doc)
    -
    -    def test_bound_methods(self):
    -        # test that first parameter is correctly removed from argspec
    -        doc = '\ndoc' if TC.__doc__ is not None else ''
    -        for meth, mtip  in ((tc.t1, "()"), (tc.t4, "(*args)"), (tc.t6, "(self)"),
    -                            (tc.__call__, '(ci)'), (tc, '(ci)'), (TC.cm, "(a)"),):
    -            self.assertEqual(signature(meth), mtip + doc)
    -
    -    def test_starred_parameter(self):
    -        # test that starred first parameter is *not* removed from argspec
    -        class C:
    -            def m1(*args): pass
    -            def m2(**kwds): pass
    -        def f1(args, kwargs, *a, **k): pass
    -        def f2(args, kwargs, args1, kwargs1, *a, **k): pass
    -        c = C()
    -        self.assertEqual(signature(C.m1), '(*args)')
    -        self.assertEqual(signature(c.m1), '(*args)')
    -        self.assertEqual(signature(C.m2), '(**kwargs)')
    -        self.assertEqual(signature(c.m2), '(**kwargs)')
    -        self.assertEqual(signature(f1), '(args, kwargs, *args1, **kwargs1)')
    -        self.assertEqual(signature(f2),
    -                         '(args, kwargs, args1, kwargs1, *args2, **kwargs2)')
    -
    -    def test_no_docstring(self):
    -        def nd(s): pass
    -        TC.nd = nd
    -        self.assertEqual(signature(nd), "(s)")
    -        self.assertEqual(signature(TC.nd), "(s)")
    -        self.assertEqual(signature(tc.nd), "()")
    -
    -    def test_attribute_exception(self):
    -        class NoCall(object):
    -            def __getattr__(self, name):
    -                raise BaseException
    -        class Call(NoCall):
    -            def __call__(self, ci):
    -                pass
    -        for meth, mtip  in ((NoCall, '()'), (Call, '()'),
    -                            (NoCall(), ''), (Call(), '(ci)')):
    -            self.assertEqual(signature(meth), mtip)
    -
    -    def test_non_callables(self):
    -        for obj in (0, 0.0, '0', b'0', [], {}):
    -            self.assertEqual(signature(obj), '')
    -
    -class Get_entityTest(unittest.TestCase):
    -    # In 3.x, get_entity changed from 'instance method' to module function
    -    # since 'self' not used. Use dummy instance until change 2.7 also.
    -    def test_bad_entity(self):
    -        self.assertIsNone(CTi.get_entity('1//0'))
    -    def test_good_entity(self):
    -        self.assertIs(CTi.get_entity('int'), int)
    -
    -class Py2Test(unittest.TestCase):
    -    def test_paramtuple_float(self):
    -        # 18539: (a,b) becomes '.0' in code object; change that but not 0.0
    -        with warnings.catch_warnings():
    -            # Suppess message of py3 deprecation of parameter unpacking
    -            warnings.simplefilter("ignore")
    -            exec "def f((a,b), c=0.0): pass"
    -        self.assertEqual(signature(f), '(, c=0.0)')
    -
    -if __name__ == '__main__':
    -    unittest.main(verbosity=2, exit=False)
    diff --git a/PythonLib/full/idlelib/idle_test/test_codecontext.py b/PythonLib/full/idlelib/idle_test/test_codecontext.py
    new file mode 100644
    index 00000000..3ec49e97
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_codecontext.py
    @@ -0,0 +1,447 @@
    +"Test codecontext, coverage 100%"
    +
    +from idlelib import codecontext
    +import unittest
    +import unittest.mock
    +from test.support import requires
    +from tkinter import NSEW, Tk, Frame, Text, TclError
    +
    +from unittest import mock
    +import re
    +from idlelib import config
    +
    +
    +usercfg = codecontext.idleConf.userCfg
    +testcfg = {
    +    'main': config.IdleUserConfParser(''),
    +    'highlight': config.IdleUserConfParser(''),
    +    'keys': config.IdleUserConfParser(''),
    +    'extensions': config.IdleUserConfParser(''),
    +}
    +code_sample = """\
    +
    +class C1():
    +    # Class comment.
    +    def __init__(self, a, b):
    +        self.a = a
    +        self.b = b
    +    def compare(self):
    +        if a > b:
    +            return a
    +        elif a < b:
    +            return b
    +        else:
    +            return None
    +"""
    +
    +
    +class DummyEditwin:
    +    def __init__(self, root, frame, text):
    +        self.root = root
    +        self.top = root
    +        self.text_frame = frame
    +        self.text = text
    +        self.label = ''
    +
    +    def getlineno(self, index):
    +        return int(float(self.text.index(index)))
    +
    +    def update_menu_label(self, **kwargs):
    +        self.label = kwargs['label']
    +
    +
    +class CodeContextTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        requires('gui')
    +        root = cls.root = Tk()
    +        root.withdraw()
    +        frame = cls.frame = Frame(root)
    +        text = cls.text = Text(frame)
    +        text.insert('1.0', code_sample)
    +        # Need to pack for creation of code context text widget.
    +        frame.pack(side='left', fill='both', expand=1)
    +        text.grid(row=1, column=1, sticky=NSEW)
    +        cls.editor = DummyEditwin(root, frame, text)
    +        codecontext.idleConf.userCfg = testcfg
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        codecontext.idleConf.userCfg = usercfg
    +        cls.editor.text.delete('1.0', 'end')
    +        del cls.editor, cls.frame, cls.text
    +        cls.root.update_idletasks()
    +        cls.root.destroy()
    +        del cls.root
    +
    +    def setUp(self):
    +        self.text.yview(0)
    +        self.text['font'] = 'TkFixedFont'
    +        self.cc = codecontext.CodeContext(self.editor)
    +
    +        self.highlight_cfg = {"background": '#abcdef',
    +                              "foreground": '#123456'}
    +        orig_idleConf_GetHighlight = codecontext.idleConf.GetHighlight
    +        def mock_idleconf_GetHighlight(theme, element):
    +            if element == 'context':
    +                return self.highlight_cfg
    +            return orig_idleConf_GetHighlight(theme, element)
    +        GetHighlight_patcher = unittest.mock.patch.object(
    +            codecontext.idleConf, 'GetHighlight', mock_idleconf_GetHighlight)
    +        GetHighlight_patcher.start()
    +        self.addCleanup(GetHighlight_patcher.stop)
    +
    +        self.font_override = 'TkFixedFont'
    +        def mock_idleconf_GetFont(root, configType, section):
    +            return self.font_override
    +        GetFont_patcher = unittest.mock.patch.object(
    +            codecontext.idleConf, 'GetFont', mock_idleconf_GetFont)
    +        GetFont_patcher.start()
    +        self.addCleanup(GetFont_patcher.stop)
    +
    +    def tearDown(self):
    +        if self.cc.context:
    +            self.cc.context.destroy()
    +        # Explicitly call __del__ to remove scheduled scripts.
    +        self.cc.__del__()
    +        del self.cc.context, self.cc
    +
    +    def test_init(self):
    +        eq = self.assertEqual
    +        ed = self.editor
    +        cc = self.cc
    +
    +        eq(cc.editwin, ed)
    +        eq(cc.text, ed.text)
    +        eq(cc.text['font'], ed.text['font'])
    +        self.assertIsNone(cc.context)
    +        eq(cc.info, [(0, -1, '', False)])
    +        eq(cc.topvisible, 1)
    +        self.assertIsNone(self.cc.t1)
    +
    +    def test_del(self):
    +        self.cc.__del__()
    +
    +    def test_del_with_timer(self):
    +        timer = self.cc.t1 = self.text.after(10000, lambda: None)
    +        self.cc.__del__()
    +        with self.assertRaises(TclError) as cm:
    +            self.root.tk.call('after', 'info', timer)
    +        self.assertIn("doesn't exist", str(cm.exception))
    +
    +    def test_reload(self):
    +        codecontext.CodeContext.reload()
    +        self.assertEqual(self.cc.context_depth, 15)
    +
    +    def test_toggle_code_context_event(self):
    +        eq = self.assertEqual
    +        cc = self.cc
    +        toggle = cc.toggle_code_context_event
    +
    +        # Make sure code context is off.
    +        if cc.context:
    +            toggle()
    +
    +        # Toggle on.
    +        toggle()
    +        self.assertIsNotNone(cc.context)
    +        eq(cc.context['font'], self.text['font'])
    +        eq(cc.context['fg'], self.highlight_cfg['foreground'])
    +        eq(cc.context['bg'], self.highlight_cfg['background'])
    +        eq(cc.context.get('1.0', 'end-1c'), '')
    +        eq(cc.editwin.label, 'Hide Code Context')
    +        eq(self.root.tk.call('after', 'info', self.cc.t1)[1], 'timer')
    +
    +        # Toggle off.
    +        toggle()
    +        self.assertIsNone(cc.context)
    +        eq(cc.editwin.label, 'Show Code Context')
    +        self.assertIsNone(self.cc.t1)
    +
    +        # Scroll down and toggle back on.
    +        line11_context = '\n'.join(x[2] for x in cc.get_context(11)[0])
    +        cc.text.yview(11)
    +        toggle()
    +        eq(cc.context.get('1.0', 'end-1c'), line11_context)
    +
    +        # Toggle off and on again.
    +        toggle()
    +        toggle()
    +        eq(cc.context.get('1.0', 'end-1c'), line11_context)
    +
    +    def test_get_context(self):
    +        eq = self.assertEqual
    +        gc = self.cc.get_context
    +
    +        # stopline must be greater than 0.
    +        with self.assertRaises(AssertionError):
    +            gc(1, stopline=0)
    +
    +        eq(gc(3), ([(2, 0, 'class C1():', 'class')], 0))
    +
    +        # Don't return comment.
    +        eq(gc(4), ([(2, 0, 'class C1():', 'class')], 0))
    +
    +        # Two indentation levels and no comment.
    +        eq(gc(5), ([(2, 0, 'class C1():', 'class'),
    +                    (4, 4, '    def __init__(self, a, b):', 'def')], 0))
    +
    +        # Only one 'def' is returned, not both at the same indent level.
    +        eq(gc(10), ([(2, 0, 'class C1():', 'class'),
    +                     (7, 4, '    def compare(self):', 'def'),
    +                     (8, 8, '        if a > b:', 'if')], 0))
    +
    +        # With 'elif', also show the 'if' even though it's at the same level.
    +        eq(gc(11), ([(2, 0, 'class C1():', 'class'),
    +                     (7, 4, '    def compare(self):', 'def'),
    +                     (8, 8, '        if a > b:', 'if'),
    +                     (10, 8, '        elif a < b:', 'elif')], 0))
    +
    +        # Set stop_line to not go back to first line in source code.
    +        # Return includes stop_line.
    +        eq(gc(11, stopline=2), ([(2, 0, 'class C1():', 'class'),
    +                                 (7, 4, '    def compare(self):', 'def'),
    +                                 (8, 8, '        if a > b:', 'if'),
    +                                 (10, 8, '        elif a < b:', 'elif')], 0))
    +        eq(gc(11, stopline=3), ([(7, 4, '    def compare(self):', 'def'),
    +                                 (8, 8, '        if a > b:', 'if'),
    +                                 (10, 8, '        elif a < b:', 'elif')], 4))
    +        eq(gc(11, stopline=8), ([(8, 8, '        if a > b:', 'if'),
    +                                 (10, 8, '        elif a < b:', 'elif')], 8))
    +
    +        # Set stop_indent to test indent level to stop at.
    +        eq(gc(11, stopindent=4), ([(7, 4, '    def compare(self):', 'def'),
    +                                   (8, 8, '        if a > b:', 'if'),
    +                                   (10, 8, '        elif a < b:', 'elif')], 4))
    +        # Check that the 'if' is included.
    +        eq(gc(11, stopindent=8), ([(8, 8, '        if a > b:', 'if'),
    +                                   (10, 8, '        elif a < b:', 'elif')], 8))
    +
    +    def test_update_code_context(self):
    +        eq = self.assertEqual
    +        cc = self.cc
    +        # Ensure code context is active.
    +        if not cc.context:
    +            cc.toggle_code_context_event()
    +
    +        # Invoke update_code_context without scrolling - nothing happens.
    +        self.assertIsNone(cc.update_code_context())
    +        eq(cc.info, [(0, -1, '', False)])
    +        eq(cc.topvisible, 1)
    +
    +        # Scroll down to line 1.
    +        cc.text.yview(1)
    +        cc.update_code_context()
    +        eq(cc.info, [(0, -1, '', False)])
    +        eq(cc.topvisible, 2)
    +        eq(cc.context.get('1.0', 'end-1c'), '')
    +
    +        # Scroll down to line 2.
    +        cc.text.yview(2)
    +        cc.update_code_context()
    +        eq(cc.info, [(0, -1, '', False), (2, 0, 'class C1():', 'class')])
    +        eq(cc.topvisible, 3)
    +        eq(cc.context.get('1.0', 'end-1c'), 'class C1():')
    +
    +        # Scroll down to line 3.  Since it's a comment, nothing changes.
    +        cc.text.yview(3)
    +        cc.update_code_context()
    +        eq(cc.info, [(0, -1, '', False), (2, 0, 'class C1():', 'class')])
    +        eq(cc.topvisible, 4)
    +        eq(cc.context.get('1.0', 'end-1c'), 'class C1():')
    +
    +        # Scroll down to line 4.
    +        cc.text.yview(4)
    +        cc.update_code_context()
    +        eq(cc.info, [(0, -1, '', False),
    +                     (2, 0, 'class C1():', 'class'),
    +                     (4, 4, '    def __init__(self, a, b):', 'def')])
    +        eq(cc.topvisible, 5)
    +        eq(cc.context.get('1.0', 'end-1c'), 'class C1():\n'
    +                                            '    def __init__(self, a, b):')
    +
    +        # Scroll down to line 11.  Last 'def' is removed.
    +        cc.text.yview(11)
    +        cc.update_code_context()
    +        eq(cc.info, [(0, -1, '', False),
    +                     (2, 0, 'class C1():', 'class'),
    +                     (7, 4, '    def compare(self):', 'def'),
    +                     (8, 8, '        if a > b:', 'if'),
    +                     (10, 8, '        elif a < b:', 'elif')])
    +        eq(cc.topvisible, 12)
    +        eq(cc.context.get('1.0', 'end-1c'), 'class C1():\n'
    +                                            '    def compare(self):\n'
    +                                            '        if a > b:\n'
    +                                            '        elif a < b:')
    +
    +        # No scroll.  No update, even though context_depth changed.
    +        cc.update_code_context()
    +        cc.context_depth = 1
    +        eq(cc.info, [(0, -1, '', False),
    +                     (2, 0, 'class C1():', 'class'),
    +                     (7, 4, '    def compare(self):', 'def'),
    +                     (8, 8, '        if a > b:', 'if'),
    +                     (10, 8, '        elif a < b:', 'elif')])
    +        eq(cc.topvisible, 12)
    +        eq(cc.context.get('1.0', 'end-1c'), 'class C1():\n'
    +                                            '    def compare(self):\n'
    +                                            '        if a > b:\n'
    +                                            '        elif a < b:')
    +
    +        # Scroll up.
    +        cc.text.yview(5)
    +        cc.update_code_context()
    +        eq(cc.info, [(0, -1, '', False),
    +                     (2, 0, 'class C1():', 'class'),
    +                     (4, 4, '    def __init__(self, a, b):', 'def')])
    +        eq(cc.topvisible, 6)
    +        # context_depth is 1.
    +        eq(cc.context.get('1.0', 'end-1c'), '    def __init__(self, a, b):')
    +
    +    def test_jumptoline(self):
    +        eq = self.assertEqual
    +        cc = self.cc
    +        jump = cc.jumptoline
    +
    +        if not cc.context:
    +            cc.toggle_code_context_event()
    +
    +        # Empty context.
    +        cc.text.yview('2.0')
    +        cc.update_code_context()
    +        eq(cc.topvisible, 2)
    +        cc.context.mark_set('insert', '1.5')
    +        jump()
    +        eq(cc.topvisible, 1)
    +
    +        # 4 lines of context showing.
    +        cc.text.yview('12.0')
    +        cc.update_code_context()
    +        eq(cc.topvisible, 12)
    +        cc.context.mark_set('insert', '3.0')
    +        jump()
    +        eq(cc.topvisible, 8)
    +
    +        # More context lines than limit.
    +        cc.context_depth = 2
    +        cc.text.yview('12.0')
    +        cc.update_code_context()
    +        eq(cc.topvisible, 12)
    +        cc.context.mark_set('insert', '1.0')
    +        jump()
    +        eq(cc.topvisible, 8)
    +
    +    @mock.patch.object(codecontext.CodeContext, 'update_code_context')
    +    def test_timer_event(self, mock_update):
    +        # Ensure code context is not active.
    +        if self.cc.context:
    +            self.cc.toggle_code_context_event()
    +        self.cc.timer_event()
    +        mock_update.assert_not_called()
    +
    +        # Activate code context.
    +        self.cc.toggle_code_context_event()
    +        self.cc.timer_event()
    +        mock_update.assert_called()
    +
    +    def test_font(self):
    +        eq = self.assertEqual
    +        cc = self.cc
    +
    +        orig_font = cc.text['font']
    +        test_font = 'TkTextFont'
    +        self.assertNotEqual(orig_font, test_font)
    +
    +        # Ensure code context is not active.
    +        if cc.context is not None:
    +            cc.toggle_code_context_event()
    +
    +        self.font_override = test_font
    +        # Nothing breaks or changes with inactive code context.
    +        cc.update_font()
    +
    +        # Activate code context, previous font change is immediately effective.
    +        cc.toggle_code_context_event()
    +        eq(cc.context['font'], test_font)
    +
    +        # Call the font update, change is picked up.
    +        self.font_override = orig_font
    +        cc.update_font()
    +        eq(cc.context['font'], orig_font)
    +
    +    def test_highlight_colors(self):
    +        eq = self.assertEqual
    +        cc = self.cc
    +
    +        orig_colors = dict(self.highlight_cfg)
    +        test_colors = {'background': '#222222', 'foreground': '#ffff00'}
    +
    +        def assert_colors_are_equal(colors):
    +            eq(cc.context['background'], colors['background'])
    +            eq(cc.context['foreground'], colors['foreground'])
    +
    +        # Ensure code context is not active.
    +        if cc.context:
    +            cc.toggle_code_context_event()
    +
    +        self.highlight_cfg = test_colors
    +        # Nothing breaks with inactive code context.
    +        cc.update_highlight_colors()
    +
    +        # Activate code context, previous colors change is immediately effective.
    +        cc.toggle_code_context_event()
    +        assert_colors_are_equal(test_colors)
    +
    +        # Call colors update with no change to the configured colors.
    +        cc.update_highlight_colors()
    +        assert_colors_are_equal(test_colors)
    +
    +        # Call the colors update with code context active, change is picked up.
    +        self.highlight_cfg = orig_colors
    +        cc.update_highlight_colors()
    +        assert_colors_are_equal(orig_colors)
    +
    +
    +class HelperFunctionText(unittest.TestCase):
    +
    +    def test_get_spaces_firstword(self):
    +        get = codecontext.get_spaces_firstword
    +        test_lines = (
    +            ('    first word', ('    ', 'first')),
    +            ('\tfirst word', ('\t', 'first')),
    +            ('  \u19D4\u19D2: ', ('  ', '\u19D4\u19D2')),
    +            ('no spaces', ('', 'no')),
    +            ('', ('', '')),
    +            ('# TEST COMMENT', ('', '')),
    +            ('    (continuation)', ('    ', ''))
    +            )
    +        for line, expected_output in test_lines:
    +            self.assertEqual(get(line), expected_output)
    +
    +        # Send the pattern in the call.
    +        self.assertEqual(get('    (continuation)',
    +                             c=re.compile(r'^(\s*)([^\s]*)')),
    +                         ('    ', '(continuation)'))
    +
    +    def test_get_line_info(self):
    +        eq = self.assertEqual
    +        gli = codecontext.get_line_info
    +        lines = code_sample.splitlines()
    +
    +        # Line 1 is not a BLOCKOPENER.
    +        eq(gli(lines[0]), (codecontext.INFINITY, '', False))
    +        # Line 2 is a BLOCKOPENER without an indent.
    +        eq(gli(lines[1]), (0, 'class C1():', 'class'))
    +        # Line 3 is not a BLOCKOPENER and does not return the indent level.
    +        eq(gli(lines[2]), (codecontext.INFINITY, '    # Class comment.', False))
    +        # Line 4 is a BLOCKOPENER and is indented.
    +        eq(gli(lines[3]), (4, '    def __init__(self, a, b):', 'def'))
    +        # Line 8 is a different BLOCKOPENER and is indented.
    +        eq(gli(lines[7]), (8, '        if a > b:', 'if'))
    +        # Test tab.
    +        eq(gli('\tif a == b:'), (1, '\tif a == b:', 'if'))
    +
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_colorizer.py b/PythonLib/full/idlelib/idle_test/test_colorizer.py
    new file mode 100644
    index 00000000..c31c4923
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_colorizer.py
    @@ -0,0 +1,423 @@
    +"Test colorizer, coverage 93%."
    +
    +from idlelib import colorizer
    +from test.support import requires
    +import unittest
    +from unittest import mock
    +
    +from functools import partial
    +from tkinter import Tk, Text
    +from idlelib import config
    +from idlelib.percolator import Percolator
    +
    +
    +usercfg = colorizer.idleConf.userCfg
    +testcfg = {
    +    'main': config.IdleUserConfParser(''),
    +    'highlight': config.IdleUserConfParser(''),
    +    'keys': config.IdleUserConfParser(''),
    +    'extensions': config.IdleUserConfParser(''),
    +}
    +
    +source = (
    +    "if True: int ('1') # keyword, builtin, string, comment\n"
    +    "elif False: print(0)  # 'string' in comment\n"
    +    "else: float(None)  # if in comment\n"
    +    "if iF + If + IF: 'keyword matching must respect case'\n"
    +    "if'': x or''  # valid string-keyword no-space combinations\n"
    +    "async def f(): await g()\n"
    +    "'x', '''x''', \"x\", \"\"\"x\"\"\"\n"
    +    )
    +
    +
    +def setUpModule():
    +    colorizer.idleConf.userCfg = testcfg
    +
    +
    +def tearDownModule():
    +    colorizer.idleConf.userCfg = usercfg
    +
    +
    +class FunctionTest(unittest.TestCase):
    +
    +    def test_any(self):
    +        self.assertEqual(colorizer.any('test', ('a', 'b', 'cd')),
    +                         '(?Pa|b|cd)')
    +
    +    def test_make_pat(self):
    +        # Tested in more detail by testing prog.
    +        self.assertTrue(colorizer.make_pat())
    +
    +    def test_prog(self):
    +        prog = colorizer.prog
    +        eq = self.assertEqual
    +        line = 'def f():\n    print("hello")\n'
    +        m = prog.search(line)
    +        eq(m.groupdict()['KEYWORD'], 'def')
    +        m = prog.search(line, m.end())
    +        eq(m.groupdict()['SYNC'], '\n')
    +        m = prog.search(line, m.end())
    +        eq(m.groupdict()['BUILTIN'], 'print')
    +        m = prog.search(line, m.end())
    +        eq(m.groupdict()['STRING'], '"hello"')
    +        m = prog.search(line, m.end())
    +        eq(m.groupdict()['SYNC'], '\n')
    +
    +    def test_idprog(self):
    +        idprog = colorizer.idprog
    +        m = idprog.match('nospace')
    +        self.assertIsNone(m)
    +        m = idprog.match(' space')
    +        self.assertEqual(m.group(0), ' space')
    +
    +
    +class ColorConfigTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        requires('gui')
    +        root = cls.root = Tk()
    +        root.withdraw()
    +        cls.text = Text(root)
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        del cls.text
    +        cls.root.update_idletasks()
    +        cls.root.destroy()
    +        del cls.root
    +
    +    def test_color_config(self):
    +        text = self.text
    +        eq = self.assertEqual
    +        colorizer.color_config(text)
    +        # Uses IDLE Classic theme as default.
    +        eq(text['background'], '#ffffff')
    +        eq(text['foreground'], '#000000')
    +        eq(text['selectbackground'], 'gray')
    +        eq(text['selectforeground'], '#000000')
    +        eq(text['insertbackground'], 'black')
    +        eq(text['inactiveselectbackground'], 'gray')
    +
    +
    +class ColorDelegatorInstantiationTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        requires('gui')
    +        root = cls.root = Tk()
    +        root.withdraw()
    +        text = cls.text = Text(root)
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        del cls.text
    +        cls.root.update_idletasks()
    +        cls.root.destroy()
    +        del cls.root
    +
    +    def setUp(self):
    +        self.color = colorizer.ColorDelegator()
    +
    +    def tearDown(self):
    +        self.color.close()
    +        self.text.delete('1.0', 'end')
    +        self.color.resetcache()
    +        del self.color
    +
    +    def test_init(self):
    +        color = self.color
    +        self.assertIsInstance(color, colorizer.ColorDelegator)
    +
    +    def test_init_state(self):
    +        # init_state() is called during the instantiation of
    +        # ColorDelegator in setUp().
    +        color = self.color
    +        self.assertIsNone(color.after_id)
    +        self.assertTrue(color.allow_colorizing)
    +        self.assertFalse(color.colorizing)
    +        self.assertFalse(color.stop_colorizing)
    +
    +
    +class ColorDelegatorTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        requires('gui')
    +        root = cls.root = Tk()
    +        root.withdraw()
    +        text = cls.text = Text(root)
    +        cls.percolator = Percolator(text)
    +        # Delegator stack = [Delegator(text)]
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        cls.percolator.redir.close()
    +        del cls.percolator, cls.text
    +        cls.root.update_idletasks()
    +        cls.root.destroy()
    +        del cls.root
    +
    +    def setUp(self):
    +        self.color = colorizer.ColorDelegator()
    +        self.percolator.insertfilter(self.color)
    +        # Calls color.setdelegate(Delegator(text)).
    +
    +    def tearDown(self):
    +        self.color.close()
    +        self.percolator.removefilter(self.color)
    +        self.text.delete('1.0', 'end')
    +        self.color.resetcache()
    +        del self.color
    +
    +    def test_setdelegate(self):
    +        # Called in setUp when filter is attached to percolator.
    +        color = self.color
    +        self.assertIsInstance(color.delegate, colorizer.Delegator)
    +        # It is too late to mock notify_range, so test side effect.
    +        self.assertEqual(self.root.tk.call(
    +            'after', 'info', color.after_id)[1], 'timer')
    +
    +    def test_LoadTagDefs(self):
    +        highlight = partial(config.idleConf.GetHighlight, theme='IDLE Classic')
    +        for tag, colors in self.color.tagdefs.items():
    +            with self.subTest(tag=tag):
    +                self.assertIn('background', colors)
    +                self.assertIn('foreground', colors)
    +                if tag not in ('SYNC', 'TODO'):
    +                    self.assertEqual(colors, highlight(element=tag.lower()))
    +
    +    def test_config_colors(self):
    +        text = self.text
    +        highlight = partial(config.idleConf.GetHighlight, theme='IDLE Classic')
    +        for tag in self.color.tagdefs:
    +            for plane in ('background', 'foreground'):
    +                with self.subTest(tag=tag, plane=plane):
    +                    if tag in ('SYNC', 'TODO'):
    +                        self.assertEqual(text.tag_cget(tag, plane), '')
    +                    else:
    +                        self.assertEqual(text.tag_cget(tag, plane),
    +                                         highlight(element=tag.lower())[plane])
    +        # 'sel' is marked as the highest priority.
    +        self.assertEqual(text.tag_names()[-1], 'sel')
    +
    +    @mock.patch.object(colorizer.ColorDelegator, 'notify_range')
    +    def test_insert(self, mock_notify):
    +        text = self.text
    +        # Initial text.
    +        text.insert('insert', 'foo')
    +        self.assertEqual(text.get('1.0', 'end'), 'foo\n')
    +        mock_notify.assert_called_with('1.0', '1.0+3c')
    +        # Additional text.
    +        text.insert('insert', 'barbaz')
    +        self.assertEqual(text.get('1.0', 'end'), 'foobarbaz\n')
    +        mock_notify.assert_called_with('1.3', '1.3+6c')
    +
    +    @mock.patch.object(colorizer.ColorDelegator, 'notify_range')
    +    def test_delete(self, mock_notify):
    +        text = self.text
    +        # Initialize text.
    +        text.insert('insert', 'abcdefghi')
    +        self.assertEqual(text.get('1.0', 'end'), 'abcdefghi\n')
    +        # Delete single character.
    +        text.delete('1.7')
    +        self.assertEqual(text.get('1.0', 'end'), 'abcdefgi\n')
    +        mock_notify.assert_called_with('1.7')
    +        # Delete multiple characters.
    +        text.delete('1.3', '1.6')
    +        self.assertEqual(text.get('1.0', 'end'), 'abcgi\n')
    +        mock_notify.assert_called_with('1.3')
    +
    +    def test_notify_range(self):
    +        text = self.text
    +        color = self.color
    +        eq = self.assertEqual
    +
    +        # Colorizing already scheduled.
    +        save_id = color.after_id
    +        eq(self.root.tk.call('after', 'info', save_id)[1], 'timer')
    +        self.assertFalse(color.colorizing)
    +        self.assertFalse(color.stop_colorizing)
    +        self.assertTrue(color.allow_colorizing)
    +
    +        # Coloring scheduled and colorizing in progress.
    +        color.colorizing = True
    +        color.notify_range('1.0', 'end')
    +        self.assertFalse(color.stop_colorizing)
    +        eq(color.after_id, save_id)
    +
    +        # No colorizing scheduled and colorizing in progress.
    +        text.after_cancel(save_id)
    +        color.after_id = None
    +        color.notify_range('1.0', '1.0+3c')
    +        self.assertTrue(color.stop_colorizing)
    +        self.assertIsNotNone(color.after_id)
    +        eq(self.root.tk.call('after', 'info', color.after_id)[1], 'timer')
    +        # New event scheduled.
    +        self.assertNotEqual(color.after_id, save_id)
    +
    +        # No colorizing scheduled and colorizing off.
    +        text.after_cancel(color.after_id)
    +        color.after_id = None
    +        color.allow_colorizing = False
    +        color.notify_range('1.4', '1.4+10c')
    +        # Nothing scheduled when colorizing is off.
    +        self.assertIsNone(color.after_id)
    +
    +    def test_toggle_colorize_event(self):
    +        color = self.color
    +        eq = self.assertEqual
    +
    +        # Starts with colorizing allowed and scheduled.
    +        self.assertFalse(color.colorizing)
    +        self.assertFalse(color.stop_colorizing)
    +        self.assertTrue(color.allow_colorizing)
    +        eq(self.root.tk.call('after', 'info', color.after_id)[1], 'timer')
    +
    +        # Toggle colorizing off.
    +        color.toggle_colorize_event()
    +        self.assertIsNone(color.after_id)
    +        self.assertFalse(color.colorizing)
    +        self.assertFalse(color.stop_colorizing)
    +        self.assertFalse(color.allow_colorizing)
    +
    +        # Toggle on while colorizing in progress (doesn't add timer).
    +        color.colorizing = True
    +        color.toggle_colorize_event()
    +        self.assertIsNone(color.after_id)
    +        self.assertTrue(color.colorizing)
    +        self.assertFalse(color.stop_colorizing)
    +        self.assertTrue(color.allow_colorizing)
    +
    +        # Toggle off while colorizing in progress.
    +        color.toggle_colorize_event()
    +        self.assertIsNone(color.after_id)
    +        self.assertTrue(color.colorizing)
    +        self.assertTrue(color.stop_colorizing)
    +        self.assertFalse(color.allow_colorizing)
    +
    +        # Toggle on while colorizing not in progress.
    +        color.colorizing = False
    +        color.toggle_colorize_event()
    +        eq(self.root.tk.call('after', 'info', color.after_id)[1], 'timer')
    +        self.assertFalse(color.colorizing)
    +        self.assertTrue(color.stop_colorizing)
    +        self.assertTrue(color.allow_colorizing)
    +
    +    @mock.patch.object(colorizer.ColorDelegator, 'recolorize_main')
    +    def test_recolorize(self, mock_recmain):
    +        text = self.text
    +        color = self.color
    +        eq = self.assertEqual
    +        # Call recolorize manually and not scheduled.
    +        text.after_cancel(color.after_id)
    +
    +        # No delegate.
    +        save_delegate = color.delegate
    +        color.delegate = None
    +        color.recolorize()
    +        mock_recmain.assert_not_called()
    +        color.delegate = save_delegate
    +
    +        # Toggle off colorizing.
    +        color.allow_colorizing = False
    +        color.recolorize()
    +        mock_recmain.assert_not_called()
    +        color.allow_colorizing = True
    +
    +        # Colorizing in progress.
    +        color.colorizing = True
    +        color.recolorize()
    +        mock_recmain.assert_not_called()
    +        color.colorizing = False
    +
    +        # Colorizing is done, but not completed, so rescheduled.
    +        color.recolorize()
    +        self.assertFalse(color.stop_colorizing)
    +        self.assertFalse(color.colorizing)
    +        mock_recmain.assert_called()
    +        eq(mock_recmain.call_count, 1)
    +        # Rescheduled when TODO tag still exists.
    +        eq(self.root.tk.call('after', 'info', color.after_id)[1], 'timer')
    +
    +        # No changes to text, so no scheduling added.
    +        text.tag_remove('TODO', '1.0', 'end')
    +        color.recolorize()
    +        self.assertFalse(color.stop_colorizing)
    +        self.assertFalse(color.colorizing)
    +        mock_recmain.assert_called()
    +        eq(mock_recmain.call_count, 2)
    +        self.assertIsNone(color.after_id)
    +
    +    @mock.patch.object(colorizer.ColorDelegator, 'notify_range')
    +    def test_recolorize_main(self, mock_notify):
    +        text = self.text
    +        color = self.color
    +        eq = self.assertEqual
    +
    +        text.insert('insert', source)
    +        expected = (('1.0', ('KEYWORD',)), ('1.2', ()), ('1.3', ('KEYWORD',)),
    +                    ('1.7', ()), ('1.9', ('BUILTIN',)), ('1.14', ('STRING',)),
    +                    ('1.19', ('COMMENT',)),
    +                    ('2.1', ('KEYWORD',)), ('2.18', ()), ('2.25', ('COMMENT',)),
    +                    ('3.6', ('BUILTIN',)), ('3.12', ('KEYWORD',)), ('3.21', ('COMMENT',)),
    +                    ('4.0', ('KEYWORD',)), ('4.3', ()), ('4.6', ()),
    +                    ('5.2', ('STRING',)), ('5.8', ('KEYWORD',)), ('5.10', ('STRING',)),
    +                    ('6.0', ('KEYWORD',)), ('6.10', ('DEFINITION',)), ('6.11', ()),
    +                    ('7.0', ('STRING',)), ('7.4', ()), ('7.5', ('STRING',)),
    +                    ('7.12', ()), ('7.14', ('STRING',)),
    +                    # SYNC at the end of every line.
    +                    ('1.55', ('SYNC',)), ('2.50', ('SYNC',)), ('3.34', ('SYNC',)),
    +                   )
    +
    +        # Nothing marked to do therefore no tags in text.
    +        text.tag_remove('TODO', '1.0', 'end')
    +        color.recolorize_main()
    +        for tag in text.tag_names():
    +            with self.subTest(tag=tag):
    +                eq(text.tag_ranges(tag), ())
    +
    +        # Source marked for processing.
    +        text.tag_add('TODO', '1.0', 'end')
    +        # Check some indexes.
    +        color.recolorize_main()
    +        for index, expected_tags in expected:
    +            with self.subTest(index=index):
    +                eq(text.tag_names(index), expected_tags)
    +
    +        # Check for some tags for ranges.
    +        eq(text.tag_nextrange('TODO', '1.0'), ())
    +        eq(text.tag_nextrange('KEYWORD', '1.0'), ('1.0', '1.2'))
    +        eq(text.tag_nextrange('COMMENT', '2.0'), ('2.22', '2.43'))
    +        eq(text.tag_nextrange('SYNC', '2.0'), ('2.43', '3.0'))
    +        eq(text.tag_nextrange('STRING', '2.0'), ('4.17', '4.53'))
    +        eq(text.tag_nextrange('STRING', '7.0'), ('7.0', '7.3'))
    +        eq(text.tag_nextrange('STRING', '7.3'), ('7.5', '7.12'))
    +        eq(text.tag_nextrange('STRING', '7.12'), ('7.14', '7.17'))
    +        eq(text.tag_nextrange('STRING', '7.17'), ('7.19', '7.26'))
    +        eq(text.tag_nextrange('SYNC', '7.0'), ('7.26', '9.0'))
    +
    +    @mock.patch.object(colorizer.ColorDelegator, 'recolorize')
    +    @mock.patch.object(colorizer.ColorDelegator, 'notify_range')
    +    def test_removecolors(self, mock_notify, mock_recolorize):
    +        text = self.text
    +        color = self.color
    +        text.insert('insert', source)
    +
    +        color.recolorize_main()
    +        # recolorize_main doesn't add these tags.
    +        text.tag_add("ERROR", "1.0")
    +        text.tag_add("TODO", "1.0")
    +        text.tag_add("hit", "1.0")
    +        for tag in color.tagdefs:
    +            with self.subTest(tag=tag):
    +                self.assertNotEqual(text.tag_ranges(tag), ())
    +
    +        color.removecolors()
    +        for tag in color.tagdefs:
    +            with self.subTest(tag=tag):
    +                self.assertEqual(text.tag_ranges(tag), ())
    +
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_config.py b/PythonLib/full/idlelib/idle_test/test_config.py
    new file mode 100644
    index 00000000..697fda52
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_config.py
    @@ -0,0 +1,805 @@
    +"""Test config, coverage 93%.
    +(100% for IdleConfParser, IdleUserConfParser*, ConfigChanges).
    +* Exception is OSError clause in Save method.
    +Much of IdleConf is also exercised by ConfigDialog and test_configdialog.
    +"""
    +from idlelib import config
    +import sys
    +import os
    +import tempfile
    +from test.support import captured_stderr, findfile
    +import unittest
    +from unittest import mock
    +import idlelib
    +from idlelib.idle_test.mock_idle import Func
    +
    +# Tests should not depend on fortuitous user configurations.
    +# They must not affect actual user .cfg files.
    +# Replace user parsers with empty parsers that cannot be saved
    +# due to getting '' as the filename when created.
    +
    +idleConf = config.idleConf
    +usercfg = idleConf.userCfg
    +testcfg = {}
    +usermain = testcfg['main'] = config.IdleUserConfParser('')
    +userhigh = testcfg['highlight'] = config.IdleUserConfParser('')
    +userkeys = testcfg['keys'] = config.IdleUserConfParser('')
    +userextn = testcfg['extensions'] = config.IdleUserConfParser('')
    +
    +def setUpModule():
    +    idleConf.userCfg = testcfg
    +    idlelib.testing = True
    +
    +def tearDownModule():
    +    idleConf.userCfg = usercfg
    +    idlelib.testing = False
    +
    +
    +class IdleConfParserTest(unittest.TestCase):
    +    """Test that IdleConfParser works"""
    +
    +    config = """
    +        [one]
    +        one = false
    +        two = true
    +        three = 10
    +
    +        [two]
    +        one = a string
    +        two = true
    +        three = false
    +    """
    +
    +    def test_get(self):
    +        parser = config.IdleConfParser('')
    +        parser.read_string(self.config)
    +        eq = self.assertEqual
    +
    +        # Test with type argument.
    +        self.assertIs(parser.Get('one', 'one', type='bool'), False)
    +        self.assertIs(parser.Get('one', 'two', type='bool'), True)
    +        eq(parser.Get('one', 'three', type='int'), 10)
    +        eq(parser.Get('two', 'one'), 'a string')
    +        self.assertIs(parser.Get('two', 'two', type='bool'), True)
    +        self.assertIs(parser.Get('two', 'three', type='bool'), False)
    +
    +        # Test without type should fallback to string.
    +        eq(parser.Get('two', 'two'), 'true')
    +        eq(parser.Get('two', 'three'), 'false')
    +
    +        # If option not exist, should return None, or default.
    +        self.assertIsNone(parser.Get('not', 'exist'))
    +        eq(parser.Get('not', 'exist', default='DEFAULT'), 'DEFAULT')
    +
    +    def test_get_option_list(self):
    +        parser = config.IdleConfParser('')
    +        parser.read_string(self.config)
    +        get_list = parser.GetOptionList
    +        self.assertCountEqual(get_list('one'), ['one', 'two', 'three'])
    +        self.assertCountEqual(get_list('two'), ['one', 'two', 'three'])
    +        self.assertEqual(get_list('not exist'), [])
    +
    +    def test_load_nothing(self):
    +        parser = config.IdleConfParser('')
    +        parser.Load()
    +        self.assertEqual(parser.sections(), [])
    +
    +    def test_load_file(self):
    +        # Borrow test/cfgparser.1 from test_configparser.
    +        config_path = findfile('cfgparser.1')
    +        parser = config.IdleConfParser(config_path)
    +        parser.Load()
    +
    +        self.assertEqual(parser.Get('Foo Bar', 'foo'), 'newbar')
    +        self.assertEqual(parser.GetOptionList('Foo Bar'), ['foo'])
    +
    +
    +class IdleUserConfParserTest(unittest.TestCase):
    +    """Test that IdleUserConfParser works"""
    +
    +    def new_parser(self, path=''):
    +        return config.IdleUserConfParser(path)
    +
    +    def test_set_option(self):
    +        parser = self.new_parser()
    +        parser.add_section('Foo')
    +        # Setting new option in existing section should return True.
    +        self.assertTrue(parser.SetOption('Foo', 'bar', 'true'))
    +        # Setting existing option with same value should return False.
    +        self.assertFalse(parser.SetOption('Foo', 'bar', 'true'))
    +        # Setting exiting option with new value should return True.
    +        self.assertTrue(parser.SetOption('Foo', 'bar', 'false'))
    +        self.assertEqual(parser.Get('Foo', 'bar'), 'false')
    +
    +        # Setting option in new section should create section and return True.
    +        self.assertTrue(parser.SetOption('Bar', 'bar', 'true'))
    +        self.assertCountEqual(parser.sections(), ['Bar', 'Foo'])
    +        self.assertEqual(parser.Get('Bar', 'bar'), 'true')
    +
    +    def test_remove_option(self):
    +        parser = self.new_parser()
    +        parser.AddSection('Foo')
    +        parser.SetOption('Foo', 'bar', 'true')
    +
    +        self.assertTrue(parser.RemoveOption('Foo', 'bar'))
    +        self.assertFalse(parser.RemoveOption('Foo', 'bar'))
    +        self.assertFalse(parser.RemoveOption('Not', 'Exist'))
    +
    +    def test_add_section(self):
    +        parser = self.new_parser()
    +        self.assertEqual(parser.sections(), [])
    +
    +        # Should not add duplicate section.
    +        # Configparser raises DuplicateError, IdleParser not.
    +        parser.AddSection('Foo')
    +        parser.AddSection('Foo')
    +        parser.AddSection('Bar')
    +        self.assertCountEqual(parser.sections(), ['Bar', 'Foo'])
    +
    +    def test_remove_empty_sections(self):
    +        parser = self.new_parser()
    +
    +        parser.AddSection('Foo')
    +        parser.AddSection('Bar')
    +        parser.SetOption('Idle', 'name', 'val')
    +        self.assertCountEqual(parser.sections(), ['Bar', 'Foo', 'Idle'])
    +        parser.RemoveEmptySections()
    +        self.assertEqual(parser.sections(), ['Idle'])
    +
    +    def test_is_empty(self):
    +        parser = self.new_parser()
    +
    +        parser.AddSection('Foo')
    +        parser.AddSection('Bar')
    +        self.assertTrue(parser.IsEmpty())
    +        self.assertEqual(parser.sections(), [])
    +
    +        parser.SetOption('Foo', 'bar', 'false')
    +        parser.AddSection('Bar')
    +        self.assertFalse(parser.IsEmpty())
    +        self.assertCountEqual(parser.sections(), ['Foo'])
    +
    +    def test_save(self):
    +        with tempfile.TemporaryDirectory() as tdir:
    +            path = os.path.join(tdir, 'test.cfg')
    +            parser = self.new_parser(path)
    +            parser.AddSection('Foo')
    +            parser.SetOption('Foo', 'bar', 'true')
    +
    +            # Should save to path when config is not empty.
    +            self.assertFalse(os.path.exists(path))
    +            parser.Save()
    +            self.assertTrue(os.path.exists(path))
    +
    +            # Should remove the file from disk when config is empty.
    +            parser.remove_section('Foo')
    +            parser.Save()
    +            self.assertFalse(os.path.exists(path))
    +
    +
    +class IdleConfTest(unittest.TestCase):
    +    """Test for idleConf"""
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        cls.config_string = {}
    +
    +        conf = config.IdleConf(_utest=True)
    +        if __name__ != '__main__':
    +            idle_dir = os.path.dirname(__file__)
    +        else:
    +            idle_dir = os.path.abspath(sys.path[0])
    +        for ctype in conf.config_types:
    +            config_path = os.path.join(idle_dir, '../config-%s.def' % ctype)
    +            with open(config_path, 'r') as f:
    +                cls.config_string[ctype] = f.read()
    +
    +        cls.orig_warn = config._warn
    +        config._warn = Func()
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        config._warn = cls.orig_warn
    +
    +    def new_config(self, _utest=False):
    +        return config.IdleConf(_utest=_utest)
    +
    +    def mock_config(self):
    +        """Return a mocked idleConf
    +
    +        Both default and user config used the same config-*.def
    +        """
    +        conf = config.IdleConf(_utest=True)
    +        for ctype in conf.config_types:
    +            conf.defaultCfg[ctype] = config.IdleConfParser('')
    +            conf.defaultCfg[ctype].read_string(self.config_string[ctype])
    +            conf.userCfg[ctype] = config.IdleUserConfParser('')
    +            conf.userCfg[ctype].read_string(self.config_string[ctype])
    +
    +        return conf
    +
    +    @unittest.skipIf(sys.platform.startswith('win'), 'this is test for unix system')
    +    def test_get_user_cfg_dir_unix(self):
    +        # Test to get user config directory under unix.
    +        conf = self.new_config(_utest=True)
    +
    +        # Check normal way should success
    +        with mock.patch('os.path.expanduser', return_value='/home/foo'):
    +            with mock.patch('os.path.exists', return_value=True):
    +                self.assertEqual(conf.GetUserCfgDir(), '/home/foo/.idlerc')
    +
    +        # Check os.getcwd should success
    +        with mock.patch('os.path.expanduser', return_value='~'):
    +            with mock.patch('os.getcwd', return_value='/home/foo/cpython'):
    +                with mock.patch('os.mkdir'):
    +                    self.assertEqual(conf.GetUserCfgDir(),
    +                                     '/home/foo/cpython/.idlerc')
    +
    +        # Check user dir not exists and created failed should raise SystemExit
    +        with mock.patch('os.path.join', return_value='/path/not/exists'):
    +            with self.assertRaises(SystemExit):
    +                with self.assertRaises(FileNotFoundError):
    +                    conf.GetUserCfgDir()
    +
    +    @unittest.skipIf(not sys.platform.startswith('win'), 'this is test for Windows system')
    +    def test_get_user_cfg_dir_windows(self):
    +        # Test to get user config directory under Windows.
    +        conf = self.new_config(_utest=True)
    +
    +        # Check normal way should success
    +        with mock.patch('os.path.expanduser', return_value='C:\\foo'):
    +            with mock.patch('os.path.exists', return_value=True):
    +                self.assertEqual(conf.GetUserCfgDir(), 'C:\\foo\\.idlerc')
    +
    +        # Check os.getcwd should success
    +        with mock.patch('os.path.expanduser', return_value='~'):
    +            with mock.patch('os.getcwd', return_value='C:\\foo\\cpython'):
    +                with mock.patch('os.mkdir'):
    +                    self.assertEqual(conf.GetUserCfgDir(),
    +                                     'C:\\foo\\cpython\\.idlerc')
    +
    +        # Check user dir not exists and created failed should raise SystemExit
    +        with mock.patch('os.path.join', return_value='/path/not/exists'):
    +            with self.assertRaises(SystemExit):
    +                with self.assertRaises(FileNotFoundError):
    +                    conf.GetUserCfgDir()
    +
    +    def test_create_config_handlers(self):
    +        conf = self.new_config(_utest=True)
    +
    +        # Mock out idle_dir
    +        idle_dir = '/home/foo'
    +        with mock.patch.dict({'__name__': '__foo__'}):
    +            with mock.patch('os.path.dirname', return_value=idle_dir):
    +                conf.CreateConfigHandlers()
    +
    +        # Check keys are equal
    +        self.assertCountEqual(conf.defaultCfg.keys(), conf.config_types)
    +        self.assertCountEqual(conf.userCfg.keys(), conf.config_types)
    +
    +        # Check conf parser are correct type
    +        for default_parser in conf.defaultCfg.values():
    +            self.assertIsInstance(default_parser, config.IdleConfParser)
    +        for user_parser in conf.userCfg.values():
    +            self.assertIsInstance(user_parser, config.IdleUserConfParser)
    +
    +        # Check config path are correct
    +        for cfg_type, parser in conf.defaultCfg.items():
    +            self.assertEqual(parser.file,
    +                             os.path.join(idle_dir, f'config-{cfg_type}.def'))
    +        for cfg_type, parser in conf.userCfg.items():
    +            self.assertEqual(parser.file,
    +                             os.path.join(conf.userdir or '#', f'config-{cfg_type}.cfg'))
    +
    +    def test_load_cfg_files(self):
    +        conf = self.new_config(_utest=True)
    +
    +        # Borrow test/cfgparser.1 from test_configparser.
    +        config_path = findfile('cfgparser.1')
    +        conf.defaultCfg['foo'] = config.IdleConfParser(config_path)
    +        conf.userCfg['foo'] = config.IdleUserConfParser(config_path)
    +
    +        # Load all config from path
    +        conf.LoadCfgFiles()
    +
    +        eq = self.assertEqual
    +
    +        # Check defaultCfg is loaded
    +        eq(conf.defaultCfg['foo'].Get('Foo Bar', 'foo'), 'newbar')
    +        eq(conf.defaultCfg['foo'].GetOptionList('Foo Bar'), ['foo'])
    +
    +        # Check userCfg is loaded
    +        eq(conf.userCfg['foo'].Get('Foo Bar', 'foo'), 'newbar')
    +        eq(conf.userCfg['foo'].GetOptionList('Foo Bar'), ['foo'])
    +
    +    def test_save_user_cfg_files(self):
    +        conf = self.mock_config()
    +
    +        with mock.patch('idlelib.config.IdleUserConfParser.Save') as m:
    +            conf.SaveUserCfgFiles()
    +            self.assertEqual(m.call_count, len(conf.userCfg))
    +
    +    def test_get_option(self):
    +        conf = self.mock_config()
    +
    +        eq = self.assertEqual
    +        eq(conf.GetOption('main', 'EditorWindow', 'width'), '80')
    +        eq(conf.GetOption('main', 'EditorWindow', 'width', type='int'), 80)
    +        with mock.patch('idlelib.config._warn') as _warn:
    +            eq(conf.GetOption('main', 'EditorWindow', 'font', type='int'), None)
    +            eq(conf.GetOption('main', 'EditorWindow', 'NotExists'), None)
    +            eq(conf.GetOption('main', 'EditorWindow', 'NotExists', default='NE'), 'NE')
    +            eq(_warn.call_count, 4)
    +
    +    def test_set_option(self):
    +        conf = self.mock_config()
    +
    +        conf.SetOption('main', 'Foo', 'bar', 'newbar')
    +        self.assertEqual(conf.GetOption('main', 'Foo', 'bar'), 'newbar')
    +
    +    def test_get_section_list(self):
    +        conf = self.mock_config()
    +
    +        self.assertCountEqual(
    +            conf.GetSectionList('default', 'main'),
    +            ['General', 'EditorWindow', 'PyShell', 'Indent', 'Theme',
    +             'Keys', 'History', 'HelpFiles'])
    +        self.assertCountEqual(
    +            conf.GetSectionList('user', 'main'),
    +            ['General', 'EditorWindow', 'PyShell', 'Indent', 'Theme',
    +             'Keys', 'History', 'HelpFiles'])
    +
    +        with self.assertRaises(config.InvalidConfigSet):
    +            conf.GetSectionList('foobar', 'main')
    +        with self.assertRaises(config.InvalidConfigType):
    +            conf.GetSectionList('default', 'notexists')
    +
    +    def test_get_highlight(self):
    +        conf = self.mock_config()
    +
    +        eq = self.assertEqual
    +        eq(conf.GetHighlight('IDLE Classic', 'normal'), {'foreground': '#000000',
    +                                                         'background': '#ffffff'})
    +
    +        # Test cursor (this background should be normal-background)
    +        eq(conf.GetHighlight('IDLE Classic', 'cursor'), {'foreground': 'black',
    +                                                         'background': '#ffffff'})
    +
    +        # Test get user themes
    +        conf.SetOption('highlight', 'Foobar', 'normal-foreground', '#747474')
    +        conf.SetOption('highlight', 'Foobar', 'normal-background', '#171717')
    +        with mock.patch('idlelib.config._warn'):
    +            eq(conf.GetHighlight('Foobar', 'normal'), {'foreground': '#747474',
    +                                                       'background': '#171717'})
    +
    +    def test_get_theme_dict(self):
    +        # TODO: finish.
    +        conf = self.mock_config()
    +
    +        # These two should be the same
    +        self.assertEqual(
    +            conf.GetThemeDict('default', 'IDLE Classic'),
    +            conf.GetThemeDict('user', 'IDLE Classic'))
    +
    +        with self.assertRaises(config.InvalidTheme):
    +            conf.GetThemeDict('bad', 'IDLE Classic')
    +
    +    def test_get_current_theme_and_keys(self):
    +        conf = self.mock_config()
    +
    +        self.assertEqual(conf.CurrentTheme(), conf.current_colors_and_keys('Theme'))
    +        self.assertEqual(conf.CurrentKeys(), conf.current_colors_and_keys('Keys'))
    +
    +    def test_current_colors_and_keys(self):
    +        conf = self.mock_config()
    +
    +        self.assertEqual(conf.current_colors_and_keys('Theme'), 'IDLE Classic')
    +
    +    def test_default_keys(self):
    +        current_platform = sys.platform
    +        conf = self.new_config(_utest=True)
    +
    +        sys.platform = 'win32'
    +        self.assertEqual(conf.default_keys(), 'IDLE Classic Windows')
    +
    +        sys.platform = 'darwin'
    +        self.assertEqual(conf.default_keys(), 'IDLE Classic OSX')
    +
    +        sys.platform = 'some-linux'
    +        self.assertEqual(conf.default_keys(), 'IDLE Modern Unix')
    +
    +        # Restore platform
    +        sys.platform = current_platform
    +
    +    def test_get_extensions(self):
    +        userextn.read_string('''
    +            [ZzDummy]
    +            enable = True
    +            [DISABLE]
    +            enable = False
    +            ''')
    +        eq = self.assertEqual
    +        iGE = idleConf.GetExtensions
    +        eq(iGE(shell_only=True), [])
    +        eq(iGE(), ['ZzDummy'])
    +        eq(iGE(editor_only=True), ['ZzDummy'])
    +        eq(iGE(active_only=False), ['ZzDummy', 'DISABLE'])
    +        eq(iGE(active_only=False, editor_only=True), ['ZzDummy', 'DISABLE'])
    +        userextn.remove_section('ZzDummy')
    +        userextn.remove_section('DISABLE')
    +
    +
    +    def test_remove_key_bind_names(self):
    +        conf = self.mock_config()
    +
    +        self.assertCountEqual(
    +            conf.RemoveKeyBindNames(conf.GetSectionList('default', 'extensions')),
    +            ['AutoComplete', 'CodeContext', 'FormatParagraph', 'ParenMatch', 'ZzDummy'])
    +
    +    def test_get_extn_name_for_event(self):
    +        userextn.read_string('''
    +            [ZzDummy]
    +            enable = True
    +            ''')
    +        eq = self.assertEqual
    +        eq(idleConf.GetExtnNameForEvent('z-in'), 'ZzDummy')
    +        eq(idleConf.GetExtnNameForEvent('z-out'), None)
    +        userextn.remove_section('ZzDummy')
    +
    +    def test_get_extension_keys(self):
    +        userextn.read_string('''
    +            [ZzDummy]
    +            enable = True
    +            ''')
    +        self.assertEqual(idleConf.GetExtensionKeys('ZzDummy'),
    +           {'<>': ['']})
    +        userextn.remove_section('ZzDummy')
    +# need option key test
    +##        key = [''] if sys.platform == 'darwin' else ['']
    +##        eq(conf.GetExtensionKeys('ZoomHeight'), {'<>': key})
    +
    +    def test_get_extension_bindings(self):
    +        userextn.read_string('''
    +            [ZzDummy]
    +            enable = True
    +            ''')
    +        eq = self.assertEqual
    +        iGEB = idleConf.GetExtensionBindings
    +        eq(iGEB('NotExists'), {})
    +        expect = {'<>': [''],
    +                  '<>': ['']}
    +        eq(iGEB('ZzDummy'), expect)
    +        userextn.remove_section('ZzDummy')
    +
    +    def test_get_keybinding(self):
    +        conf = self.mock_config()
    +
    +        eq = self.assertEqual
    +        eq(conf.GetKeyBinding('IDLE Modern Unix', '<>'),
    +            ['', ''])
    +        eq(conf.GetKeyBinding('IDLE Classic Unix', '<>'),
    +            ['', ''])
    +        eq(conf.GetKeyBinding('IDLE Classic Windows', '<>'),
    +            ['', ''])
    +        eq(conf.GetKeyBinding('IDLE Classic Mac', '<>'), [''])
    +        eq(conf.GetKeyBinding('IDLE Classic OSX', '<>'), [''])
    +
    +        # Test keybinding not exists
    +        eq(conf.GetKeyBinding('NOT EXISTS', '<>'), [])
    +        eq(conf.GetKeyBinding('IDLE Modern Unix', 'NOT EXISTS'), [])
    +
    +    def test_get_current_keyset(self):
    +        current_platform = sys.platform
    +        conf = self.mock_config()
    +
    +        # Ensure that platform isn't darwin
    +        sys.platform = 'some-linux'
    +        self.assertEqual(conf.GetCurrentKeySet(), conf.GetKeySet(conf.CurrentKeys()))
    +
    +        # This should not be the same, since replace ')
    +        self.assertEqual(conf.GetKeySet('IDLE Modern Unix')['<>'], '')
    +
    +    def test_is_core_binding(self):
    +        # XXX: Should move out the core keys to config file or other place
    +        conf = self.mock_config()
    +
    +        self.assertTrue(conf.IsCoreBinding('copy'))
    +        self.assertTrue(conf.IsCoreBinding('cut'))
    +        self.assertTrue(conf.IsCoreBinding('del-word-right'))
    +        self.assertFalse(conf.IsCoreBinding('not-exists'))
    +
    +    def test_extra_help_source_list(self):
    +        # Test GetExtraHelpSourceList and GetAllExtraHelpSourcesList in same
    +        # place to prevent prepare input data twice.
    +        conf = self.mock_config()
    +
    +        # Test default with no extra help source
    +        self.assertEqual(conf.GetExtraHelpSourceList('default'), [])
    +        self.assertEqual(conf.GetExtraHelpSourceList('user'), [])
    +        with self.assertRaises(config.InvalidConfigSet):
    +            self.assertEqual(conf.GetExtraHelpSourceList('bad'), [])
    +        self.assertCountEqual(
    +            conf.GetAllExtraHelpSourcesList(),
    +            conf.GetExtraHelpSourceList('default') + conf.GetExtraHelpSourceList('user'))
    +
    +        # Add help source to user config
    +        conf.userCfg['main'].SetOption('HelpFiles', '4', 'Python;https://python.org')  # This is bad input
    +        conf.userCfg['main'].SetOption('HelpFiles', '3', 'Python:https://python.org')  # This is bad input
    +        conf.userCfg['main'].SetOption('HelpFiles', '2', 'Pillow;https://pillow.readthedocs.io/en/latest/')
    +        conf.userCfg['main'].SetOption('HelpFiles', '1', 'IDLE;C:/Programs/Python36/Lib/idlelib/help.html')
    +        self.assertEqual(conf.GetExtraHelpSourceList('user'),
    +                         [('IDLE', 'C:/Programs/Python36/Lib/idlelib/help.html', '1'),
    +                          ('Pillow', 'https://pillow.readthedocs.io/en/latest/', '2'),
    +                          ('Python', 'https://python.org', '4')])
    +        self.assertCountEqual(
    +            conf.GetAllExtraHelpSourcesList(),
    +            conf.GetExtraHelpSourceList('default') + conf.GetExtraHelpSourceList('user'))
    +
    +    def test_get_font(self):
    +        from test.support import requires
    +        from tkinter import Tk
    +        from tkinter.font import Font
    +        conf = self.mock_config()
    +
    +        requires('gui')
    +        root = Tk()
    +        root.withdraw()
    +
    +        f = Font.actual(Font(name='TkFixedFont', exists=True, root=root))
    +        self.assertEqual(
    +            conf.GetFont(root, 'main', 'EditorWindow'),
    +            (f['family'], 10 if f['size'] <= 0 else f['size'], f['weight']))
    +
    +        # Cleanup root
    +        root.destroy()
    +        del root
    +
    +    def test_get_core_keys(self):
    +        conf = self.mock_config()
    +
    +        eq = self.assertEqual
    +        eq(conf.GetCoreKeys()['<>'], [''])
    +        eq(conf.GetCoreKeys()['<>'], ['', ''])
    +        eq(conf.GetCoreKeys()['<>'], [''])
    +        eq(conf.GetCoreKeys('IDLE Classic Windows')['<>'],
    +           ['', ''])
    +        eq(conf.GetCoreKeys('IDLE Classic OSX')['<>'], [''])
    +        eq(conf.GetCoreKeys('IDLE Classic Unix')['<>'],
    +           ['', ''])
    +        eq(conf.GetCoreKeys('IDLE Modern Unix')['<>'],
    +            ['', ''])
    +
    +
    +class CurrentColorKeysTest(unittest.TestCase):
    +    """ Test colorkeys function with user config [Theme] and [Keys] patterns.
    +
    +        colorkeys = config.IdleConf.current_colors_and_keys
    +        Test all patterns written by IDLE and some errors
    +        Item 'default' should really be 'builtin' (versus 'custom).
    +    """
    +    colorkeys = idleConf.current_colors_and_keys
    +    default_theme = 'IDLE Classic'
    +    default_keys = idleConf.default_keys()
    +
    +    def test_old_builtin_theme(self):
    +        # On initial installation, user main is blank.
    +        self.assertEqual(self.colorkeys('Theme'), self.default_theme)
    +        # For old default, name2 must be blank.
    +        usermain.read_string('''
    +            [Theme]
    +            default = True
    +            ''')
    +        # IDLE omits 'name' for default old builtin theme.
    +        self.assertEqual(self.colorkeys('Theme'), self.default_theme)
    +        # IDLE adds 'name' for non-default old builtin theme.
    +        usermain['Theme']['name'] = 'IDLE New'
    +        self.assertEqual(self.colorkeys('Theme'), 'IDLE New')
    +        # Erroneous non-default old builtin reverts to default.
    +        usermain['Theme']['name'] = 'non-existent'
    +        self.assertEqual(self.colorkeys('Theme'), self.default_theme)
    +        usermain.remove_section('Theme')
    +
    +    def test_new_builtin_theme(self):
    +        # IDLE writes name2 for new builtins.
    +        usermain.read_string('''
    +            [Theme]
    +            default = True
    +            name2 = IDLE Dark
    +            ''')
    +        self.assertEqual(self.colorkeys('Theme'), 'IDLE Dark')
    +        # Leftover 'name', not removed, is ignored.
    +        usermain['Theme']['name'] = 'IDLE New'
    +        self.assertEqual(self.colorkeys('Theme'), 'IDLE Dark')
    +        # Erroneous non-default new builtin reverts to default.
    +        usermain['Theme']['name2'] = 'non-existent'
    +        self.assertEqual(self.colorkeys('Theme'), self.default_theme)
    +        usermain.remove_section('Theme')
    +
    +    def test_user_override_theme(self):
    +        # Erroneous custom name (no definition) reverts to default.
    +        usermain.read_string('''
    +            [Theme]
    +            default = False
    +            name = Custom Dark
    +            ''')
    +        self.assertEqual(self.colorkeys('Theme'), self.default_theme)
    +        # Custom name is valid with matching Section name.
    +        userhigh.read_string('[Custom Dark]\na=b')
    +        self.assertEqual(self.colorkeys('Theme'), 'Custom Dark')
    +        # Name2 is ignored.
    +        usermain['Theme']['name2'] = 'non-existent'
    +        self.assertEqual(self.colorkeys('Theme'), 'Custom Dark')
    +        usermain.remove_section('Theme')
    +        userhigh.remove_section('Custom Dark')
    +
    +    def test_old_builtin_keys(self):
    +        # On initial installation, user main is blank.
    +        self.assertEqual(self.colorkeys('Keys'), self.default_keys)
    +        # For old default, name2 must be blank, name is always used.
    +        usermain.read_string('''
    +            [Keys]
    +            default = True
    +            name = IDLE Classic Unix
    +            ''')
    +        self.assertEqual(self.colorkeys('Keys'), 'IDLE Classic Unix')
    +        # Erroneous non-default old builtin reverts to default.
    +        usermain['Keys']['name'] = 'non-existent'
    +        self.assertEqual(self.colorkeys('Keys'), self.default_keys)
    +        usermain.remove_section('Keys')
    +
    +    def test_new_builtin_keys(self):
    +        # IDLE writes name2 for new builtins.
    +        usermain.read_string('''
    +            [Keys]
    +            default = True
    +            name2 = IDLE Modern Unix
    +            ''')
    +        self.assertEqual(self.colorkeys('Keys'), 'IDLE Modern Unix')
    +        # Leftover 'name', not removed, is ignored.
    +        usermain['Keys']['name'] = 'IDLE Classic Unix'
    +        self.assertEqual(self.colorkeys('Keys'), 'IDLE Modern Unix')
    +        # Erroneous non-default new builtin reverts to default.
    +        usermain['Keys']['name2'] = 'non-existent'
    +        self.assertEqual(self.colorkeys('Keys'), self.default_keys)
    +        usermain.remove_section('Keys')
    +
    +    def test_user_override_keys(self):
    +        # Erroneous custom name (no definition) reverts to default.
    +        usermain.read_string('''
    +            [Keys]
    +            default = False
    +            name = Custom Keys
    +            ''')
    +        self.assertEqual(self.colorkeys('Keys'), self.default_keys)
    +        # Custom name is valid with matching Section name.
    +        userkeys.read_string('[Custom Keys]\na=b')
    +        self.assertEqual(self.colorkeys('Keys'), 'Custom Keys')
    +        # Name2 is ignored.
    +        usermain['Keys']['name2'] = 'non-existent'
    +        self.assertEqual(self.colorkeys('Keys'), 'Custom Keys')
    +        usermain.remove_section('Keys')
    +        userkeys.remove_section('Custom Keys')
    +
    +
    +class ChangesTest(unittest.TestCase):
    +
    +    empty = {'main':{}, 'highlight':{}, 'keys':{}, 'extensions':{}}
    +
    +    def load(self):  # Test_add_option verifies that this works.
    +        changes = self.changes
    +        changes.add_option('main', 'Msec', 'mitem', 'mval')
    +        changes.add_option('highlight', 'Hsec', 'hitem', 'hval')
    +        changes.add_option('keys', 'Ksec', 'kitem', 'kval')
    +        return changes
    +
    +    loaded = {'main': {'Msec': {'mitem': 'mval'}},
    +              'highlight': {'Hsec': {'hitem': 'hval'}},
    +              'keys': {'Ksec': {'kitem':'kval'}},
    +              'extensions': {}}
    +
    +    def setUp(self):
    +        self.changes = config.ConfigChanges()
    +
    +    def test_init(self):
    +        self.assertEqual(self.changes, self.empty)
    +
    +    def test_add_option(self):
    +        changes = self.load()
    +        self.assertEqual(changes, self.loaded)
    +        changes.add_option('main', 'Msec', 'mitem', 'mval')
    +        self.assertEqual(changes, self.loaded)
    +
    +    def test_save_option(self):  # Static function does not touch changes.
    +        save_option = self.changes.save_option
    +        self.assertTrue(save_option('main', 'Indent', 'what', '0'))
    +        self.assertFalse(save_option('main', 'Indent', 'what', '0'))
    +        self.assertEqual(usermain['Indent']['what'], '0')
    +
    +        self.assertTrue(save_option('main', 'Indent', 'use-spaces', '0'))
    +        self.assertEqual(usermain['Indent']['use-spaces'], '0')
    +        self.assertTrue(save_option('main', 'Indent', 'use-spaces', '1'))
    +        self.assertFalse(usermain.has_option('Indent', 'use-spaces'))
    +        usermain.remove_section('Indent')
    +
    +    def test_save_added(self):
    +        changes = self.load()
    +        self.assertTrue(changes.save_all())
    +        self.assertEqual(usermain['Msec']['mitem'], 'mval')
    +        self.assertEqual(userhigh['Hsec']['hitem'], 'hval')
    +        self.assertEqual(userkeys['Ksec']['kitem'], 'kval')
    +        changes.add_option('main', 'Msec', 'mitem', 'mval')
    +        self.assertFalse(changes.save_all())
    +        usermain.remove_section('Msec')
    +        userhigh.remove_section('Hsec')
    +        userkeys.remove_section('Ksec')
    +
    +    def test_save_help(self):
    +        # Any change to HelpFiles overwrites entire section.
    +        changes = self.changes
    +        changes.save_option('main', 'HelpFiles', 'IDLE', 'idledoc')
    +        changes.add_option('main', 'HelpFiles', 'ELDI', 'codeldi')
    +        changes.save_all()
    +        self.assertFalse(usermain.has_option('HelpFiles', 'IDLE'))
    +        self.assertTrue(usermain.has_option('HelpFiles', 'ELDI'))
    +
    +    def test_save_default(self):  # Cover 2nd and 3rd false branches.
    +        changes = self.changes
    +        changes.add_option('main', 'Indent', 'use-spaces', '1')
    +        # save_option returns False; cfg_type_changed remains False.
    +
    +    # TODO: test that save_all calls usercfg Saves.
    +
    +    def test_delete_section(self):
    +        changes = self.load()
    +        changes.delete_section('main', 'fake')  # Test no exception.
    +        self.assertEqual(changes, self.loaded)  # Test nothing deleted.
    +        for cfgtype, section in (('main', 'Msec'), ('keys', 'Ksec')):
    +            testcfg[cfgtype].SetOption(section, 'name', 'value')
    +            changes.delete_section(cfgtype, section)
    +            with self.assertRaises(KeyError):
    +                changes[cfgtype][section]  # Test section gone from changes
    +                testcfg[cfgtype][section]  # and from mock userCfg.
    +        # TODO test for save call.
    +
    +    def test_clear(self):
    +        changes = self.load()
    +        changes.clear()
    +        self.assertEqual(changes, self.empty)
    +
    +
    +class WarningTest(unittest.TestCase):
    +
    +    def test_warn(self):
    +        Equal = self.assertEqual
    +        config._warned = set()
    +        with captured_stderr() as stderr:
    +            config._warn('warning', 'key')
    +        Equal(config._warned, {('warning','key')})
    +        Equal(stderr.getvalue(), 'warning'+'\n')
    +        with captured_stderr() as stderr:
    +            config._warn('warning', 'key')
    +        Equal(stderr.getvalue(), '')
    +        with captured_stderr() as stderr:
    +            config._warn('warn2', 'yek')
    +        Equal(config._warned, {('warning','key'), ('warn2','yek')})
    +        Equal(stderr.getvalue(), 'warn2'+'\n')
    +
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_config_key.py b/PythonLib/full/idlelib/idle_test/test_config_key.py
    new file mode 100644
    index 00000000..b7fe7fd6
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_config_key.py
    @@ -0,0 +1,291 @@
    +"""Test config_key, coverage 98%.
    +
    +Coverage is effectively 100%.  Tkinter dialog is mocked, Mac-only line
    +may be skipped, and dummy function in bind test should not be called.
    +Not tested: exit with 'self.advanced or self.keys_ok(keys)) ...' False.
    +"""
    +
    +from idlelib import config_key
    +from test.support import requires
    +import unittest
    +from unittest import mock
    +from tkinter import Tk, TclError
    +from idlelib.idle_test.mock_idle import Func
    +from idlelib.idle_test.mock_tk import Mbox_func
    +
    +gkd = config_key.GetKeysDialog
    +
    +
    +class ValidationTest(unittest.TestCase):
    +    "Test validation methods: ok, keys_ok, bind_ok."
    +
    +    class Validator(gkd):
    +        def __init__(self, *args, **kwargs):
    +            config_key.GetKeysDialog.__init__(self, *args, **kwargs)
    +            class list_keys_final:
    +                get = Func()
    +            self.list_keys_final = list_keys_final
    +        get_modifiers = Func()
    +        showerror = Mbox_func()
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        requires('gui')
    +        cls.root = Tk()
    +        cls.root.withdraw()
    +        keylist = [[''], ['', '']]
    +        cls.dialog = cls.Validator(
    +            cls.root, 'Title', '<>', keylist, _utest=True)
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        cls.dialog.cancel()
    +        cls.root.update_idletasks()
    +        cls.root.destroy()
    +        del cls.dialog, cls.root
    +
    +    def setUp(self):
    +        self.dialog.showerror.message = ''
    +    # A test that needs a particular final key value should set it.
    +    # A test that sets a non-blank modifier list should reset it to [].
    +
    +    def test_ok_empty(self):
    +        self.dialog.key_string.set(' ')
    +        self.dialog.ok()
    +        self.assertEqual(self.dialog.result, '')
    +        self.assertEqual(self.dialog.showerror.message, 'No key specified.')
    +
    +    def test_ok_good(self):
    +        self.dialog.key_string.set('')
    +        self.dialog.list_keys_final.get.result = 'F11'
    +        self.dialog.ok()
    +        self.assertEqual(self.dialog.result, '')
    +        self.assertEqual(self.dialog.showerror.message, '')
    +
    +    def test_keys_no_ending(self):
    +        self.assertFalse(self.dialog.keys_ok(''))
    +        self.assertIn('No modifier', self.dialog.showerror.message)
    +
    +    def test_keys_no_modifier_ok(self):
    +        self.dialog.list_keys_final.get.result = 'F11'
    +        self.assertTrue(self.dialog.keys_ok(''))
    +        self.assertEqual(self.dialog.showerror.message, '')
    +
    +    def test_keys_shift_bad(self):
    +        self.dialog.list_keys_final.get.result = 'a'
    +        self.dialog.get_modifiers.result = ['Shift']
    +        self.assertFalse(self.dialog.keys_ok(''))
    +        self.assertIn('shift modifier', self.dialog.showerror.message)
    +        self.dialog.get_modifiers.result = []
    +
    +    def test_keys_dup(self):
    +        for mods, final, seq in (([], 'F12', ''),
    +                                 (['Control'], 'x', ''),
    +                                 (['Control'], 'X', '')):
    +            with self.subTest(m=mods, f=final, s=seq):
    +                self.dialog.list_keys_final.get.result = final
    +                self.dialog.get_modifiers.result = mods
    +                self.assertFalse(self.dialog.keys_ok(seq))
    +                self.assertIn('already in use', self.dialog.showerror.message)
    +        self.dialog.get_modifiers.result = []
    +
    +    def test_bind_ok(self):
    +        self.assertTrue(self.dialog.bind_ok(''))
    +        self.assertEqual(self.dialog.showerror.message, '')
    +
    +    def test_bind_not_ok(self):
    +        self.assertFalse(self.dialog.bind_ok(''))
    +        self.assertIn('not accepted', self.dialog.showerror.message)
    +
    +
    +class ToggleLevelTest(unittest.TestCase):
    +    "Test toggle between Basic and Advanced frames."
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        requires('gui')
    +        cls.root = Tk()
    +        cls.root.withdraw()
    +        cls.dialog = gkd(cls.root, 'Title', '<>', [], _utest=True)
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        cls.dialog.cancel()
    +        cls.root.update_idletasks()
    +        cls.root.destroy()
    +        del cls.dialog, cls.root
    +
    +    def test_toggle_level(self):
    +        dialog = self.dialog
    +
    +        def stackorder():
    +            """Get the stack order of the children of the frame.
    +
    +            winfo_children() stores the children in stack order, so
    +            this can be used to check whether a frame is above or
    +            below another one.
    +            """
    +            for index, child in enumerate(dialog.frame.winfo_children()):
    +                if child._name == 'keyseq_basic':
    +                    basic = index
    +                if child._name == 'keyseq_advanced':
    +                    advanced = index
    +            return basic, advanced
    +
    +        # New window starts at basic level.
    +        self.assertFalse(dialog.advanced)
    +        self.assertIn('Advanced', dialog.button_level['text'])
    +        basic, advanced = stackorder()
    +        self.assertGreater(basic, advanced)
    +
    +        # Toggle to advanced.
    +        dialog.toggle_level()
    +        self.assertTrue(dialog.advanced)
    +        self.assertIn('Basic', dialog.button_level['text'])
    +        basic, advanced = stackorder()
    +        self.assertGreater(advanced, basic)
    +
    +        # Toggle to basic.
    +        dialog.button_level.invoke()
    +        self.assertFalse(dialog.advanced)
    +        self.assertIn('Advanced', dialog.button_level['text'])
    +        basic, advanced = stackorder()
    +        self.assertGreater(basic, advanced)
    +
    +
    +class KeySelectionTest(unittest.TestCase):
    +    "Test selecting key on Basic frames."
    +
    +    class Basic(gkd):
    +        def __init__(self, *args, **kwargs):
    +            super().__init__(*args, **kwargs)
    +            class list_keys_final:
    +                get = Func()
    +                select_clear = Func()
    +                yview = Func()
    +            self.list_keys_final = list_keys_final
    +        def set_modifiers_for_platform(self):
    +            self.modifiers = ['foo', 'bar', 'BAZ']
    +            self.modifier_label = {'BAZ': 'ZZZ'}
    +        showerror = Mbox_func()
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        requires('gui')
    +        cls.root = Tk()
    +        cls.root.withdraw()
    +        cls.dialog = cls.Basic(cls.root, 'Title', '<>', [], _utest=True)
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        cls.dialog.cancel()
    +        cls.root.update_idletasks()
    +        cls.root.destroy()
    +        del cls.dialog, cls.root
    +
    +    def setUp(self):
    +        self.dialog.clear_key_seq()
    +
    +    def test_get_modifiers(self):
    +        dialog = self.dialog
    +        gm = dialog.get_modifiers
    +        eq = self.assertEqual
    +
    +        # Modifiers are set on/off by invoking the checkbutton.
    +        dialog.modifier_checkbuttons['foo'].invoke()
    +        eq(gm(), ['foo'])
    +
    +        dialog.modifier_checkbuttons['BAZ'].invoke()
    +        eq(gm(), ['foo', 'BAZ'])
    +
    +        dialog.modifier_checkbuttons['foo'].invoke()
    +        eq(gm(), ['BAZ'])
    +
    +    @mock.patch.object(gkd, 'get_modifiers')
    +    def test_build_key_string(self, mock_modifiers):
    +        dialog = self.dialog
    +        key = dialog.list_keys_final
    +        string = dialog.key_string.get
    +        eq = self.assertEqual
    +
    +        key.get.result = 'a'
    +        mock_modifiers.return_value = []
    +        dialog.build_key_string()
    +        eq(string(), '')
    +
    +        mock_modifiers.return_value = ['mymod']
    +        dialog.build_key_string()
    +        eq(string(), '')
    +
    +        key.get.result = ''
    +        mock_modifiers.return_value = ['mymod', 'test']
    +        dialog.build_key_string()
    +        eq(string(), '')
    +
    +    @mock.patch.object(gkd, 'get_modifiers')
    +    def test_final_key_selected(self, mock_modifiers):
    +        dialog = self.dialog
    +        key = dialog.list_keys_final
    +        string = dialog.key_string.get
    +        eq = self.assertEqual
    +
    +        mock_modifiers.return_value = ['Shift']
    +        key.get.result = '{'
    +        dialog.final_key_selected()
    +        eq(string(), '')
    +
    +
    +class CancelTest(unittest.TestCase):
    +    "Simulate user clicking [Cancel] button."
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        requires('gui')
    +        cls.root = Tk()
    +        cls.root.withdraw()
    +        cls.dialog = gkd(cls.root, 'Title', '<>', [], _utest=True)
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        cls.dialog.cancel()
    +        cls.root.update_idletasks()
    +        cls.root.destroy()
    +        del cls.dialog, cls.root
    +
    +    def test_cancel(self):
    +        self.assertEqual(self.dialog.winfo_class(), 'Toplevel')
    +        self.dialog.button_cancel.invoke()
    +        with self.assertRaises(TclError):
    +            self.dialog.winfo_class()
    +        self.assertEqual(self.dialog.result, '')
    +
    +
    +class HelperTest(unittest.TestCase):
    +    "Test module level helper functions."
    +
    +    def test_translate_key(self):
    +        tr = config_key.translate_key
    +        eq = self.assertEqual
    +
    +        # Letters return unchanged with no 'Shift'.
    +        eq(tr('q', []), 'Key-q')
    +        eq(tr('q', ['Control', 'Alt']), 'Key-q')
    +
    +        # 'Shift' uppercases single lowercase letters.
    +        eq(tr('q', ['Shift']), 'Key-Q')
    +        eq(tr('q', ['Control', 'Shift']), 'Key-Q')
    +        eq(tr('q', ['Control', 'Alt', 'Shift']), 'Key-Q')
    +
    +        # Convert key name to keysym.
    +        eq(tr('Page Up', []), 'Key-Prior')
    +        # 'Shift' doesn't change case when it's not a single char.
    +        eq(tr('*', ['Shift']), 'Key-asterisk')
    +
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_config_name.py b/PythonLib/full/idlelib/idle_test/test_config_name.py
    deleted file mode 100644
    index 2a4df6a7..00000000
    --- a/PythonLib/full/idlelib/idle_test/test_config_name.py
    +++ /dev/null
    @@ -1,77 +0,0 @@
    -"""Unit tests for idlelib.configSectionNameDialog"""
    -import unittest
    -from idlelib.idle_test.mock_tk import Var, Mbox
    -from idlelib import configSectionNameDialog as name_dialog_module
    -
    -name_dialog = name_dialog_module.GetCfgSectionNameDialog
    -
    -class Dummy_name_dialog(object):
    -    # Mock for testing the following methods of name_dialog
    -    name_ok = name_dialog.name_ok.im_func
    -    Ok = name_dialog.Ok.im_func
    -    Cancel = name_dialog.Cancel.im_func
    -    # Attributes, constant or variable, needed for tests
    -    used_names = ['used']
    -    name = Var()
    -    result = None
    -    destroyed = False
    -    def grab_release(self):
    -        pass
    -    def destroy(self):
    -        self.destroyed = True
    -
    -# name_ok calls Mbox.showerror if name is not ok
    -orig_mbox = name_dialog_module.tkMessageBox
    -showerror = Mbox.showerror
    -
    -class ConfigNameTest(unittest.TestCase):
    -    dialog = Dummy_name_dialog()
    -
    -    @classmethod
    -    def setUpClass(cls):
    -        name_dialog_module.tkMessageBox = Mbox
    -
    -    @classmethod
    -    def tearDownClass(cls):
    -        name_dialog_module.tkMessageBox = orig_mbox
    -
    -    def test_blank_name(self):
    -        self.dialog.name.set(' ')
    -        self.assertEqual(self.dialog.name_ok(), '')
    -        self.assertEqual(showerror.title, 'Name Error')
    -        self.assertIn('No', showerror.message)
    -
    -    def test_used_name(self):
    -        self.dialog.name.set('used')
    -        self.assertEqual(self.dialog.name_ok(), '')
    -        self.assertEqual(showerror.title, 'Name Error')
    -        self.assertIn('use', showerror.message)
    -
    -    def test_long_name(self):
    -        self.dialog.name.set('good'*8)
    -        self.assertEqual(self.dialog.name_ok(), '')
    -        self.assertEqual(showerror.title, 'Name Error')
    -        self.assertIn('too long', showerror.message)
    -
    -    def test_good_name(self):
    -        self.dialog.name.set('  good ')
    -        showerror.title = 'No Error'  # should not be called
    -        self.assertEqual(self.dialog.name_ok(), 'good')
    -        self.assertEqual(showerror.title, 'No Error')
    -
    -    def test_ok(self):
    -        self.dialog.destroyed = False
    -        self.dialog.name.set('good')
    -        self.dialog.Ok()
    -        self.assertEqual(self.dialog.result, 'good')
    -        self.assertTrue(self.dialog.destroyed)
    -
    -    def test_cancel(self):
    -        self.dialog.destroyed = False
    -        self.dialog.Cancel()
    -        self.assertEqual(self.dialog.result, '')
    -        self.assertTrue(self.dialog.destroyed)
    -
    -
    -if __name__ == '__main__':
    -    unittest.main(verbosity=2, exit=False)
    diff --git a/PythonLib/full/idlelib/idle_test/test_configdialog.py b/PythonLib/full/idlelib/idle_test/test_configdialog.py
    index ba651005..1fea6d41 100644
    --- a/PythonLib/full/idlelib/idle_test/test_configdialog.py
    +++ b/PythonLib/full/idlelib/idle_test/test_configdialog.py
    @@ -1,32 +1,1548 @@
    -'''Unittests for idlelib/configHandler.py
    +"""Test configdialog, coverage 94%.
     
    -Coverage: 46% just by creating dialog. The other half is change code.
    -
    -'''
    +Half the class creates dialog, half works with user customizations.
    +"""
    +from idlelib import configdialog
    +from test.support import requires
    +requires('gui')
     import unittest
    -from test.test_support import requires
    -from Tkinter import Tk
    -from idlelib.configDialog import ConfigDialog
    -from idlelib.macosxSupport import _initializeTkVariantTests
    +from unittest import mock
    +from idlelib.idle_test.mock_idle import Func
    +from tkinter import (Tk, StringVar, IntVar, BooleanVar, DISABLED, NORMAL)
    +from idlelib import config
    +from idlelib.configdialog import idleConf, changes, tracers
    +
    +# Tests should not depend on fortuitous user configurations.
    +# They must not affect actual user .cfg files.
    +# Use solution from test_config: empty parsers with no filename.
    +usercfg = idleConf.userCfg
    +testcfg = {
    +    'main': config.IdleUserConfParser(''),
    +    'highlight': config.IdleUserConfParser(''),
    +    'keys': config.IdleUserConfParser(''),
    +    'extensions': config.IdleUserConfParser(''),
    +}
    +
    +root = None
    +dialog = None
    +mainpage = changes['main']
    +highpage = changes['highlight']
    +keyspage = changes['keys']
    +extpage = changes['extensions']
    +
    +
    +def setUpModule():
    +    global root, dialog
    +    idleConf.userCfg = testcfg
    +    root = Tk()
    +    # root.withdraw()    # Comment out, see issue 30870
    +    dialog = configdialog.ConfigDialog(root, 'Test', _utest=True)
    +
    +
    +def tearDownModule():
    +    global root, dialog
    +    idleConf.userCfg = usercfg
    +    tracers.detach()
    +    tracers.clear()
    +    changes.clear()
    +    root.update_idletasks()
    +    root.destroy()
    +    root = dialog = None
     
     
     class ConfigDialogTest(unittest.TestCase):
     
    +    def test_deactivate_current_config(self):
    +        pass
    +
    +    def activate_config_changes(self):
    +        pass
    +
    +
    +class ButtonTest(unittest.TestCase):
    +
    +    def test_click_ok(self):
    +        d = dialog
    +        apply = d.apply = mock.Mock()
    +        destroy = d.destroy = mock.Mock()
    +        d.buttons['Ok'].invoke()
    +        apply.assert_called_once()
    +        destroy.assert_called_once()
    +        del d.destroy, d.apply
    +
    +    def test_click_apply(self):
    +        d = dialog
    +        deactivate = d.deactivate_current_config = mock.Mock()
    +        save_ext = d.save_all_changed_extensions = mock.Mock()
    +        activate = d.activate_config_changes = mock.Mock()
    +        d.buttons['Apply'].invoke()
    +        deactivate.assert_called_once()
    +        save_ext.assert_called_once()
    +        activate.assert_called_once()
    +        del d.save_all_changed_extensions
    +        del d.activate_config_changes, d.deactivate_current_config
    +
    +    def test_click_cancel(self):
    +        d = dialog
    +        d.destroy = Func()
    +        changes['main']['something'] = 1
    +        d.buttons['Cancel'].invoke()
    +        self.assertEqual(changes['main'], {})
    +        self.assertEqual(d.destroy.called, 1)
    +        del d.destroy
    +
    +    def test_click_help(self):
    +        dialog.note.select(dialog.keyspage)
    +        with mock.patch.object(configdialog, 'view_text',
    +                               new_callable=Func) as view:
    +            dialog.buttons['Help'].invoke()
    +            title, contents = view.kwds['title'], view.kwds['contents']
    +        self.assertEqual(title, 'Help for IDLE preferences')
    +        self.assertTrue(contents.startswith('When you click') and
    +                        contents.endswith('a different name.\n'))
    +
    +
    +class FontPageTest(unittest.TestCase):
    +    """Test that font widgets enable users to make font changes.
    +
    +    Test that widget actions set vars, that var changes add three
    +    options to changes and call set_samples, and that set_samples
    +    changes the font of both sample boxes.
    +    """
    +    @classmethod
    +    def setUpClass(cls):
    +        page = cls.page = dialog.fontpage
    +        dialog.note.select(page)
    +        page.set_samples = Func()  # Mask instance method.
    +        page.update()
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        del cls.page.set_samples  # Unmask instance method.
    +
    +    def setUp(self):
    +        changes.clear()
    +
    +    def test_load_font_cfg(self):
    +        # Leave widget load test to human visual check.
    +        # TODO Improve checks when add IdleConf.get_font_values.
    +        tracers.detach()
    +        d = self.page
    +        d.font_name.set('Fake')
    +        d.font_size.set('1')
    +        d.font_bold.set(True)
    +        d.set_samples.called = 0
    +        d.load_font_cfg()
    +        self.assertNotEqual(d.font_name.get(), 'Fake')
    +        self.assertNotEqual(d.font_size.get(), '1')
    +        self.assertFalse(d.font_bold.get())
    +        self.assertEqual(d.set_samples.called, 1)
    +        tracers.attach()
    +
    +    def test_fontlist_key(self):
    +        # Up and Down keys should select a new font.
    +        d = self.page
    +        if d.fontlist.size() < 2:
    +            self.skipTest('need at least 2 fonts')
    +        fontlist = d.fontlist
    +        fontlist.activate(0)
    +        font = d.fontlist.get('active')
    +
    +        # Test Down key.
    +        fontlist.focus_force()
    +        fontlist.update()
    +        fontlist.event_generate('')
    +        fontlist.event_generate('')
    +
    +        down_font = fontlist.get('active')
    +        self.assertNotEqual(down_font, font)
    +        self.assertIn(d.font_name.get(), down_font.lower())
    +
    +        # Test Up key.
    +        fontlist.focus_force()
    +        fontlist.update()
    +        fontlist.event_generate('')
    +        fontlist.event_generate('')
    +
    +        up_font = fontlist.get('active')
    +        self.assertEqual(up_font, font)
    +        self.assertIn(d.font_name.get(), up_font.lower())
    +
    +    def test_fontlist_mouse(self):
    +        # Click on item should select that item.
    +        d = self.page
    +        if d.fontlist.size() < 2:
    +            self.skipTest('need at least 2 fonts')
    +        fontlist = d.fontlist
    +        fontlist.activate(0)
    +
    +        # Select next item in listbox
    +        fontlist.focus_force()
    +        fontlist.see(1)
    +        fontlist.update()
    +        x, y, dx, dy = fontlist.bbox(1)
    +        x += dx // 2
    +        y += dy // 2
    +        fontlist.event_generate('', x=x, y=y)
    +        fontlist.event_generate('', x=x, y=y)
    +
    +        font1 = fontlist.get(1)
    +        select_font = fontlist.get('anchor')
    +        self.assertEqual(select_font, font1)
    +        self.assertIn(d.font_name.get(), font1.lower())
    +
    +    def test_sizelist(self):
    +        # Click on number should select that number
    +        d = self.page
    +        d.sizelist.variable.set(40)
    +        self.assertEqual(d.font_size.get(), '40')
    +
    +    def test_bold_toggle(self):
    +        # Click on checkbutton should invert it.
    +        d = self.page
    +        d.font_bold.set(False)
    +        d.bold_toggle.invoke()
    +        self.assertTrue(d.font_bold.get())
    +        d.bold_toggle.invoke()
    +        self.assertFalse(d.font_bold.get())
    +
    +    def test_font_set(self):
    +        # Test that setting a font Variable results in 3 provisional
    +        # change entries and a call to set_samples. Use values sure to
    +        # not be defaults.
    +
    +        default_font = idleConf.GetFont(root, 'main', 'EditorWindow')
    +        default_size = str(default_font[1])
    +        default_bold = default_font[2] == 'bold'
    +        d = self.page
    +        d.font_size.set(default_size)
    +        d.font_bold.set(default_bold)
    +        d.set_samples.called = 0
    +
    +        d.font_name.set('Test Font')
    +        expected = {'EditorWindow': {'font': 'Test Font',
    +                                     'font-size': default_size,
    +                                     'font-bold': str(default_bold)}}
    +        self.assertEqual(mainpage, expected)
    +        self.assertEqual(d.set_samples.called, 1)
    +        changes.clear()
    +
    +        d.font_size.set('20')
    +        expected = {'EditorWindow': {'font': 'Test Font',
    +                                     'font-size': '20',
    +                                     'font-bold': str(default_bold)}}
    +        self.assertEqual(mainpage, expected)
    +        self.assertEqual(d.set_samples.called, 2)
    +        changes.clear()
    +
    +        d.font_bold.set(not default_bold)
    +        expected = {'EditorWindow': {'font': 'Test Font',
    +                                     'font-size': '20',
    +                                     'font-bold': str(not default_bold)}}
    +        self.assertEqual(mainpage, expected)
    +        self.assertEqual(d.set_samples.called, 3)
    +
    +    def test_set_samples(self):
    +        d = self.page
    +        del d.set_samples  # Unmask method for test
    +        orig_samples = d.font_sample, d.highlight_sample
    +        d.font_sample, d.highlight_sample = {}, {}
    +        d.font_name.set('test')
    +        d.font_size.set('5')
    +        d.font_bold.set(1)
    +        expected = {'font': ('test', '5', 'bold')}
    +
    +        # Test set_samples.
    +        d.set_samples()
    +        self.assertTrue(d.font_sample == d.highlight_sample == expected)
    +
    +        d.font_sample, d.highlight_sample = orig_samples
    +        d.set_samples = Func()  # Re-mask for other tests.
    +
    +
    +class IndentTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        cls.page = dialog.fontpage
    +        cls.page.update()
    +
    +    def test_load_tab_cfg(self):
    +        d = self.page
    +        d.space_num.set(16)
    +        d.load_tab_cfg()
    +        self.assertEqual(d.space_num.get(), 4)
    +
    +    def test_indent_scale(self):
    +        d = self.page
    +        changes.clear()
    +        d.indent_scale.set(20)
    +        self.assertEqual(d.space_num.get(), 16)
    +        self.assertEqual(mainpage, {'Indent': {'num-spaces': '16'}})
    +
    +
    +class HighPageTest(unittest.TestCase):
    +    """Test that highlight tab widgets enable users to make changes.
    +
    +    Test that widget actions set vars, that var changes add
    +    options to changes and that themes work correctly.
    +    """
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        page = cls.page = dialog.highpage
    +        dialog.note.select(page)
    +        page.set_theme_type = Func()
    +        page.paint_theme_sample = Func()
    +        page.set_highlight_target = Func()
    +        page.set_color_sample = Func()
    +        page.update()
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        d = cls.page
    +        del d.set_theme_type, d.paint_theme_sample
    +        del d.set_highlight_target, d.set_color_sample
    +
    +    def setUp(self):
    +        d = self.page
    +        # The following is needed for test_load_key_cfg, _delete_custom_keys.
    +        # This may indicate a defect in some test or function.
    +        for section in idleConf.GetSectionList('user', 'highlight'):
    +            idleConf.userCfg['highlight'].remove_section(section)
    +        changes.clear()
    +        d.set_theme_type.called = 0
    +        d.paint_theme_sample.called = 0
    +        d.set_highlight_target.called = 0
    +        d.set_color_sample.called = 0
    +
    +    def test_load_theme_cfg(self):
    +        tracers.detach()
    +        d = self.page
    +        eq = self.assertEqual
    +
    +        # Use builtin theme with no user themes created.
    +        idleConf.CurrentTheme = mock.Mock(return_value='IDLE Classic')
    +        d.load_theme_cfg()
    +        self.assertTrue(d.theme_source.get())
    +        # builtinlist sets variable builtin_name to the CurrentTheme default.
    +        eq(d.builtin_name.get(), 'IDLE Classic')
    +        eq(d.custom_name.get(), '- no custom themes -')
    +        eq(d.custom_theme_on.state(), ('disabled',))
    +        eq(d.set_theme_type.called, 1)
    +        eq(d.paint_theme_sample.called, 1)
    +        eq(d.set_highlight_target.called, 1)
    +
    +        # Builtin theme with non-empty user theme list.
    +        idleConf.SetOption('highlight', 'test1', 'option', 'value')
    +        idleConf.SetOption('highlight', 'test2', 'option2', 'value2')
    +        d.load_theme_cfg()
    +        eq(d.builtin_name.get(), 'IDLE Classic')
    +        eq(d.custom_name.get(), 'test1')
    +        eq(d.set_theme_type.called, 2)
    +        eq(d.paint_theme_sample.called, 2)
    +        eq(d.set_highlight_target.called, 2)
    +
    +        # Use custom theme.
    +        idleConf.CurrentTheme = mock.Mock(return_value='test2')
    +        idleConf.SetOption('main', 'Theme', 'default', '0')
    +        d.load_theme_cfg()
    +        self.assertFalse(d.theme_source.get())
    +        eq(d.builtin_name.get(), 'IDLE Classic')
    +        eq(d.custom_name.get(), 'test2')
    +        eq(d.set_theme_type.called, 3)
    +        eq(d.paint_theme_sample.called, 3)
    +        eq(d.set_highlight_target.called, 3)
    +
    +        del idleConf.CurrentTheme
    +        tracers.attach()
    +
    +    def test_theme_source(self):
    +        eq = self.assertEqual
    +        d = self.page
    +        # Test these separately.
    +        d.var_changed_builtin_name = Func()
    +        d.var_changed_custom_name = Func()
    +        # Builtin selected.
    +        d.builtin_theme_on.invoke()
    +        eq(mainpage, {'Theme': {'default': 'True'}})
    +        eq(d.var_changed_builtin_name.called, 1)
    +        eq(d.var_changed_custom_name.called, 0)
    +        changes.clear()
    +
    +        # Custom selected.
    +        d.custom_theme_on.state(('!disabled',))
    +        d.custom_theme_on.invoke()
    +        self.assertEqual(mainpage, {'Theme': {'default': 'False'}})
    +        eq(d.var_changed_builtin_name.called, 1)
    +        eq(d.var_changed_custom_name.called, 1)
    +        del d.var_changed_builtin_name, d.var_changed_custom_name
    +
    +    def test_builtin_name(self):
    +        eq = self.assertEqual
    +        d = self.page
    +        item_list = ['IDLE Classic', 'IDLE Dark', 'IDLE New']
    +
    +        # Not in old_themes, defaults name to first item.
    +        idleConf.SetOption('main', 'Theme', 'name', 'spam')
    +        d.builtinlist.SetMenu(item_list, 'IDLE Dark')
    +        eq(mainpage, {'Theme': {'name': 'IDLE Classic',
    +                                'name2': 'IDLE Dark'}})
    +        eq(d.theme_message['text'], 'New theme, see Help')
    +        eq(d.paint_theme_sample.called, 1)
    +
    +        # Not in old themes - uses name2.
    +        changes.clear()
    +        idleConf.SetOption('main', 'Theme', 'name', 'IDLE New')
    +        d.builtinlist.SetMenu(item_list, 'IDLE Dark')
    +        eq(mainpage, {'Theme': {'name2': 'IDLE Dark'}})
    +        eq(d.theme_message['text'], 'New theme, see Help')
    +        eq(d.paint_theme_sample.called, 2)
    +
    +        # Builtin name in old_themes.
    +        changes.clear()
    +        d.builtinlist.SetMenu(item_list, 'IDLE Classic')
    +        eq(mainpage, {'Theme': {'name': 'IDLE Classic', 'name2': ''}})
    +        eq(d.theme_message['text'], '')
    +        eq(d.paint_theme_sample.called, 3)
    +
    +    def test_custom_name(self):
    +        d = self.page
    +
    +        # If no selections, doesn't get added.
    +        d.customlist.SetMenu([], '- no custom themes -')
    +        self.assertNotIn('Theme', mainpage)
    +        self.assertEqual(d.paint_theme_sample.called, 0)
    +
    +        # Custom name selected.
    +        changes.clear()
    +        d.customlist.SetMenu(['a', 'b', 'c'], 'c')
    +        self.assertEqual(mainpage, {'Theme': {'name': 'c'}})
    +        self.assertEqual(d.paint_theme_sample.called, 1)
    +
    +    def test_color(self):
    +        d = self.page
    +        d.on_new_color_set = Func()
    +        # self.color is only set in get_color through ColorChooser.
    +        d.color.set('green')
    +        self.assertEqual(d.on_new_color_set.called, 1)
    +        del d.on_new_color_set
    +
    +    def test_highlight_target_list_mouse(self):
    +        # Set highlight_target through targetlist.
    +        eq = self.assertEqual
    +        d = self.page
    +
    +        d.targetlist.SetMenu(['a', 'b', 'c'], 'c')
    +        eq(d.highlight_target.get(), 'c')
    +        eq(d.set_highlight_target.called, 1)
    +
    +    def test_highlight_target_text_mouse(self):
    +        # Set highlight_target through clicking highlight_sample.
    +        eq = self.assertEqual
    +        d = self.page
    +
    +        elem = {}
    +        count = 0
    +        hs = d.highlight_sample
    +        hs.focus_force()
    +        hs.see(1.0)
    +        hs.update_idletasks()
    +
    +        def tag_to_element(elem):
    +            for element, tag in d.theme_elements.items():
    +                elem[tag[0]] = element
    +
    +        def click_it(start):
    +            x, y, dx, dy = hs.bbox(start)
    +            x += dx // 2
    +            y += dy // 2
    +            hs.event_generate('', x=0, y=0)
    +            hs.event_generate('', x=x, y=y)
    +            hs.event_generate('', x=x, y=y)
    +            hs.event_generate('', x=x, y=y)
    +
    +        # Flip theme_elements to make the tag the key.
    +        tag_to_element(elem)
    +
    +        # If highlight_sample has a tag that isn't in theme_elements, there
    +        # will be a KeyError in the test run.
    +        for tag in hs.tag_names():
    +            for start_index in hs.tag_ranges(tag)[0::2]:
    +                count += 1
    +                click_it(start_index)
    +                eq(d.highlight_target.get(), elem[tag])
    +                eq(d.set_highlight_target.called, count)
    +
    +    def test_highlight_sample_double_click(self):
    +        # Test double click on highlight_sample.
    +        eq = self.assertEqual
    +        d = self.page
    +
    +        hs = d.highlight_sample
    +        hs.focus_force()
    +        hs.see(1.0)
    +        hs.update_idletasks()
    +
    +        # Test binding from configdialog.
    +        hs.event_generate('', x=0, y=0)
    +        hs.event_generate('', x=0, y=0)
    +        # Double click is a sequence of two clicks in a row.
    +        for _ in range(2):
    +            hs.event_generate('', x=0, y=0)
    +            hs.event_generate('', x=0, y=0)
    +
    +        eq(hs.tag_ranges('sel'), ())
    +
    +    def test_highlight_sample_b1_motion(self):
    +        # Test button motion on highlight_sample.
    +        eq = self.assertEqual
    +        d = self.page
    +
    +        hs = d.highlight_sample
    +        hs.focus_force()
    +        hs.see(1.0)
    +        hs.update_idletasks()
    +
    +        x, y, dx, dy, offset = hs.dlineinfo('1.0')
    +
    +        # Test binding from configdialog.
    +        hs.event_generate('')
    +        hs.event_generate('')
    +        hs.event_generate('', x=x, y=y)
    +        hs.event_generate('', x=x, y=y)
    +        hs.event_generate('', x=dx, y=dy)
    +        hs.event_generate('', x=dx, y=dy)
    +
    +        eq(hs.tag_ranges('sel'), ())
    +
    +    def test_set_theme_type(self):
    +        eq = self.assertEqual
    +        d = self.page
    +        del d.set_theme_type
    +
    +        # Builtin theme selected.
    +        d.theme_source.set(True)
    +        d.set_theme_type()
    +        eq(d.builtinlist['state'], NORMAL)
    +        eq(d.customlist['state'], DISABLED)
    +        eq(d.button_delete_custom.state(), ('disabled',))
    +
    +        # Custom theme selected.
    +        d.theme_source.set(False)
    +        d.set_theme_type()
    +        eq(d.builtinlist['state'], DISABLED)
    +        eq(d.custom_theme_on.state(), ('selected',))
    +        eq(d.customlist['state'], NORMAL)
    +        eq(d.button_delete_custom.state(), ())
    +        d.set_theme_type = Func()
    +
    +    def test_get_color(self):
    +        eq = self.assertEqual
    +        d = self.page
    +        orig_chooser = configdialog.tkColorChooser.askcolor
    +        chooser = configdialog.tkColorChooser.askcolor = Func()
    +        gntn = d.get_new_theme_name = Func()
    +
    +        d.highlight_target.set('Editor Breakpoint')
    +        d.color.set('#ffffff')
    +
    +        # Nothing selected.
    +        chooser.result = (None, None)
    +        d.button_set_color.invoke()
    +        eq(d.color.get(), '#ffffff')
    +
    +        # Selection same as previous color.
    +        chooser.result = ('', d.style.lookup(d.frame_color_set['style'], 'background'))
    +        d.button_set_color.invoke()
    +        eq(d.color.get(), '#ffffff')
    +
    +        # Select different color.
    +        chooser.result = ((222.8671875, 0.0, 0.0), '#de0000')
    +
    +        # Default theme.
    +        d.color.set('#ffffff')
    +        d.theme_source.set(True)
    +
    +        # No theme name selected therefore color not saved.
    +        gntn.result = ''
    +        d.button_set_color.invoke()
    +        eq(gntn.called, 1)
    +        eq(d.color.get(), '#ffffff')
    +        # Theme name selected.
    +        gntn.result = 'My New Theme'
    +        d.button_set_color.invoke()
    +        eq(d.custom_name.get(), gntn.result)
    +        eq(d.color.get(), '#de0000')
    +
    +        # Custom theme.
    +        d.color.set('#ffffff')
    +        d.theme_source.set(False)
    +        d.button_set_color.invoke()
    +        eq(d.color.get(), '#de0000')
    +
    +        del d.get_new_theme_name
    +        configdialog.tkColorChooser.askcolor = orig_chooser
    +
    +    def test_on_new_color_set(self):
    +        d = self.page
    +        color = '#3f7cae'
    +        d.custom_name.set('Python')
    +        d.highlight_target.set('Selected Text')
    +        d.fg_bg_toggle.set(True)
    +
    +        d.color.set(color)
    +        self.assertEqual(d.style.lookup(d.frame_color_set['style'], 'background'), color)
    +        self.assertEqual(d.highlight_sample.tag_cget('hilite', 'foreground'), color)
    +        self.assertEqual(highpage,
    +                         {'Python': {'hilite-foreground': color}})
    +
    +    def test_get_new_theme_name(self):
    +        orig_sectionname = configdialog.SectionName
    +        sn = configdialog.SectionName = Func(return_self=True)
    +        d = self.page
    +
    +        sn.result = 'New Theme'
    +        self.assertEqual(d.get_new_theme_name(''), 'New Theme')
    +
    +        configdialog.SectionName = orig_sectionname
    +
    +    def test_save_as_new_theme(self):
    +        d = self.page
    +        gntn = d.get_new_theme_name = Func()
    +        d.theme_source.set(True)
    +
    +        # No name entered.
    +        gntn.result = ''
    +        d.button_save_custom.invoke()
    +        self.assertNotIn(gntn.result, idleConf.userCfg['highlight'])
    +
    +        # Name entered.
    +        gntn.result = 'my new theme'
    +        gntn.called = 0
    +        self.assertNotIn(gntn.result, idleConf.userCfg['highlight'])
    +        d.button_save_custom.invoke()
    +        self.assertIn(gntn.result, idleConf.userCfg['highlight'])
    +
    +        del d.get_new_theme_name
    +
    +    def test_create_new_and_save_new(self):
    +        eq = self.assertEqual
    +        d = self.page
    +
    +        # Use default as previously active theme.
    +        d.theme_source.set(True)
    +        d.builtin_name.set('IDLE Classic')
    +        first_new = 'my new custom theme'
    +        second_new = 'my second custom theme'
    +
    +        # No changes, so themes are an exact copy.
    +        self.assertNotIn(first_new, idleConf.userCfg)
    +        d.create_new(first_new)
    +        eq(idleConf.GetSectionList('user', 'highlight'), [first_new])
    +        eq(idleConf.GetThemeDict('default', 'IDLE Classic'),
    +           idleConf.GetThemeDict('user', first_new))
    +        eq(d.custom_name.get(), first_new)
    +        self.assertFalse(d.theme_source.get())  # Use custom set.
    +        eq(d.set_theme_type.called, 1)
    +
    +        # Test that changed targets are in new theme.
    +        changes.add_option('highlight', first_new, 'hit-background', 'yellow')
    +        self.assertNotIn(second_new, idleConf.userCfg)
    +        d.create_new(second_new)
    +        eq(idleConf.GetSectionList('user', 'highlight'), [first_new, second_new])
    +        self.assertNotEqual(idleConf.GetThemeDict('user', first_new),
    +                            idleConf.GetThemeDict('user', second_new))
    +        # Check that difference in themes was in `hit-background` from `changes`.
    +        idleConf.SetOption('highlight', first_new, 'hit-background', 'yellow')
    +        eq(idleConf.GetThemeDict('user', first_new),
    +           idleConf.GetThemeDict('user', second_new))
    +
    +    def test_set_highlight_target(self):
    +        eq = self.assertEqual
    +        d = self.page
    +        del d.set_highlight_target
    +
    +        # Target is cursor.
    +        d.highlight_target.set('Cursor')
    +        eq(d.fg_on.state(), ('disabled', 'selected'))
    +        eq(d.bg_on.state(), ('disabled',))
    +        self.assertTrue(d.fg_bg_toggle)
    +        eq(d.set_color_sample.called, 1)
    +
    +        # Target is not cursor.
    +        d.highlight_target.set('Comment')
    +        eq(d.fg_on.state(), ('selected',))
    +        eq(d.bg_on.state(), ())
    +        self.assertTrue(d.fg_bg_toggle)
    +        eq(d.set_color_sample.called, 2)
    +
    +        d.set_highlight_target = Func()
    +
    +    def test_set_color_sample_binding(self):
    +        d = self.page
    +        scs = d.set_color_sample
    +
    +        d.fg_on.invoke()
    +        self.assertEqual(scs.called, 1)
    +
    +        d.bg_on.invoke()
    +        self.assertEqual(scs.called, 2)
    +
    +    def test_set_color_sample(self):
    +        d = self.page
    +        del d.set_color_sample
    +        d.highlight_target.set('Selected Text')
    +        d.fg_bg_toggle.set(True)
    +        d.set_color_sample()
    +        self.assertEqual(
    +                d.style.lookup(d.frame_color_set['style'], 'background'),
    +                d.highlight_sample.tag_cget('hilite', 'foreground'))
    +        d.set_color_sample = Func()
    +
    +    def test_paint_theme_sample(self):
    +        eq = self.assertEqual
    +        page = self.page
    +        del page.paint_theme_sample  # Delete masking mock.
    +        hs_tag = page.highlight_sample.tag_cget
    +        gh = idleConf.GetHighlight
    +
    +        # Create custom theme based on IDLE Dark.
    +        page.theme_source.set(True)
    +        page.builtin_name.set('IDLE Dark')
    +        theme = 'IDLE Test'
    +        page.create_new(theme)
    +        page.set_color_sample.called = 0
    +
    +        # Base theme with nothing in `changes`.
    +        page.paint_theme_sample()
    +        new_console = {'foreground': 'blue',
    +                       'background': 'yellow',}
    +        for key, value in new_console.items():
    +            self.assertNotEqual(hs_tag('console', key), value)
    +        eq(page.set_color_sample.called, 1)
    +
    +        # Apply changes.
    +        for key, value in new_console.items():
    +            changes.add_option('highlight', theme, 'console-'+key, value)
    +        page.paint_theme_sample()
    +        for key, value in new_console.items():
    +            eq(hs_tag('console', key), value)
    +        eq(page.set_color_sample.called, 2)
    +
    +        page.paint_theme_sample = Func()
    +
    +    def test_delete_custom(self):
    +        eq = self.assertEqual
    +        d = self.page
    +        d.button_delete_custom.state(('!disabled',))
    +        yesno = d.askyesno = Func()
    +        dialog.deactivate_current_config = Func()
    +        dialog.activate_config_changes = Func()
    +
    +        theme_name = 'spam theme'
    +        idleConf.userCfg['highlight'].SetOption(theme_name, 'name', 'value')
    +        highpage[theme_name] = {'option': 'True'}
    +
    +        theme_name2 = 'other theme'
    +        idleConf.userCfg['highlight'].SetOption(theme_name2, 'name', 'value')
    +        highpage[theme_name2] = {'option': 'False'}
    +
    +        # Force custom theme.
    +        d.custom_theme_on.state(('!disabled',))
    +        d.custom_theme_on.invoke()
    +        d.custom_name.set(theme_name)
    +
    +        # Cancel deletion.
    +        yesno.result = False
    +        d.button_delete_custom.invoke()
    +        eq(yesno.called, 1)
    +        eq(highpage[theme_name], {'option': 'True'})
    +        eq(idleConf.GetSectionList('user', 'highlight'), [theme_name, theme_name2])
    +        eq(dialog.deactivate_current_config.called, 0)
    +        eq(dialog.activate_config_changes.called, 0)
    +        eq(d.set_theme_type.called, 0)
    +
    +        # Confirm deletion.
    +        yesno.result = True
    +        d.button_delete_custom.invoke()
    +        eq(yesno.called, 2)
    +        self.assertNotIn(theme_name, highpage)
    +        eq(idleConf.GetSectionList('user', 'highlight'), [theme_name2])
    +        eq(d.custom_theme_on.state(), ())
    +        eq(d.custom_name.get(), theme_name2)
    +        eq(dialog.deactivate_current_config.called, 1)
    +        eq(dialog.activate_config_changes.called, 1)
    +        eq(d.set_theme_type.called, 1)
    +
    +        # Confirm deletion of second theme - empties list.
    +        d.custom_name.set(theme_name2)
    +        yesno.result = True
    +        d.button_delete_custom.invoke()
    +        eq(yesno.called, 3)
    +        self.assertNotIn(theme_name, highpage)
    +        eq(idleConf.GetSectionList('user', 'highlight'), [])
    +        eq(d.custom_theme_on.state(), ('disabled',))
    +        eq(d.custom_name.get(), '- no custom themes -')
    +        eq(dialog.deactivate_current_config.called, 2)
    +        eq(dialog.activate_config_changes.called, 2)
    +        eq(d.set_theme_type.called, 2)
    +
    +        del dialog.activate_config_changes, dialog.deactivate_current_config
    +        del d.askyesno
    +
    +
    +class KeysPageTest(unittest.TestCase):
    +    """Test that keys tab widgets enable users to make changes.
    +
    +    Test that widget actions set vars, that var changes add
    +    options to changes and that key sets works correctly.
    +    """
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        page = cls.page = dialog.keyspage
    +        dialog.note.select(page)
    +        page.set_keys_type = Func()
    +        page.load_keys_list = Func()
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        page = cls.page
    +        del page.set_keys_type, page.load_keys_list
    +
    +    def setUp(self):
    +        d = self.page
    +        # The following is needed for test_load_key_cfg, _delete_custom_keys.
    +        # This may indicate a defect in some test or function.
    +        for section in idleConf.GetSectionList('user', 'keys'):
    +            idleConf.userCfg['keys'].remove_section(section)
    +        changes.clear()
    +        d.set_keys_type.called = 0
    +        d.load_keys_list.called = 0
    +
    +    def test_load_key_cfg(self):
    +        tracers.detach()
    +        d = self.page
    +        eq = self.assertEqual
    +
    +        # Use builtin keyset with no user keysets created.
    +        idleConf.CurrentKeys = mock.Mock(return_value='IDLE Classic OSX')
    +        d.load_key_cfg()
    +        self.assertTrue(d.keyset_source.get())
    +        # builtinlist sets variable builtin_name to the CurrentKeys default.
    +        eq(d.builtin_name.get(), 'IDLE Classic OSX')
    +        eq(d.custom_name.get(), '- no custom keys -')
    +        eq(d.custom_keyset_on.state(), ('disabled',))
    +        eq(d.set_keys_type.called, 1)
    +        eq(d.load_keys_list.called, 1)
    +        eq(d.load_keys_list.args, ('IDLE Classic OSX', ))
    +
    +        # Builtin keyset with non-empty user keyset list.
    +        idleConf.SetOption('keys', 'test1', 'option', 'value')
    +        idleConf.SetOption('keys', 'test2', 'option2', 'value2')
    +        d.load_key_cfg()
    +        eq(d.builtin_name.get(), 'IDLE Classic OSX')
    +        eq(d.custom_name.get(), 'test1')
    +        eq(d.set_keys_type.called, 2)
    +        eq(d.load_keys_list.called, 2)
    +        eq(d.load_keys_list.args, ('IDLE Classic OSX', ))
    +
    +        # Use custom keyset.
    +        idleConf.CurrentKeys = mock.Mock(return_value='test2')
    +        idleConf.default_keys = mock.Mock(return_value='IDLE Modern Unix')
    +        idleConf.SetOption('main', 'Keys', 'default', '0')
    +        d.load_key_cfg()
    +        self.assertFalse(d.keyset_source.get())
    +        eq(d.builtin_name.get(), 'IDLE Modern Unix')
    +        eq(d.custom_name.get(), 'test2')
    +        eq(d.set_keys_type.called, 3)
    +        eq(d.load_keys_list.called, 3)
    +        eq(d.load_keys_list.args, ('test2', ))
    +
    +        del idleConf.CurrentKeys, idleConf.default_keys
    +        tracers.attach()
    +
    +    def test_keyset_source(self):
    +        eq = self.assertEqual
    +        d = self.page
    +        # Test these separately.
    +        d.var_changed_builtin_name = Func()
    +        d.var_changed_custom_name = Func()
    +        # Builtin selected.
    +        d.builtin_keyset_on.invoke()
    +        eq(mainpage, {'Keys': {'default': 'True'}})
    +        eq(d.var_changed_builtin_name.called, 1)
    +        eq(d.var_changed_custom_name.called, 0)
    +        changes.clear()
    +
    +        # Custom selected.
    +        d.custom_keyset_on.state(('!disabled',))
    +        d.custom_keyset_on.invoke()
    +        self.assertEqual(mainpage, {'Keys': {'default': 'False'}})
    +        eq(d.var_changed_builtin_name.called, 1)
    +        eq(d.var_changed_custom_name.called, 1)
    +        del d.var_changed_builtin_name, d.var_changed_custom_name
    +
    +    def test_builtin_name(self):
    +        eq = self.assertEqual
    +        d = self.page
    +        idleConf.userCfg['main'].remove_section('Keys')
    +        item_list = ['IDLE Classic Windows', 'IDLE Classic OSX',
    +                     'IDLE Modern UNIX']
    +
    +        # Not in old_keys, defaults name to first item.
    +        d.builtinlist.SetMenu(item_list, 'IDLE Modern UNIX')
    +        eq(mainpage, {'Keys': {'name': 'IDLE Classic Windows',
    +                               'name2': 'IDLE Modern UNIX'}})
    +        eq(d.keys_message['text'], 'New key set, see Help')
    +        eq(d.load_keys_list.called, 1)
    +        eq(d.load_keys_list.args, ('IDLE Modern UNIX', ))
    +
    +        # Not in old keys - uses name2.
    +        changes.clear()
    +        idleConf.SetOption('main', 'Keys', 'name', 'IDLE Classic Unix')
    +        d.builtinlist.SetMenu(item_list, 'IDLE Modern UNIX')
    +        eq(mainpage, {'Keys': {'name2': 'IDLE Modern UNIX'}})
    +        eq(d.keys_message['text'], 'New key set, see Help')
    +        eq(d.load_keys_list.called, 2)
    +        eq(d.load_keys_list.args, ('IDLE Modern UNIX', ))
    +
    +        # Builtin name in old_keys.
    +        changes.clear()
    +        d.builtinlist.SetMenu(item_list, 'IDLE Classic OSX')
    +        eq(mainpage, {'Keys': {'name': 'IDLE Classic OSX', 'name2': ''}})
    +        eq(d.keys_message['text'], '')
    +        eq(d.load_keys_list.called, 3)
    +        eq(d.load_keys_list.args, ('IDLE Classic OSX', ))
    +
    +    def test_custom_name(self):
    +        d = self.page
    +
    +        # If no selections, doesn't get added.
    +        d.customlist.SetMenu([], '- no custom keys -')
    +        self.assertNotIn('Keys', mainpage)
    +        self.assertEqual(d.load_keys_list.called, 0)
    +
    +        # Custom name selected.
    +        changes.clear()
    +        d.customlist.SetMenu(['a', 'b', 'c'], 'c')
    +        self.assertEqual(mainpage, {'Keys': {'name': 'c'}})
    +        self.assertEqual(d.load_keys_list.called, 1)
    +
    +    def test_keybinding(self):
    +        idleConf.SetOption('extensions', 'ZzDummy', 'enable', 'True')
    +        d = self.page
    +        d.custom_name.set('my custom keys')
    +        d.bindingslist.delete(0, 'end')
    +        d.bindingslist.insert(0, 'copy')
    +        d.bindingslist.insert(1, 'z-in')
    +        d.bindingslist.selection_set(0)
    +        d.bindingslist.selection_anchor(0)
    +        # Core binding - adds to keys.
    +        d.keybinding.set('')
    +        self.assertEqual(keyspage,
    +                         {'my custom keys': {'copy': ''}})
    +
    +        # Not a core binding - adds to extensions.
    +        d.bindingslist.selection_set(1)
    +        d.bindingslist.selection_anchor(1)
    +        d.keybinding.set('')
    +        self.assertEqual(extpage,
    +                         {'ZzDummy_cfgBindings': {'z-in': ''}})
    +
    +    def test_set_keys_type(self):
    +        eq = self.assertEqual
    +        d = self.page
    +        del d.set_keys_type
    +
    +        # Builtin keyset selected.
    +        d.keyset_source.set(True)
    +        d.set_keys_type()
    +        eq(d.builtinlist['state'], NORMAL)
    +        eq(d.customlist['state'], DISABLED)
    +        eq(d.button_delete_custom_keys.state(), ('disabled',))
    +
    +        # Custom keyset selected.
    +        d.keyset_source.set(False)
    +        d.set_keys_type()
    +        eq(d.builtinlist['state'], DISABLED)
    +        eq(d.custom_keyset_on.state(), ('selected',))
    +        eq(d.customlist['state'], NORMAL)
    +        eq(d.button_delete_custom_keys.state(), ())
    +        d.set_keys_type = Func()
    +
    +    def test_get_new_keys(self):
    +        eq = self.assertEqual
    +        d = self.page
    +        orig_getkeysdialog = configdialog.GetKeysDialog
    +        gkd = configdialog.GetKeysDialog = Func(return_self=True)
    +        gnkn = d.get_new_keys_name = Func()
    +
    +        d.button_new_keys.state(('!disabled',))
    +        d.bindingslist.delete(0, 'end')
    +        d.bindingslist.insert(0, 'copy - ')
    +        d.bindingslist.selection_set(0)
    +        d.bindingslist.selection_anchor(0)
    +        d.keybinding.set('Key-a')
    +        d.keyset_source.set(True)  # Default keyset.
    +
    +        # Default keyset; no change to binding.
    +        gkd.result = ''
    +        d.button_new_keys.invoke()
    +        eq(d.bindingslist.get('anchor'), 'copy - ')
    +        # Keybinding isn't changed when there isn't a change entered.
    +        eq(d.keybinding.get(), 'Key-a')
    +
    +        # Default keyset; binding changed.
    +        gkd.result = ''
    +        # No keyset name selected therefore binding not saved.
    +        gnkn.result = ''
    +        d.button_new_keys.invoke()
    +        eq(gnkn.called, 1)
    +        eq(d.bindingslist.get('anchor'), 'copy - ')
    +        # Keyset name selected.
    +        gnkn.result = 'My New Key Set'
    +        d.button_new_keys.invoke()
    +        eq(d.custom_name.get(), gnkn.result)
    +        eq(d.bindingslist.get('anchor'), 'copy - ')
    +        eq(d.keybinding.get(), '')
    +
    +        # User keyset; binding changed.
    +        d.keyset_source.set(False)  # Custom keyset.
    +        gnkn.called = 0
    +        gkd.result = ''
    +        d.button_new_keys.invoke()
    +        eq(gnkn.called, 0)
    +        eq(d.bindingslist.get('anchor'), 'copy - ')
    +        eq(d.keybinding.get(), '')
    +
    +        del d.get_new_keys_name
    +        configdialog.GetKeysDialog = orig_getkeysdialog
    +
    +    def test_get_new_keys_name(self):
    +        orig_sectionname = configdialog.SectionName
    +        sn = configdialog.SectionName = Func(return_self=True)
    +        d = self.page
    +
    +        sn.result = 'New Keys'
    +        self.assertEqual(d.get_new_keys_name(''), 'New Keys')
    +
    +        configdialog.SectionName = orig_sectionname
    +
    +    def test_save_as_new_key_set(self):
    +        d = self.page
    +        gnkn = d.get_new_keys_name = Func()
    +        d.keyset_source.set(True)
    +
    +        # No name entered.
    +        gnkn.result = ''
    +        d.button_save_custom_keys.invoke()
    +
    +        # Name entered.
    +        gnkn.result = 'my new key set'
    +        gnkn.called = 0
    +        self.assertNotIn(gnkn.result, idleConf.userCfg['keys'])
    +        d.button_save_custom_keys.invoke()
    +        self.assertIn(gnkn.result, idleConf.userCfg['keys'])
    +
    +        del d.get_new_keys_name
    +
    +    def test_on_bindingslist_select(self):
    +        d = self.page
    +        b = d.bindingslist
    +        b.delete(0, 'end')
    +        b.insert(0, 'copy')
    +        b.insert(1, 'find')
    +        b.activate(0)
    +
    +        b.focus_force()
    +        b.see(1)
    +        b.update()
    +        x, y, dx, dy = b.bbox(1)
    +        x += dx // 2
    +        y += dy // 2
    +        b.event_generate('', x=0, y=0)
    +        b.event_generate('', x=x, y=y)
    +        b.event_generate('', x=x, y=y)
    +        b.event_generate('', x=x, y=y)
    +        self.assertEqual(b.get('anchor'), 'find')
    +        self.assertEqual(d.button_new_keys.state(), ())
    +
    +    def test_create_new_key_set_and_save_new_key_set(self):
    +        eq = self.assertEqual
    +        d = self.page
    +
    +        # Use default as previously active keyset.
    +        d.keyset_source.set(True)
    +        d.builtin_name.set('IDLE Classic Windows')
    +        first_new = 'my new custom key set'
    +        second_new = 'my second custom keyset'
    +
    +        # No changes, so keysets are an exact copy.
    +        self.assertNotIn(first_new, idleConf.userCfg)
    +        d.create_new_key_set(first_new)
    +        eq(idleConf.GetSectionList('user', 'keys'), [first_new])
    +        eq(idleConf.GetKeySet('IDLE Classic Windows'),
    +           idleConf.GetKeySet(first_new))
    +        eq(d.custom_name.get(), first_new)
    +        self.assertFalse(d.keyset_source.get())  # Use custom set.
    +        eq(d.set_keys_type.called, 1)
    +
    +        # Test that changed keybindings are in new keyset.
    +        changes.add_option('keys', first_new, 'copy', '')
    +        self.assertNotIn(second_new, idleConf.userCfg)
    +        d.create_new_key_set(second_new)
    +        eq(idleConf.GetSectionList('user', 'keys'), [first_new, second_new])
    +        self.assertNotEqual(idleConf.GetKeySet(first_new),
    +                            idleConf.GetKeySet(second_new))
    +        # Check that difference in keysets was in option `copy` from `changes`.
    +        idleConf.SetOption('keys', first_new, 'copy', '')
    +        eq(idleConf.GetKeySet(first_new), idleConf.GetKeySet(second_new))
    +
    +    def test_load_keys_list(self):
    +        eq = self.assertEqual
    +        d = self.page
    +        gks = idleConf.GetKeySet = Func()
    +        del d.load_keys_list
    +        b = d.bindingslist
    +
    +        b.delete(0, 'end')
    +        b.insert(0, '<>')
    +        b.insert(1, '<>')
    +        gks.result = {'<>': ['', ''],
    +                      '<>': [''],
    +                      '<>': ['']}
    +        changes.add_option('keys', 'my keys', 'spam', '')
    +        expected = ('copy -  ',
    +                    'force-open-completions - ',
    +                    'spam - ')
    +
    +        # No current selection.
    +        d.load_keys_list('my keys')
    +        eq(b.get(0, 'end'), expected)
    +        eq(b.get('anchor'), '')
    +        eq(b.curselection(), ())
    +
    +        # Check selection.
    +        b.selection_set(1)
    +        b.selection_anchor(1)
    +        d.load_keys_list('my keys')
    +        eq(b.get(0, 'end'), expected)
    +        eq(b.get('anchor'), 'force-open-completions - ')
    +        eq(b.curselection(), (1, ))
    +
    +        # Change selection.
    +        b.selection_set(2)
    +        b.selection_anchor(2)
    +        d.load_keys_list('my keys')
    +        eq(b.get(0, 'end'), expected)
    +        eq(b.get('anchor'), 'spam - ')
    +        eq(b.curselection(), (2, ))
    +        d.load_keys_list = Func()
    +
    +        del idleConf.GetKeySet
    +
    +    def test_delete_custom_keys(self):
    +        eq = self.assertEqual
    +        d = self.page
    +        d.button_delete_custom_keys.state(('!disabled',))
    +        yesno = d.askyesno = Func()
    +        dialog.deactivate_current_config = Func()
    +        dialog.activate_config_changes = Func()
    +
    +        keyset_name = 'spam key set'
    +        idleConf.userCfg['keys'].SetOption(keyset_name, 'name', 'value')
    +        keyspage[keyset_name] = {'option': 'True'}
    +
    +        keyset_name2 = 'other key set'
    +        idleConf.userCfg['keys'].SetOption(keyset_name2, 'name', 'value')
    +        keyspage[keyset_name2] = {'option': 'False'}
    +
    +        # Force custom keyset.
    +        d.custom_keyset_on.state(('!disabled',))
    +        d.custom_keyset_on.invoke()
    +        d.custom_name.set(keyset_name)
    +
    +        # Cancel deletion.
    +        yesno.result = False
    +        d.button_delete_custom_keys.invoke()
    +        eq(yesno.called, 1)
    +        eq(keyspage[keyset_name], {'option': 'True'})
    +        eq(idleConf.GetSectionList('user', 'keys'), [keyset_name, keyset_name2])
    +        eq(dialog.deactivate_current_config.called, 0)
    +        eq(dialog.activate_config_changes.called, 0)
    +        eq(d.set_keys_type.called, 0)
    +
    +        # Confirm deletion.
    +        yesno.result = True
    +        d.button_delete_custom_keys.invoke()
    +        eq(yesno.called, 2)
    +        self.assertNotIn(keyset_name, keyspage)
    +        eq(idleConf.GetSectionList('user', 'keys'), [keyset_name2])
    +        eq(d.custom_keyset_on.state(), ())
    +        eq(d.custom_name.get(), keyset_name2)
    +        eq(dialog.deactivate_current_config.called, 1)
    +        eq(dialog.activate_config_changes.called, 1)
    +        eq(d.set_keys_type.called, 1)
    +
    +        # Confirm deletion of second keyset - empties list.
    +        d.custom_name.set(keyset_name2)
    +        yesno.result = True
    +        d.button_delete_custom_keys.invoke()
    +        eq(yesno.called, 3)
    +        self.assertNotIn(keyset_name, keyspage)
    +        eq(idleConf.GetSectionList('user', 'keys'), [])
    +        eq(d.custom_keyset_on.state(), ('disabled',))
    +        eq(d.custom_name.get(), '- no custom keys -')
    +        eq(dialog.deactivate_current_config.called, 2)
    +        eq(dialog.activate_config_changes.called, 2)
    +        eq(d.set_keys_type.called, 2)
    +
    +        del dialog.activate_config_changes, dialog.deactivate_current_config
    +        del d.askyesno
    +
    +
    +class GenPageTest(unittest.TestCase):
    +    """Test that general tab widgets enable users to make changes.
    +
    +    Test that widget actions set vars, that var changes add
    +    options to changes and that helplist works correctly.
    +    """
         @classmethod
         def setUpClass(cls):
    -        requires('gui')
    -        cls.root = Tk()
    -        cls.root.withdraw()
    -        _initializeTkVariantTests(cls.root)
    +        page = cls.page = dialog.genpage
    +        dialog.note.select(page)
    +        page.set = page.set_add_delete_state = Func()
    +        page.upc = page.update_help_changes = Func()
    +        page.update()
     
         @classmethod
         def tearDownClass(cls):
    -        cls.root.destroy()
    -        del cls.root
    +        page = cls.page
    +        del page.set, page.set_add_delete_state
    +        del page.upc, page.update_help_changes
    +        page.helplist.delete(0, 'end')
    +        page.user_helplist.clear()
    +
    +    def setUp(self):
    +        changes.clear()
    +
    +    def test_load_general_cfg(self):
    +        # Set to wrong values, load, check right values.
    +        eq = self.assertEqual
    +        d = self.page
    +        d.startup_edit.set(1)
    +        d.autosave.set(1)
    +        d.win_width.set(1)
    +        d.win_height.set(1)
    +        d.helplist.insert('end', 'bad')
    +        d.user_helplist = ['bad', 'worse']
    +        idleConf.SetOption('main', 'HelpFiles', '1', 'name;file')
    +        d.load_general_cfg()
    +        eq(d.startup_edit.get(), 0)
    +        eq(d.autosave.get(), 0)
    +        eq(d.win_width.get(), '80')
    +        eq(d.win_height.get(), '40')
    +        eq(d.helplist.get(0, 'end'), ('name',))
    +        eq(d.user_helplist, [('name', 'file', '1')])
    +
    +    def test_startup(self):
    +        d = self.page
    +        d.startup_editor_on.invoke()
    +        self.assertEqual(mainpage,
    +                         {'General': {'editor-on-startup': '1'}})
    +        changes.clear()
    +        d.startup_shell_on.invoke()
    +        self.assertEqual(mainpage,
    +                         {'General': {'editor-on-startup': '0'}})
    +
    +    def test_editor_size(self):
    +        d = self.page
    +        d.win_height_int.delete(0, 'end')
    +        d.win_height_int.insert(0, '11')
    +        self.assertEqual(mainpage, {'EditorWindow': {'height': '11'}})
    +        changes.clear()
    +        d.win_width_int.delete(0, 'end')
    +        d.win_width_int.insert(0, '11')
    +        self.assertEqual(mainpage, {'EditorWindow': {'width': '11'}})
    +
    +    def test_cursor_blink(self):
    +        self.page.cursor_blink_bool.invoke()
    +        self.assertEqual(mainpage, {'EditorWindow': {'cursor-blink': 'False'}})
    +
    +    def test_autocomplete_wait(self):
    +        self.page.auto_wait_int.delete(0, 'end')
    +        self.page.auto_wait_int.insert(0, '11')
    +        self.assertEqual(extpage, {'AutoComplete': {'popupwait': '11'}})
    +
    +    def test_parenmatch(self):
    +        d = self.page
    +        eq = self.assertEqual
    +        d.paren_style_type['menu'].invoke(0)
    +        eq(extpage, {'ParenMatch': {'style': 'opener'}})
    +        changes.clear()
    +        d.paren_flash_time.delete(0, 'end')
    +        d.paren_flash_time.insert(0, '11')
    +        eq(extpage, {'ParenMatch': {'flash-delay': '11'}})
    +        changes.clear()
    +        d.bell_on.invoke()
    +        eq(extpage, {'ParenMatch': {'bell': 'False'}})
    +
    +    def test_autosave(self):
    +        d = self.page
    +        d.save_auto_on.invoke()
    +        self.assertEqual(mainpage, {'General': {'autosave': '1'}})
    +        d.save_ask_on.invoke()
    +        self.assertEqual(mainpage, {'General': {'autosave': '0'}})
    +
    +    def test_paragraph(self):
    +        self.page.format_width_int.delete(0, 'end')
    +        self.page.format_width_int.insert(0, '11')
    +        self.assertEqual(extpage, {'FormatParagraph': {'max-width': '11'}})
    +
    +    def test_context(self):
    +        self.page.context_int.delete(0, 'end')
    +        self.page.context_int.insert(0, '1')
    +        self.assertEqual(extpage, {'CodeContext': {'maxlines': '1'}})
    +
    +    def test_source_selected(self):
    +        d = self.page
    +        d.set = d.set_add_delete_state
    +        d.upc = d.update_help_changes
    +        helplist = d.helplist
    +        dex = 'end'
    +        helplist.insert(dex, 'source')
    +        helplist.activate(dex)
    +
    +        helplist.focus_force()
    +        helplist.see(dex)
    +        helplist.update()
    +        x, y, dx, dy = helplist.bbox(dex)
    +        x += dx // 2
    +        y += dy // 2
    +        d.set.called = d.upc.called = 0
    +        helplist.event_generate('', x=0, y=0)
    +        helplist.event_generate('', x=x, y=y)
    +        helplist.event_generate('', x=x, y=y)
    +        helplist.event_generate('', x=x, y=y)
    +        self.assertEqual(helplist.get('anchor'), 'source')
    +        self.assertTrue(d.set.called)
    +        self.assertFalse(d.upc.called)
    +
    +    def test_set_add_delete_state(self):
    +        # Call with 0 items, 1 unselected item, 1 selected item.
    +        eq = self.assertEqual
    +        d = self.page
    +        del d.set_add_delete_state  # Unmask method.
    +        sad = d.set_add_delete_state
    +        h = d.helplist
    +
    +        h.delete(0, 'end')
    +        sad()
    +        eq(d.button_helplist_edit.state(), ('disabled',))
    +        eq(d.button_helplist_remove.state(), ('disabled',))
    +
    +        h.insert(0, 'source')
    +        sad()
    +        eq(d.button_helplist_edit.state(), ('disabled',))
    +        eq(d.button_helplist_remove.state(), ('disabled',))
    +
    +        h.selection_set(0)
    +        sad()
    +        eq(d.button_helplist_edit.state(), ())
    +        eq(d.button_helplist_remove.state(), ())
    +        d.set_add_delete_state = Func()  # Mask method.
    +
    +    def test_helplist_item_add(self):
    +        # Call without and twice with HelpSource result.
    +        # Double call enables check on order.
    +        eq = self.assertEqual
    +        orig_helpsource = configdialog.HelpSource
    +        hs = configdialog.HelpSource = Func(return_self=True)
    +        d = self.page
    +        d.helplist.delete(0, 'end')
    +        d.user_helplist.clear()
    +        d.set.called = d.upc.called = 0
    +
    +        hs.result = ''
    +        d.helplist_item_add()
    +        self.assertTrue(list(d.helplist.get(0, 'end')) ==
    +                        d.user_helplist == [])
    +        self.assertFalse(d.upc.called)
    +
    +        hs.result = ('name1', 'file1')
    +        d.helplist_item_add()
    +        hs.result = ('name2', 'file2')
    +        d.helplist_item_add()
    +        eq(d.helplist.get(0, 'end'), ('name1', 'name2'))
    +        eq(d.user_helplist, [('name1', 'file1'), ('name2', 'file2')])
    +        eq(d.upc.called, 2)
    +        self.assertFalse(d.set.called)
    +
    +        configdialog.HelpSource = orig_helpsource
    +
    +    def test_helplist_item_edit(self):
    +        # Call without and with HelpSource change.
    +        eq = self.assertEqual
    +        orig_helpsource = configdialog.HelpSource
    +        hs = configdialog.HelpSource = Func(return_self=True)
    +        d = self.page
    +        d.helplist.delete(0, 'end')
    +        d.helplist.insert(0, 'name1')
    +        d.helplist.selection_set(0)
    +        d.helplist.selection_anchor(0)
    +        d.user_helplist.clear()
    +        d.user_helplist.append(('name1', 'file1'))
    +        d.set.called = d.upc.called = 0
    +
    +        hs.result = ''
    +        d.helplist_item_edit()
    +        hs.result = ('name1', 'file1')
    +        d.helplist_item_edit()
    +        eq(d.helplist.get(0, 'end'), ('name1',))
    +        eq(d.user_helplist, [('name1', 'file1')])
    +        self.assertFalse(d.upc.called)
    +
    +        hs.result = ('name2', 'file2')
    +        d.helplist_item_edit()
    +        eq(d.helplist.get(0, 'end'), ('name2',))
    +        eq(d.user_helplist, [('name2', 'file2')])
    +        self.assertTrue(d.upc.called == d.set.called == 1)
    +
    +        configdialog.HelpSource = orig_helpsource
    +
    +    def test_helplist_item_remove(self):
    +        eq = self.assertEqual
    +        d = self.page
    +        d.helplist.delete(0, 'end')
    +        d.helplist.insert(0, 'name1')
    +        d.helplist.selection_set(0)
    +        d.helplist.selection_anchor(0)
    +        d.user_helplist.clear()
    +        d.user_helplist.append(('name1', 'file1'))
    +        d.set.called = d.upc.called = 0
    +
    +        d.helplist_item_remove()
    +        eq(d.helplist.get(0, 'end'), ())
    +        eq(d.user_helplist, [])
    +        self.assertTrue(d.upc.called == d.set.called == 1)
    +
    +    def test_update_help_changes(self):
    +        d = self.page
    +        del d.update_help_changes
    +        d.user_helplist.clear()
    +        d.user_helplist.append(('name1', 'file1'))
    +        d.user_helplist.append(('name2', 'file2'))
    +
    +        d.update_help_changes()
    +        self.assertEqual(mainpage['HelpFiles'],
    +                         {'1': 'name1;file1', '2': 'name2;file2'})
    +        d.update_help_changes = Func()
    +
    +
    +class VarTraceTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        cls.tracers = configdialog.VarTrace()
    +        cls.iv = IntVar(root)
    +        cls.bv = BooleanVar(root)
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        del cls.tracers, cls.iv, cls.bv
    +
    +    def setUp(self):
    +        self.tracers.clear()
    +        self.called = 0
    +
    +    def var_changed_increment(self, *params):
    +        self.called += 13
    +
    +    def var_changed_boolean(self, *params):
    +        pass
    +
    +    def test_init(self):
    +        tr = self.tracers
    +        tr.__init__()
    +        self.assertEqual(tr.untraced, [])
    +        self.assertEqual(tr.traced, [])
    +
    +    def test_clear(self):
    +        tr = self.tracers
    +        tr.untraced.append(0)
    +        tr.traced.append(1)
    +        tr.clear()
    +        self.assertEqual(tr.untraced, [])
    +        self.assertEqual(tr.traced, [])
    +
    +    def test_add(self):
    +        tr = self.tracers
    +        func = Func()
    +        cb = tr.make_callback = mock.Mock(return_value=func)
    +
    +        iv = tr.add(self.iv, self.var_changed_increment)
    +        self.assertIs(iv, self.iv)
    +        bv = tr.add(self.bv, self.var_changed_boolean)
    +        self.assertIs(bv, self.bv)
    +
    +        sv = StringVar(root)
    +        sv2 = tr.add(sv, ('main', 'section', 'option'))
    +        self.assertIs(sv2, sv)
    +        cb.assert_called_once()
    +        cb.assert_called_with(sv, ('main', 'section', 'option'))
    +
    +        expected = [(iv, self.var_changed_increment),
    +                    (bv, self.var_changed_boolean),
    +                    (sv, func)]
    +        self.assertEqual(tr.traced, [])
    +        self.assertEqual(tr.untraced, expected)
    +
    +        del tr.make_callback
    +
    +    def test_make_callback(self):
    +        cb = self.tracers.make_callback(self.iv, ('main', 'section', 'option'))
    +        self.assertTrue(callable(cb))
    +        self.iv.set(42)
    +        # Not attached, so set didn't invoke the callback.
    +        self.assertNotIn('section', changes['main'])
    +        # Invoke callback manually.
    +        cb()
    +        self.assertIn('section', changes['main'])
    +        self.assertEqual(changes['main']['section']['option'], '42')
    +        changes.clear()
    +
    +    def test_attach_detach(self):
    +        tr = self.tracers
    +        iv = tr.add(self.iv, self.var_changed_increment)
    +        bv = tr.add(self.bv, self.var_changed_boolean)
    +        expected = [(iv, self.var_changed_increment),
    +                    (bv, self.var_changed_boolean)]
    +
    +        # Attach callbacks and test call increment.
    +        tr.attach()
    +        self.assertEqual(tr.untraced, [])
    +        self.assertCountEqual(tr.traced, expected)
    +        iv.set(1)
    +        self.assertEqual(iv.get(), 1)
    +        self.assertEqual(self.called, 13)
    +
    +        # Check that only one callback is attached to a variable.
    +        # If more than one callback were attached, then var_changed_increment
    +        # would be called twice and the counter would be 2.
    +        self.called = 0
    +        tr.attach()
    +        iv.set(1)
    +        self.assertEqual(self.called, 13)
     
    -    def test_dialog(self):
    -        d = ConfigDialog(self.root, 'Test', _utest=True)
    -        d.remove_var_callbacks()
    +        # Detach callbacks.
    +        self.called = 0
    +        tr.detach()
    +        self.assertEqual(tr.traced, [])
    +        self.assertCountEqual(tr.untraced, expected)
    +        iv.set(1)
    +        self.assertEqual(self.called, 0)
     
     
     if __name__ == '__main__':
    diff --git a/PythonLib/full/idlelib/idle_test/test_debugger.py b/PythonLib/full/idlelib/idle_test/test_debugger.py
    new file mode 100644
    index 00000000..35efb341
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_debugger.py
    @@ -0,0 +1,29 @@
    +"Test debugger, coverage 19%"
    +
    +from idlelib import debugger
    +import unittest
    +from test.support import requires
    +requires('gui')
    +from tkinter import Tk
    +
    +
    +class NameSpaceTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        cls.root = Tk()
    +        cls.root.withdraw()
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        cls.root.destroy()
    +        del cls.root
    +
    +    def test_init(self):
    +        debugger.NamespaceViewer(self.root, 'Test')
    +
    +
    +# Other classes are Idb, Debugger, and StackViewer.
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_debugger_r.py b/PythonLib/full/idlelib/idle_test/test_debugger_r.py
    new file mode 100644
    index 00000000..199f6344
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_debugger_r.py
    @@ -0,0 +1,29 @@
    +"Test debugger_r, coverage 30%."
    +
    +from idlelib import debugger_r
    +import unittest
    +from test.support import requires
    +from tkinter import Tk
    +
    +
    +class Test(unittest.TestCase):
    +
    +##    @classmethod
    +##    def setUpClass(cls):
    +##        requires('gui')
    +##        cls.root = Tk()
    +##
    +##    @classmethod
    +##    def tearDownClass(cls):
    +##        cls.root.destroy()
    +##        del cls.root
    +
    +    def test_init(self):
    +        self.assertTrue(True)  # Get coverage of import
    +
    +
    +# Classes GUIProxy, IdbAdapter, FrameProxy, CodeProxy, DictProxy,
    +# GUIAdapter, IdbProxy plus 7 module functions.
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_debugobj.py b/PythonLib/full/idlelib/idle_test/test_debugobj.py
    new file mode 100644
    index 00000000..131ce22b
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_debugobj.py
    @@ -0,0 +1,57 @@
    +"Test debugobj, coverage 40%."
    +
    +from idlelib import debugobj
    +import unittest
    +
    +
    +class ObjectTreeItemTest(unittest.TestCase):
    +
    +    def test_init(self):
    +        ti = debugobj.ObjectTreeItem('label', 22)
    +        self.assertEqual(ti.labeltext, 'label')
    +        self.assertEqual(ti.object, 22)
    +        self.assertEqual(ti.setfunction, None)
    +
    +
    +class ClassTreeItemTest(unittest.TestCase):
    +
    +    def test_isexpandable(self):
    +        ti = debugobj.ClassTreeItem('label', 0)
    +        self.assertTrue(ti.IsExpandable())
    +
    +
    +class AtomicObjectTreeItemTest(unittest.TestCase):
    +
    +    def test_isexpandable(self):
    +        ti = debugobj.AtomicObjectTreeItem('label', 0)
    +        self.assertFalse(ti.IsExpandable())
    +
    +
    +class SequenceTreeItemTest(unittest.TestCase):
    +
    +    def test_isexpandable(self):
    +        ti = debugobj.SequenceTreeItem('label', ())
    +        self.assertFalse(ti.IsExpandable())
    +        ti = debugobj.SequenceTreeItem('label', (1,))
    +        self.assertTrue(ti.IsExpandable())
    +
    +    def test_keys(self):
    +        ti = debugobj.SequenceTreeItem('label', 'abc')
    +        self.assertEqual(list(ti.keys()), [0, 1, 2])
    +
    +
    +class DictTreeItemTest(unittest.TestCase):
    +
    +    def test_isexpandable(self):
    +        ti = debugobj.DictTreeItem('label', {})
    +        self.assertFalse(ti.IsExpandable())
    +        ti = debugobj.DictTreeItem('label', {1:1})
    +        self.assertTrue(ti.IsExpandable())
    +
    +    def test_keys(self):
    +        ti = debugobj.DictTreeItem('label', {1:1, 0:0, 2:2})
    +        self.assertEqual(ti.keys(), [0, 1, 2])
    +
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_debugobj_r.py b/PythonLib/full/idlelib/idle_test/test_debugobj_r.py
    new file mode 100644
    index 00000000..86e51b6c
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_debugobj_r.py
    @@ -0,0 +1,22 @@
    +"Test debugobj_r, coverage 56%."
    +
    +from idlelib import debugobj_r
    +import unittest
    +
    +
    +class WrappedObjectTreeItemTest(unittest.TestCase):
    +
    +    def test_getattr(self):
    +        ti = debugobj_r.WrappedObjectTreeItem(list)
    +        self.assertEqual(ti.append, list.append)
    +
    +class StubObjectTreeItemTest(unittest.TestCase):
    +
    +    def test_init(self):
    +        ti = debugobj_r.StubObjectTreeItem('socket', 1111)
    +        self.assertEqual(ti.sockio, 'socket')
    +        self.assertEqual(ti.oid, 1111)
    +
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_delegator.py b/PythonLib/full/idlelib/idle_test/test_delegator.py
    index b8ae5eee..92241629 100644
    --- a/PythonLib/full/idlelib/idle_test/test_delegator.py
    +++ b/PythonLib/full/idlelib/idle_test/test_delegator.py
    @@ -1,37 +1,44 @@
    +"Test delegator, coverage 100%."
    +
    +from idlelib.delegator import Delegator
     import unittest
    -from idlelib.Delegator import Delegator
    +
     
     class DelegatorTest(unittest.TestCase):
     
         def test_mydel(self):
    -        # test a simple use scenario
    +        # Test a simple use scenario.
     
    -        # initialize
    +        # Initialize an int delegator.
             mydel = Delegator(int)
             self.assertIs(mydel.delegate, int)
             self.assertEqual(mydel._Delegator__cache, set())
    -
    -        # add an attribute:
    +        # Trying to access a non-attribute of int fails.
             self.assertRaises(AttributeError, mydel.__getattr__, 'xyz')
    +
    +        # Add real int attribute 'bit_length' by accessing it.
             bl = mydel.bit_length
             self.assertIs(bl, int.bit_length)
             self.assertIs(mydel.__dict__['bit_length'], int.bit_length)
             self.assertEqual(mydel._Delegator__cache, {'bit_length'})
     
    -        # add a second attribute
    +        # Add attribute 'numerator'.
             mydel.numerator
             self.assertEqual(mydel._Delegator__cache, {'bit_length', 'numerator'})
     
    -        # delete the second (which, however, leaves it in the name cache)
    +        # Delete 'numerator'.
             del mydel.numerator
             self.assertNotIn('numerator', mydel.__dict__)
    -        self.assertIn('numerator', mydel._Delegator__cache)
    +        # The current implementation leaves  it in the name cache.
    +        # self.assertIn('numerator', mydel._Delegator__cache)
    +        # However, this is not required and not part of the specification
     
    -        # reset by calling .setdelegate, which calls .resetcache
    -        mydel.setdelegate(float)
    -        self.assertIs(mydel.delegate, float)
    +        # Change delegate to float, first resetting the attributes.
    +        mydel.setdelegate(float)  # calls resetcache
             self.assertNotIn('bit_length', mydel.__dict__)
             self.assertEqual(mydel._Delegator__cache, set())
    +        self.assertIs(mydel.delegate, float)
    +
     
     if __name__ == '__main__':
         unittest.main(verbosity=2, exit=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_editmenu.py b/PythonLib/full/idlelib/idle_test/test_editmenu.py
    index 51d5c163..17478473 100644
    --- a/PythonLib/full/idlelib/idle_test/test_editmenu.py
    +++ b/PythonLib/full/idlelib/idle_test/test_editmenu.py
    @@ -1,100 +1,73 @@
     '''Test (selected) IDLE Edit menu items.
     
    -Edit modules have their own test files files
    +Edit modules have their own test files
     '''
    -from test.test_support import requires
    -import Tkinter as tk
    +from test.support import requires
    +requires('gui')
    +import tkinter as tk
    +from tkinter import ttk
     import unittest
    -from idlelib import PyShell
    -
    +from idlelib import pyshell
     
     class PasteTest(unittest.TestCase):
         '''Test pasting into widgets that allow pasting.
     
         On X11, replacing selections requires tk fix.
         '''
    -
         @classmethod
         def setUpClass(cls):
    -        requires('gui')
             cls.root = root = tk.Tk()
    -        root.withdraw()
    -        PyShell.fix_x11_paste(root)
    +        cls.root.withdraw()
    +        pyshell.fix_x11_paste(root)
             cls.text = tk.Text(root)
             cls.entry = tk.Entry(root)
    +        cls.tentry = ttk.Entry(root)
             cls.spin = tk.Spinbox(root)
             root.clipboard_clear()
             root.clipboard_append('two')
     
         @classmethod
         def tearDownClass(cls):
    -        del cls.text, cls.entry, cls.spin
    +        del cls.text, cls.entry, cls.tentry
             cls.root.clipboard_clear()
             cls.root.update_idletasks()
    -        cls.root.update()
             cls.root.destroy()
             del cls.root
     
    -    def test_paste_text_no_selection(self):
    -        "Test pasting into text without a selection."
    -        text = self.text
    -        tag, ans = '', 'onetwo\n'
    -        text.delete('1.0', 'end')
    -        text.insert('1.0', 'one', tag)
    -        text.event_generate('<>')
    -        self.assertEqual(text.get('1.0', 'end'), ans)
    -
    -    def test_paste_text_selection(self):
    -        "Test pasting into text with a selection."
    +    def test_paste_text(self):
    +        "Test pasting into text with and without a selection."
             text = self.text
    -        tag, ans = 'sel', 'two\n'
    -        text.delete('1.0', 'end')
    -        text.insert('1.0', 'one', tag)
    -        text.event_generate('<>')
    -        self.assertEqual(text.get('1.0', 'end'), ans)
    +        for tag, ans in ('', 'onetwo\n'), ('sel', 'two\n'):
    +            with self.subTest(tag=tag, ans=ans):
    +                text.delete('1.0', 'end')
    +                text.insert('1.0', 'one', tag)
    +                text.event_generate('<>')
    +                self.assertEqual(text.get('1.0', 'end'), ans)
     
    -    def test_paste_entry_no_selection(self):
    -        "Test pasting into an entry without a selection."
    -        # On 3.6, generated <> fails without empty select range
    -        # for 'no selection'.  Live widget works fine.
    -        entry = self.entry
    -        end, ans = 0, 'onetwo'
    -        entry.delete(0, 'end')
    -        entry.insert(0, 'one')
    -        entry.select_range(0, end)  # see note
    -        entry.event_generate('<>')
    -        self.assertEqual(entry.get(), ans)
    +    def test_paste_entry(self):
    +        "Test pasting into an entry with and without a selection."
    +        # Generated <> fails for tk entry without empty select
    +        # range for 'no selection'.  Live widget works fine.
    +        for entry in self.entry, self.tentry:
    +            for end, ans in (0, 'onetwo'), ('end', 'two'):
    +                with self.subTest(entry=entry, end=end, ans=ans):
    +                    entry.delete(0, 'end')
    +                    entry.insert(0, 'one')
    +                    entry.select_range(0, end)
    +                    entry.event_generate('<>')
    +                    self.assertEqual(entry.get(), ans)
     
    -    def test_paste_entry_selection(self):
    -        "Test pasting into an entry with a selection."
    -        entry = self.entry
    -        end, ans = 'end', 'two'
    -        entry.delete(0, 'end')
    -        entry.insert(0, 'one')
    -        entry.select_range(0, end)
    -        entry.event_generate('<>')
    -        self.assertEqual(entry.get(), ans)
    -
    -    def test_paste_spin_no_selection(self):
    -        "Test pasting into a spinbox without a selection."
    +    def test_paste_spin(self):
    +        "Test pasting into a spinbox with and without a selection."
             # See note above for entry.
             spin = self.spin
    -        end, ans = 0, 'onetwo'
    -        spin.delete(0, 'end')
    -        spin.insert(0, 'one')
    -        spin.selection('range', 0, end)  # see note
    -        spin.event_generate('<>')
    -        self.assertEqual(spin.get(), ans)
    -
    -    def test_paste_spin_selection(self):
    -        "Test pasting into a spinbox with a selection."
    -        spin = self.spin
    -        end, ans = 'end', 'two'
    -        spin.delete(0, 'end')
    -        spin.insert(0, 'one')
    -        spin.selection('range', 0, end)
    -        spin.event_generate('<>')
    -        self.assertEqual(spin.get(), ans)
    +        for end, ans in (0, 'onetwo'), ('end', 'two'):
    +            with self.subTest(end=end, ans=ans):
    +                spin.delete(0, 'end')
    +                spin.insert(0, 'one')
    +                spin.selection('range', 0, end)  # see note
    +                spin.event_generate('<>')
    +                self.assertEqual(spin.get(), ans)
     
     
     if __name__ == '__main__':
    diff --git a/PythonLib/full/idlelib/idle_test/test_editor.py b/PythonLib/full/idlelib/idle_test/test_editor.py
    new file mode 100644
    index 00000000..91e8ef89
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_editor.py
    @@ -0,0 +1,194 @@
    +"Test editor, coverage 35%."
    +
    +from idlelib import editor
    +import unittest
    +from collections import namedtuple
    +from test.support import requires
    +from tkinter import Tk
    +
    +Editor = editor.EditorWindow
    +
    +
    +class EditorWindowTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        requires('gui')
    +        cls.root = Tk()
    +        cls.root.withdraw()
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        cls.root.update_idletasks()
    +        for id in cls.root.tk.call('after', 'info'):
    +            cls.root.after_cancel(id)
    +        cls.root.destroy()
    +        del cls.root
    +
    +    def test_init(self):
    +        e = Editor(root=self.root)
    +        self.assertEqual(e.root, self.root)
    +        e._close()
    +
    +
    +class TestGetLineIndent(unittest.TestCase):
    +    def test_empty_lines(self):
    +        for tabwidth in [1, 2, 4, 6, 8]:
    +            for line in ['', '\n']:
    +                with self.subTest(line=line, tabwidth=tabwidth):
    +                    self.assertEqual(
    +                        editor.get_line_indent(line, tabwidth=tabwidth),
    +                        (0, 0),
    +                    )
    +
    +    def test_tabwidth_4(self):
    +        #        (line, (raw, effective))
    +        tests = (('no spaces', (0, 0)),
    +                 # Internal space isn't counted.
    +                 ('    space test', (4, 4)),
    +                 ('\ttab test', (1, 4)),
    +                 ('\t\tdouble tabs test', (2, 8)),
    +                 # Different results when mixing tabs and spaces.
    +                 ('    \tmixed test', (5, 8)),
    +                 ('  \t  mixed test', (5, 6)),
    +                 ('\t    mixed test', (5, 8)),
    +                 # Spaces not divisible by tabwidth.
    +                 ('  \tmixed test', (3, 4)),
    +                 (' \t mixed test', (3, 5)),
    +                 ('\t  mixed test', (3, 6)),
    +                 # Only checks spaces and tabs.
    +                 ('\nnewline test', (0, 0)))
    +
    +        for line, expected in tests:
    +            with self.subTest(line=line):
    +                self.assertEqual(
    +                    editor.get_line_indent(line, tabwidth=4),
    +                    expected,
    +                )
    +
    +    def test_tabwidth_8(self):
    +        #        (line, (raw, effective))
    +        tests = (('no spaces', (0, 0)),
    +                 # Internal space isn't counted.
    +                 ('        space test', (8, 8)),
    +                 ('\ttab test', (1, 8)),
    +                 ('\t\tdouble tabs test', (2, 16)),
    +                 # Different results when mixing tabs and spaces.
    +                 ('        \tmixed test', (9, 16)),
    +                 ('      \t  mixed test', (9, 10)),
    +                 ('\t        mixed test', (9, 16)),
    +                 # Spaces not divisible by tabwidth.
    +                 ('  \tmixed test', (3, 8)),
    +                 (' \t mixed test', (3, 9)),
    +                 ('\t  mixed test', (3, 10)),
    +                 # Only checks spaces and tabs.
    +                 ('\nnewline test', (0, 0)))
    +
    +        for line, expected in tests:
    +            with self.subTest(line=line):
    +                self.assertEqual(
    +                    editor.get_line_indent(line, tabwidth=8),
    +                    expected,
    +                )
    +
    +
    +class IndentAndNewlineTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        requires('gui')
    +        cls.root = Tk()
    +        cls.root.withdraw()
    +        cls.window = Editor(root=cls.root)
    +        cls.window.indentwidth = 2
    +        cls.window.tabwidth = 2
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        cls.window._close()
    +        del cls.window
    +        cls.root.update_idletasks()
    +        for id in cls.root.tk.call('after', 'info'):
    +            cls.root.after_cancel(id)
    +        cls.root.destroy()
    +        del cls.root
    +
    +    def insert(self, text):
    +        t = self.window.text
    +        t.delete('1.0', 'end')
    +        t.insert('end', text)
    +        # Force update for colorizer to finish.
    +        t.update()
    +
    +    def test_indent_and_newline_event(self):
    +        eq = self.assertEqual
    +        w = self.window
    +        text = w.text
    +        get = text.get
    +        nl = w.newline_and_indent_event
    +
    +        TestInfo = namedtuple('Tests', ['label', 'text', 'expected', 'mark'])
    +
    +        tests = (TestInfo('Empty line inserts with no indent.',
    +                          '  \n  def __init__(self):',
    +                          '\n  \n  def __init__(self):\n',
    +                          '1.end'),
    +                 TestInfo('Inside bracket before space, deletes space.',
    +                          '  def f1(self, a, b):',
    +                          '  def f1(self,\n         a, b):\n',
    +                          '1.14'),
    +                 TestInfo('Inside bracket after space, deletes space.',
    +                          '  def f1(self, a, b):',
    +                          '  def f1(self,\n         a, b):\n',
    +                          '1.15'),
    +                 TestInfo('Inside string with one line - no indent.',
    +                          '  """Docstring."""',
    +                          '  """Docstring.\n"""\n',
    +                          '1.15'),
    +                 TestInfo('Inside string with more than one line.',
    +                          '  """Docstring.\n  Docstring Line 2"""',
    +                          '  """Docstring.\n  Docstring Line 2\n  """\n',
    +                          '2.18'),
    +                 TestInfo('Backslash with one line.',
    +                          'a =\\',
    +                          'a =\\\n  \n',
    +                          '1.end'),
    +                 TestInfo('Backslash with more than one line.',
    +                          'a =\\\n          multiline\\',
    +                          'a =\\\n          multiline\\\n          \n',
    +                          '2.end'),
    +                 TestInfo('Block opener - indents +1 level.',
    +                          '  def f1(self):\n    pass',
    +                          '  def f1(self):\n    \n    pass\n',
    +                          '1.end'),
    +                 TestInfo('Block closer - dedents -1 level.',
    +                          '  def f1(self):\n    pass',
    +                          '  def f1(self):\n    pass\n  \n',
    +                          '2.end'),
    +                 )
    +
    +        w.prompt_last_line = ''
    +        for test in tests:
    +            with self.subTest(label=test.label):
    +                self.insert(test.text)
    +                text.mark_set('insert', test.mark)
    +                nl(event=None)
    +                eq(get('1.0', 'end'), test.expected)
    +
    +        # Selected text.
    +        self.insert('  def f1(self, a, b):\n    return a + b')
    +        text.tag_add('sel', '1.17', '1.end')
    +        nl(None)
    +        # Deletes selected text before adding new line.
    +        eq(get('1.0', 'end'), '  def f1(self, a,\n         \n    return a + b\n')
    +
    +        # Preserves the whitespace in shell prompt.
    +        w.prompt_last_line = '>>> '
    +        self.insert('>>> \t\ta =')
    +        text.mark_set('insert', '1.5')
    +        nl(None)
    +        eq(get('1.0', 'end'), '>>> \na =\n')
    +
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_filelist.py b/PythonLib/full/idlelib/idle_test/test_filelist.py
    new file mode 100644
    index 00000000..731f1975
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_filelist.py
    @@ -0,0 +1,33 @@
    +"Test filelist, coverage 19%."
    +
    +from idlelib import filelist
    +import unittest
    +from test.support import requires
    +from tkinter import Tk
    +
    +class FileListTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        requires('gui')
    +        cls.root = Tk()
    +        cls.root.withdraw()
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        cls.root.update_idletasks()
    +        for id in cls.root.tk.call('after', 'info'):
    +            cls.root.after_cancel(id)
    +        cls.root.destroy()
    +        del cls.root
    +
    +    def test_new_empty(self):
    +        flist = filelist.FileList(self.root)
    +        self.assertEqual(flist.root, self.root)
    +        e = flist.new()
    +        self.assertEqual(type(e), flist.EditorWindow)
    +        e._close()
    +
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_format.py b/PythonLib/full/idlelib/idle_test/test_format.py
    new file mode 100644
    index 00000000..a79bb515
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_format.py
    @@ -0,0 +1,668 @@
    +"Test format, coverage 99%."
    +
    +from idlelib import format as ft
    +import unittest
    +from unittest import mock
    +from test.support import requires
    +from tkinter import Tk, Text
    +from idlelib.editor import EditorWindow
    +from idlelib.idle_test.mock_idle import Editor as MockEditor
    +
    +
    +class Is_Get_Test(unittest.TestCase):
    +    """Test the is_ and get_ functions"""
    +    test_comment = '# This is a comment'
    +    test_nocomment = 'This is not a comment'
    +    trailingws_comment = '# This is a comment   '
    +    leadingws_comment = '    # This is a comment'
    +    leadingws_nocomment = '    This is not a comment'
    +
    +    def test_is_all_white(self):
    +        self.assertTrue(ft.is_all_white(''))
    +        self.assertTrue(ft.is_all_white('\t\n\r\f\v'))
    +        self.assertFalse(ft.is_all_white(self.test_comment))
    +
    +    def test_get_indent(self):
    +        Equal = self.assertEqual
    +        Equal(ft.get_indent(self.test_comment), '')
    +        Equal(ft.get_indent(self.trailingws_comment), '')
    +        Equal(ft.get_indent(self.leadingws_comment), '    ')
    +        Equal(ft.get_indent(self.leadingws_nocomment), '    ')
    +
    +    def test_get_comment_header(self):
    +        Equal = self.assertEqual
    +        # Test comment strings
    +        Equal(ft.get_comment_header(self.test_comment), '#')
    +        Equal(ft.get_comment_header(self.trailingws_comment), '#')
    +        Equal(ft.get_comment_header(self.leadingws_comment), '    #')
    +        # Test non-comment strings
    +        Equal(ft.get_comment_header(self.leadingws_nocomment), '    ')
    +        Equal(ft.get_comment_header(self.test_nocomment), '')
    +
    +
    +class FindTest(unittest.TestCase):
    +    """Test the find_paragraph function in paragraph module.
    +
    +    Using the runcase() function, find_paragraph() is called with 'mark' set at
    +    multiple indexes before and inside the test paragraph.
    +
    +    It appears that code with the same indentation as a quoted string is grouped
    +    as part of the same paragraph, which is probably incorrect behavior.
    +    """
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        from idlelib.idle_test.mock_tk import Text
    +        cls.text = Text()
    +
    +    def runcase(self, inserttext, stopline, expected):
    +        # Check that find_paragraph returns the expected paragraph when
    +        # the mark index is set to beginning, middle, end of each line
    +        # up to but not including the stop line
    +        text = self.text
    +        text.insert('1.0', inserttext)
    +        for line in range(1, stopline):
    +            linelength = int(text.index("%d.end" % line).split('.')[1])
    +            for col in (0, linelength//2, linelength):
    +                tempindex = "%d.%d" % (line, col)
    +                self.assertEqual(ft.find_paragraph(text, tempindex), expected)
    +        text.delete('1.0', 'end')
    +
    +    def test_find_comment(self):
    +        comment = (
    +            "# Comment block with no blank lines before\n"
    +            "# Comment line\n"
    +            "\n")
    +        self.runcase(comment, 3, ('1.0', '3.0', '#', comment[0:58]))
    +
    +        comment = (
    +            "\n"
    +            "# Comment block with whitespace line before and after\n"
    +            "# Comment line\n"
    +            "\n")
    +        self.runcase(comment, 4, ('2.0', '4.0', '#', comment[1:70]))
    +
    +        comment = (
    +            "\n"
    +            "    # Indented comment block with whitespace before and after\n"
    +            "    # Comment line\n"
    +            "\n")
    +        self.runcase(comment, 4, ('2.0', '4.0', '    #', comment[1:82]))
    +
    +        comment = (
    +            "\n"
    +            "# Single line comment\n"
    +            "\n")
    +        self.runcase(comment, 3, ('2.0', '3.0', '#', comment[1:23]))
    +
    +        comment = (
    +            "\n"
    +            "    # Single line comment with leading whitespace\n"
    +            "\n")
    +        self.runcase(comment, 3, ('2.0', '3.0', '    #', comment[1:51]))
    +
    +        comment = (
    +            "\n"
    +            "# Comment immediately followed by code\n"
    +            "x = 42\n"
    +            "\n")
    +        self.runcase(comment, 3, ('2.0', '3.0', '#', comment[1:40]))
    +
    +        comment = (
    +            "\n"
    +            "    # Indented comment immediately followed by code\n"
    +            "x = 42\n"
    +            "\n")
    +        self.runcase(comment, 3, ('2.0', '3.0', '    #', comment[1:53]))
    +
    +        comment = (
    +            "\n"
    +            "# Comment immediately followed by indented code\n"
    +            "    x = 42\n"
    +            "\n")
    +        self.runcase(comment, 3, ('2.0', '3.0', '#', comment[1:49]))
    +
    +    def test_find_paragraph(self):
    +        teststring = (
    +            '"""String with no blank lines before\n'
    +            'String line\n'
    +            '"""\n'
    +            '\n')
    +        self.runcase(teststring, 4, ('1.0', '4.0', '', teststring[0:53]))
    +
    +        teststring = (
    +            "\n"
    +            '"""String with whitespace line before and after\n'
    +            'String line.\n'
    +            '"""\n'
    +            '\n')
    +        self.runcase(teststring, 5, ('2.0', '5.0', '', teststring[1:66]))
    +
    +        teststring = (
    +            '\n'
    +            '    """Indented string with whitespace before and after\n'
    +            '    Comment string.\n'
    +            '    """\n'
    +            '\n')
    +        self.runcase(teststring, 5, ('2.0', '5.0', '    ', teststring[1:85]))
    +
    +        teststring = (
    +            '\n'
    +            '"""Single line string."""\n'
    +            '\n')
    +        self.runcase(teststring, 3, ('2.0', '3.0', '', teststring[1:27]))
    +
    +        teststring = (
    +            '\n'
    +            '    """Single line string with leading whitespace."""\n'
    +            '\n')
    +        self.runcase(teststring, 3, ('2.0', '3.0', '    ', teststring[1:55]))
    +
    +
    +class ReformatFunctionTest(unittest.TestCase):
    +    """Test the reformat_paragraph function without the editor window."""
    +
    +    def test_reformat_paragraph(self):
    +        Equal = self.assertEqual
    +        reform = ft.reformat_paragraph
    +        hw = "O hello world"
    +        Equal(reform(' ', 1), ' ')
    +        Equal(reform("Hello    world", 20), "Hello  world")
    +
    +        # Test without leading newline
    +        Equal(reform(hw, 1), "O\nhello\nworld")
    +        Equal(reform(hw, 6), "O\nhello\nworld")
    +        Equal(reform(hw, 7), "O hello\nworld")
    +        Equal(reform(hw, 12), "O hello\nworld")
    +        Equal(reform(hw, 13), "O hello world")
    +
    +        # Test with leading newline
    +        hw = "\nO hello world"
    +        Equal(reform(hw, 1), "\nO\nhello\nworld")
    +        Equal(reform(hw, 6), "\nO\nhello\nworld")
    +        Equal(reform(hw, 7), "\nO hello\nworld")
    +        Equal(reform(hw, 12), "\nO hello\nworld")
    +        Equal(reform(hw, 13), "\nO hello world")
    +
    +
    +class ReformatCommentTest(unittest.TestCase):
    +    """Test the reformat_comment function without the editor window."""
    +
    +    def test_reformat_comment(self):
    +        Equal = self.assertEqual
    +
    +        # reformat_comment formats to a minimum of 20 characters
    +        test_string = (
    +            "    \"\"\"this is a test of a reformat for a triple quoted string"
    +            " will it reformat to less than 70 characters for me?\"\"\"")
    +        result = ft.reformat_comment(test_string, 70, "    ")
    +        expected = (
    +            "    \"\"\"this is a test of a reformat for a triple quoted string will it\n"
    +            "    reformat to less than 70 characters for me?\"\"\"")
    +        Equal(result, expected)
    +
    +        test_comment = (
    +            "# this is a test of a reformat for a triple quoted string will "
    +            "it reformat to less than 70 characters for me?")
    +        result = ft.reformat_comment(test_comment, 70, "#")
    +        expected = (
    +            "# this is a test of a reformat for a triple quoted string will it\n"
    +            "# reformat to less than 70 characters for me?")
    +        Equal(result, expected)
    +
    +
    +class FormatClassTest(unittest.TestCase):
    +    def test_init_close(self):
    +        instance = ft.FormatParagraph('editor')
    +        self.assertEqual(instance.editwin, 'editor')
    +        instance.close()
    +        self.assertEqual(instance.editwin, None)
    +
    +
    +# For testing format_paragraph_event, Initialize FormatParagraph with
    +# a mock Editor with .text and  .get_selection_indices.  The text must
    +# be a Text wrapper that adds two methods
    +
    +# A real EditorWindow creates unneeded, time-consuming baggage and
    +# sometimes emits shutdown warnings like this:
    +# "warning: callback failed in WindowList 
    +# : invalid command name ".55131368.windows".
    +# Calling EditorWindow._close in tearDownClass prevents this but causes
    +# other problems (windows left open).
    +
    +class TextWrapper:
    +    def __init__(self, master):
    +        self.text = Text(master=master)
    +    def __getattr__(self, name):
    +        return getattr(self.text, name)
    +    def undo_block_start(self): pass
    +    def undo_block_stop(self): pass
    +
    +class Editor:
    +    def __init__(self, root):
    +        self.text = TextWrapper(root)
    +    get_selection_indices = EditorWindow. get_selection_indices
    +
    +class FormatEventTest(unittest.TestCase):
    +    """Test the formatting of text inside a Text widget.
    +
    +    This is done with FormatParagraph.format.paragraph_event,
    +    which calls functions in the module as appropriate.
    +    """
    +    test_string = (
    +        "    '''this is a test of a reformat for a triple "
    +        "quoted string will it reformat to less than 70 "
    +        "characters for me?'''\n")
    +    multiline_test_string = (
    +        "    '''The first line is under the max width.\n"
    +        "    The second line's length is way over the max width. It goes "
    +        "on and on until it is over 100 characters long.\n"
    +        "    Same thing with the third line. It is also way over the max "
    +        "width, but FormatParagraph will fix it.\n"
    +        "    '''\n")
    +    multiline_test_comment = (
    +        "# The first line is under the max width.\n"
    +        "# The second line's length is way over the max width. It goes on "
    +        "and on until it is over 100 characters long.\n"
    +        "# Same thing with the third line. It is also way over the max "
    +        "width, but FormatParagraph will fix it.\n"
    +        "# The fourth line is short like the first line.")
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        requires('gui')
    +        cls.root = Tk()
    +        cls.root.withdraw()
    +        editor = Editor(root=cls.root)
    +        cls.text = editor.text.text  # Test code does not need the wrapper.
    +        cls.formatter = ft.FormatParagraph(editor).format_paragraph_event
    +        # Sets the insert mark just after the re-wrapped and inserted  text.
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        del cls.text, cls.formatter
    +        cls.root.update_idletasks()
    +        cls.root.destroy()
    +        del cls.root
    +
    +    def test_short_line(self):
    +        self.text.insert('1.0', "Short line\n")
    +        self.formatter("Dummy")
    +        self.assertEqual(self.text.get('1.0', 'insert'), "Short line\n" )
    +        self.text.delete('1.0', 'end')
    +
    +    def test_long_line(self):
    +        text = self.text
    +
    +        # Set cursor ('insert' mark) to '1.0', within text.
    +        text.insert('1.0', self.test_string)
    +        text.mark_set('insert', '1.0')
    +        self.formatter('ParameterDoesNothing', limit=70)
    +        result = text.get('1.0', 'insert')
    +        # find function includes \n
    +        expected = (
    +"    '''this is a test of a reformat for a triple quoted string will it\n"
    +"    reformat to less than 70 characters for me?'''\n")  # yes
    +        self.assertEqual(result, expected)
    +        text.delete('1.0', 'end')
    +
    +        # Select from 1.11 to line end.
    +        text.insert('1.0', self.test_string)
    +        text.tag_add('sel', '1.11', '1.end')
    +        self.formatter('ParameterDoesNothing', limit=70)
    +        result = text.get('1.0', 'insert')
    +        # selection excludes \n
    +        expected = (
    +"    '''this is a test of a reformat for a triple quoted string will it reformat\n"
    +" to less than 70 characters for me?'''")  # no
    +        self.assertEqual(result, expected)
    +        text.delete('1.0', 'end')
    +
    +    def test_multiple_lines(self):
    +        text = self.text
    +        #  Select 2 long lines.
    +        text.insert('1.0', self.multiline_test_string)
    +        text.tag_add('sel', '2.0', '4.0')
    +        self.formatter('ParameterDoesNothing', limit=70)
    +        result = text.get('2.0', 'insert')
    +        expected = (
    +"    The second line's length is way over the max width. It goes on and\n"
    +"    on until it is over 100 characters long. Same thing with the third\n"
    +"    line. It is also way over the max width, but FormatParagraph will\n"
    +"    fix it.\n")
    +        self.assertEqual(result, expected)
    +        text.delete('1.0', 'end')
    +
    +    def test_comment_block(self):
    +        text = self.text
    +
    +        # Set cursor ('insert') to '1.0', within block.
    +        text.insert('1.0', self.multiline_test_comment)
    +        self.formatter('ParameterDoesNothing', limit=70)
    +        result = text.get('1.0', 'insert')
    +        expected = (
    +"# The first line is under the max width. The second line's length is\n"
    +"# way over the max width. It goes on and on until it is over 100\n"
    +"# characters long. Same thing with the third line. It is also way over\n"
    +"# the max width, but FormatParagraph will fix it. The fourth line is\n"
    +"# short like the first line.\n")
    +        self.assertEqual(result, expected)
    +        text.delete('1.0', 'end')
    +
    +        # Select line 2, verify line 1 unaffected.
    +        text.insert('1.0', self.multiline_test_comment)
    +        text.tag_add('sel', '2.0', '3.0')
    +        self.formatter('ParameterDoesNothing', limit=70)
    +        result = text.get('1.0', 'insert')
    +        expected = (
    +"# The first line is under the max width.\n"
    +"# The second line's length is way over the max width. It goes on and\n"
    +"# on until it is over 100 characters long.\n")
    +        self.assertEqual(result, expected)
    +        text.delete('1.0', 'end')
    +
    +# The following block worked with EditorWindow but fails with the mock.
    +# Lines 2 and 3 get pasted together even though the previous block left
    +# the previous line alone. More investigation is needed.
    +##        # Select lines 3 and 4
    +##        text.insert('1.0', self.multiline_test_comment)
    +##        text.tag_add('sel', '3.0', '5.0')
    +##        self.formatter('ParameterDoesNothing')
    +##        result = text.get('3.0', 'insert')
    +##        expected = (
    +##"# Same thing with the third line. It is also way over the max width,\n"
    +##"# but FormatParagraph will fix it. The fourth line is short like the\n"
    +##"# first line.\n")
    +##        self.assertEqual(result, expected)
    +##        text.delete('1.0', 'end')
    +
    +
    +class DummyEditwin:
    +    def __init__(self, root, text):
    +        self.root = root
    +        self.text = text
    +        self.indentwidth = 4
    +        self.tabwidth = 4
    +        self.usetabs = False
    +        self.context_use_ps1 = True
    +
    +    _make_blanks = EditorWindow._make_blanks
    +    get_selection_indices = EditorWindow.get_selection_indices
    +
    +
    +class FormatRegionTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        requires('gui')
    +        cls.root = Tk()
    +        cls.root.withdraw()
    +        cls.text = Text(cls.root)
    +        cls.text.undo_block_start = mock.Mock()
    +        cls.text.undo_block_stop = mock.Mock()
    +        cls.editor = DummyEditwin(cls.root, cls.text)
    +        cls.formatter = ft.FormatRegion(cls.editor)
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        del cls.text, cls.formatter, cls.editor
    +        cls.root.update_idletasks()
    +        cls.root.destroy()
    +        del cls.root
    +
    +    def setUp(self):
    +        self.text.insert('1.0', self.code_sample)
    +
    +    def tearDown(self):
    +        self.text.delete('1.0', 'end')
    +
    +    code_sample = """\
    +# WS line needed for test.
    +class C1():
    +    # Class comment.
    +    def __init__(self, a, b):
    +        self.a = a
    +        self.b = b
    +
    +    def compare(self):
    +        if a > b:
    +            return a
    +        elif a < b:
    +            return b
    +        else:
    +            return None
    +"""
    +
    +    def test_get_region(self):
    +        get = self.formatter.get_region
    +        text = self.text
    +        eq = self.assertEqual
    +
    +        # Add selection.
    +        text.tag_add('sel', '7.0', '10.0')
    +        expected_lines = ['',
    +                          '    def compare(self):',
    +                          '        if a > b:',
    +                          '']
    +        eq(get(), ('7.0', '10.0', '\n'.join(expected_lines), expected_lines))
    +
    +        # Remove selection.
    +        text.tag_remove('sel', '1.0', 'end')
    +        eq(get(), ('15.0', '16.0', '\n', ['', '']))
    +
    +    def test_set_region(self):
    +        set_ = self.formatter.set_region
    +        text = self.text
    +        eq = self.assertEqual
    +
    +        save_bell = text.bell
    +        text.bell = mock.Mock()
    +        line6 = self.code_sample.splitlines()[5]
    +        line10 = self.code_sample.splitlines()[9]
    +
    +        text.tag_add('sel', '6.0', '11.0')
    +        head, tail, chars, lines = self.formatter.get_region()
    +
    +        # No changes.
    +        set_(head, tail, chars, lines)
    +        text.bell.assert_called_once()
    +        eq(text.get('6.0', '11.0'), chars)
    +        eq(text.get('sel.first', 'sel.last'), chars)
    +        text.tag_remove('sel', '1.0', 'end')
    +
    +        # Alter selected lines by changing lines and adding a newline.
    +        newstring = 'added line 1\n\n\n\n'
    +        newlines = newstring.split('\n')
    +        set_('7.0', '10.0', chars, newlines)
    +        # Selection changed.
    +        eq(text.get('sel.first', 'sel.last'), newstring)
    +        # Additional line added, so last index is changed.
    +        eq(text.get('7.0', '11.0'), newstring)
    +        # Before and after lines unchanged.
    +        eq(text.get('6.0', '7.0-1c'), line6)
    +        eq(text.get('11.0', '12.0-1c'), line10)
    +        text.tag_remove('sel', '1.0', 'end')
    +
    +        text.bell = save_bell
    +
    +    def test_indent_region_event(self):
    +        indent = self.formatter.indent_region_event
    +        text = self.text
    +        eq = self.assertEqual
    +
    +        text.tag_add('sel', '7.0', '10.0')
    +        indent()
    +        # Blank lines aren't affected by indent.
    +        eq(text.get('7.0', '10.0'), ('\n        def compare(self):\n            if a > b:\n'))
    +
    +    def test_dedent_region_event(self):
    +        dedent = self.formatter.dedent_region_event
    +        text = self.text
    +        eq = self.assertEqual
    +
    +        text.tag_add('sel', '7.0', '10.0')
    +        dedent()
    +        # Blank lines aren't affected by dedent.
    +        eq(text.get('7.0', '10.0'), ('\ndef compare(self):\n    if a > b:\n'))
    +
    +    def test_comment_region_event(self):
    +        comment = self.formatter.comment_region_event
    +        text = self.text
    +        eq = self.assertEqual
    +
    +        text.tag_add('sel', '7.0', '10.0')
    +        comment()
    +        eq(text.get('7.0', '10.0'), ('##\n##    def compare(self):\n##        if a > b:\n'))
    +
    +    def test_uncomment_region_event(self):
    +        comment = self.formatter.comment_region_event
    +        uncomment = self.formatter.uncomment_region_event
    +        text = self.text
    +        eq = self.assertEqual
    +
    +        text.tag_add('sel', '7.0', '10.0')
    +        comment()
    +        uncomment()
    +        eq(text.get('7.0', '10.0'), ('\n    def compare(self):\n        if a > b:\n'))
    +
    +        # Only remove comments at the beginning of a line.
    +        text.tag_remove('sel', '1.0', 'end')
    +        text.tag_add('sel', '3.0', '4.0')
    +        uncomment()
    +        eq(text.get('3.0', '3.end'), ('    # Class comment.'))
    +
    +        self.formatter.set_region('3.0', '4.0', '', ['# Class comment.', ''])
    +        uncomment()
    +        eq(text.get('3.0', '3.end'), (' Class comment.'))
    +
    +    @mock.patch.object(ft.FormatRegion, "_asktabwidth")
    +    def test_tabify_region_event(self, _asktabwidth):
    +        tabify = self.formatter.tabify_region_event
    +        text = self.text
    +        eq = self.assertEqual
    +
    +        text.tag_add('sel', '7.0', '10.0')
    +        # No tabwidth selected.
    +        _asktabwidth.return_value = None
    +        self.assertIsNone(tabify())
    +
    +        _asktabwidth.return_value = 3
    +        self.assertIsNotNone(tabify())
    +        eq(text.get('7.0', '10.0'), ('\n\t def compare(self):\n\t\t  if a > b:\n'))
    +
    +    @mock.patch.object(ft.FormatRegion, "_asktabwidth")
    +    def test_untabify_region_event(self, _asktabwidth):
    +        untabify = self.formatter.untabify_region_event
    +        text = self.text
    +        eq = self.assertEqual
    +
    +        text.tag_add('sel', '7.0', '10.0')
    +        # No tabwidth selected.
    +        _asktabwidth.return_value = None
    +        self.assertIsNone(untabify())
    +
    +        _asktabwidth.return_value = 2
    +        self.formatter.tabify_region_event()
    +        _asktabwidth.return_value = 3
    +        self.assertIsNotNone(untabify())
    +        eq(text.get('7.0', '10.0'), ('\n      def compare(self):\n            if a > b:\n'))
    +
    +    @mock.patch.object(ft, "askinteger")
    +    def test_ask_tabwidth(self, askinteger):
    +        ask = self.formatter._asktabwidth
    +        askinteger.return_value = 10
    +        self.assertEqual(ask(), 10)
    +
    +
    +class IndentsTest(unittest.TestCase):
    +
    +    @mock.patch.object(ft, "askyesno")
    +    def test_toggle_tabs(self, askyesno):
    +        editor = DummyEditwin(None, None)  # usetabs == False.
    +        indents = ft.Indents(editor)
    +        askyesno.return_value = True
    +
    +        indents.toggle_tabs_event(None)
    +        self.assertEqual(editor.usetabs, True)
    +        self.assertEqual(editor.indentwidth, 8)
    +
    +        indents.toggle_tabs_event(None)
    +        self.assertEqual(editor.usetabs, False)
    +        self.assertEqual(editor.indentwidth, 8)
    +
    +    @mock.patch.object(ft, "askinteger")
    +    def test_change_indentwidth(self, askinteger):
    +        editor = DummyEditwin(None, None)  # indentwidth == 4.
    +        indents = ft.Indents(editor)
    +
    +        askinteger.return_value = None
    +        indents.change_indentwidth_event(None)
    +        self.assertEqual(editor.indentwidth, 4)
    +
    +        askinteger.return_value = 3
    +        indents.change_indentwidth_event(None)
    +        self.assertEqual(editor.indentwidth, 3)
    +
    +        askinteger.return_value = 5
    +        editor.usetabs = True
    +        indents.change_indentwidth_event(None)
    +        self.assertEqual(editor.indentwidth, 3)
    +
    +
    +class RstripTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        requires('gui')
    +        cls.root = Tk()
    +        cls.root.withdraw()
    +        cls.text = Text(cls.root)
    +        cls.editor = MockEditor(text=cls.text)
    +        cls.do_rstrip = ft.Rstrip(cls.editor).do_rstrip
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        del cls.text, cls.do_rstrip, cls.editor
    +        cls.root.update_idletasks()
    +        cls.root.destroy()
    +        del cls.root
    +
    +    def tearDown(self):
    +        self.text.delete('1.0', 'end-1c')
    +
    +    def test_rstrip_lines(self):
    +        original = (
    +            "Line with an ending tab    \n"
    +            "Line ending in 5 spaces     \n"
    +            "Linewithnospaces\n"
    +            "    indented line\n"
    +            "    indented line with trailing space \n"
    +            "    \n")
    +        stripped = (
    +            "Line with an ending tab\n"
    +            "Line ending in 5 spaces\n"
    +            "Linewithnospaces\n"
    +            "    indented line\n"
    +            "    indented line with trailing space\n")
    +
    +        self.text.insert('1.0', original)
    +        self.do_rstrip()
    +        self.assertEqual(self.text.get('1.0', 'insert'), stripped)
    +
    +    def test_rstrip_end(self):
    +        text = self.text
    +        for code in ('', '\n', '\n\n\n'):
    +            with self.subTest(code=code):
    +                text.insert('1.0', code)
    +                self.do_rstrip()
    +                self.assertEqual(text.get('1.0','end-1c'), '')
    +        for code in ('a\n', 'a\n\n', 'a\n\n\n'):
    +            with self.subTest(code=code):
    +                text.delete('1.0', 'end-1c')
    +                text.insert('1.0', code)
    +                self.do_rstrip()
    +                self.assertEqual(text.get('1.0','end-1c'), 'a\n')
    +
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2, exit=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_formatparagraph.py b/PythonLib/full/idlelib/idle_test/test_formatparagraph.py
    deleted file mode 100644
    index 068ae381..00000000
    --- a/PythonLib/full/idlelib/idle_test/test_formatparagraph.py
    +++ /dev/null
    @@ -1,376 +0,0 @@
    -# Test the functions and main class method of FormatParagraph.py
    -import unittest
    -from idlelib import FormatParagraph as fp
    -from idlelib.EditorWindow import EditorWindow
    -from Tkinter import Tk, Text
    -from test.test_support import requires
    -
    -
    -class Is_Get_Test(unittest.TestCase):
    -    """Test the is_ and get_ functions"""
    -    test_comment = '# This is a comment'
    -    test_nocomment = 'This is not a comment'
    -    trailingws_comment = '# This is a comment   '
    -    leadingws_comment = '    # This is a comment'
    -    leadingws_nocomment = '    This is not a comment'
    -
    -    def test_is_all_white(self):
    -        self.assertTrue(fp.is_all_white(''))
    -        self.assertTrue(fp.is_all_white('\t\n\r\f\v'))
    -        self.assertFalse(fp.is_all_white(self.test_comment))
    -
    -    def test_get_indent(self):
    -        Equal = self.assertEqual
    -        Equal(fp.get_indent(self.test_comment), '')
    -        Equal(fp.get_indent(self.trailingws_comment), '')
    -        Equal(fp.get_indent(self.leadingws_comment), '    ')
    -        Equal(fp.get_indent(self.leadingws_nocomment), '    ')
    -
    -    def test_get_comment_header(self):
    -        Equal = self.assertEqual
    -        # Test comment strings
    -        Equal(fp.get_comment_header(self.test_comment), '#')
    -        Equal(fp.get_comment_header(self.trailingws_comment), '#')
    -        Equal(fp.get_comment_header(self.leadingws_comment), '    #')
    -        # Test non-comment strings
    -        Equal(fp.get_comment_header(self.leadingws_nocomment), '    ')
    -        Equal(fp.get_comment_header(self.test_nocomment), '')
    -
    -
    -class FindTest(unittest.TestCase):
    -    """Test the find_paragraph function in FormatParagraph.
    -
    -    Using the runcase() function, find_paragraph() is called with 'mark' set at
    -    multiple indexes before and inside the test paragraph.
    -
    -    It appears that code with the same indentation as a quoted string is grouped
    -    as part of the same paragraph, which is probably incorrect behavior.
    -    """
    -
    -    @classmethod
    -    def setUpClass(cls):
    -        from idlelib.idle_test.mock_tk import Text
    -        cls.text = Text()
    -
    -    def runcase(self, inserttext, stopline, expected):
    -        # Check that find_paragraph returns the expected paragraph when
    -        # the mark index is set to beginning, middle, end of each line
    -        # up to but not including the stop line
    -        text = self.text
    -        text.insert('1.0', inserttext)
    -        for line in range(1, stopline):
    -            linelength = int(text.index("%d.end" % line).split('.')[1])
    -            for col in (0, linelength//2, linelength):
    -                tempindex = "%d.%d" % (line, col)
    -                self.assertEqual(fp.find_paragraph(text, tempindex), expected)
    -        text.delete('1.0', 'end')
    -
    -    def test_find_comment(self):
    -        comment = (
    -            "# Comment block with no blank lines before\n"
    -            "# Comment line\n"
    -            "\n")
    -        self.runcase(comment, 3, ('1.0', '3.0', '#', comment[0:58]))
    -
    -        comment = (
    -            "\n"
    -            "# Comment block with whitespace line before and after\n"
    -            "# Comment line\n"
    -            "\n")
    -        self.runcase(comment, 4, ('2.0', '4.0', '#', comment[1:70]))
    -
    -        comment = (
    -            "\n"
    -            "    # Indented comment block with whitespace before and after\n"
    -            "    # Comment line\n"
    -            "\n")
    -        self.runcase(comment, 4, ('2.0', '4.0', '    #', comment[1:82]))
    -
    -        comment = (
    -            "\n"
    -            "# Single line comment\n"
    -            "\n")
    -        self.runcase(comment, 3, ('2.0', '3.0', '#', comment[1:23]))
    -
    -        comment = (
    -            "\n"
    -            "    # Single line comment with leading whitespace\n"
    -            "\n")
    -        self.runcase(comment, 3, ('2.0', '3.0', '    #', comment[1:51]))
    -
    -        comment = (
    -            "\n"
    -            "# Comment immediately followed by code\n"
    -            "x = 42\n"
    -            "\n")
    -        self.runcase(comment, 3, ('2.0', '3.0', '#', comment[1:40]))
    -
    -        comment = (
    -            "\n"
    -            "    # Indented comment immediately followed by code\n"
    -            "x = 42\n"
    -            "\n")
    -        self.runcase(comment, 3, ('2.0', '3.0', '    #', comment[1:53]))
    -
    -        comment = (
    -            "\n"
    -            "# Comment immediately followed by indented code\n"
    -            "    x = 42\n"
    -            "\n")
    -        self.runcase(comment, 3, ('2.0', '3.0', '#', comment[1:49]))
    -
    -    def test_find_paragraph(self):
    -        teststring = (
    -            '"""String with no blank lines before\n'
    -            'String line\n'
    -            '"""\n'
    -            '\n')
    -        self.runcase(teststring, 4, ('1.0', '4.0', '', teststring[0:53]))
    -
    -        teststring = (
    -            "\n"
    -            '"""String with whitespace line before and after\n'
    -            'String line.\n'
    -            '"""\n'
    -            '\n')
    -        self.runcase(teststring, 5, ('2.0', '5.0', '', teststring[1:66]))
    -
    -        teststring = (
    -            '\n'
    -            '    """Indented string with whitespace before and after\n'
    -            '    Comment string.\n'
    -            '    """\n'
    -            '\n')
    -        self.runcase(teststring, 5, ('2.0', '5.0', '    ', teststring[1:85]))
    -
    -        teststring = (
    -            '\n'
    -            '"""Single line string."""\n'
    -            '\n')
    -        self.runcase(teststring, 3, ('2.0', '3.0', '', teststring[1:27]))
    -
    -        teststring = (
    -            '\n'
    -            '    """Single line string with leading whitespace."""\n'
    -            '\n')
    -        self.runcase(teststring, 3, ('2.0', '3.0', '    ', teststring[1:55]))
    -
    -
    -class ReformatFunctionTest(unittest.TestCase):
    -    """Test the reformat_paragraph function without the editor window."""
    -
    -    def test_reformat_paragraph(self):
    -        Equal = self.assertEqual
    -        reform = fp.reformat_paragraph
    -        hw = "O hello world"
    -        Equal(reform(' ', 1), ' ')
    -        Equal(reform("Hello    world", 20), "Hello  world")
    -
    -        # Test without leading newline
    -        Equal(reform(hw, 1), "O\nhello\nworld")
    -        Equal(reform(hw, 6), "O\nhello\nworld")
    -        Equal(reform(hw, 7), "O hello\nworld")
    -        Equal(reform(hw, 12), "O hello\nworld")
    -        Equal(reform(hw, 13), "O hello world")
    -
    -        # Test with leading newline
    -        hw = "\nO hello world"
    -        Equal(reform(hw, 1), "\nO\nhello\nworld")
    -        Equal(reform(hw, 6), "\nO\nhello\nworld")
    -        Equal(reform(hw, 7), "\nO hello\nworld")
    -        Equal(reform(hw, 12), "\nO hello\nworld")
    -        Equal(reform(hw, 13), "\nO hello world")
    -
    -
    -class ReformatCommentTest(unittest.TestCase):
    -    """Test the reformat_comment function without the editor window."""
    -
    -    def test_reformat_comment(self):
    -        Equal = self.assertEqual
    -
    -        # reformat_comment formats to a minimum of 20 characters
    -        test_string = (
    -            "    \"\"\"this is a test of a reformat for a triple quoted string"
    -            " will it reformat to less than 70 characters for me?\"\"\"")
    -        result = fp.reformat_comment(test_string, 70, "    ")
    -        expected = (
    -            "    \"\"\"this is a test of a reformat for a triple quoted string will it\n"
    -            "    reformat to less than 70 characters for me?\"\"\"")
    -        Equal(result, expected)
    -
    -        test_comment = (
    -            "# this is a test of a reformat for a triple quoted string will "
    -            "it reformat to less than 70 characters for me?")
    -        result = fp.reformat_comment(test_comment, 70, "#")
    -        expected = (
    -            "# this is a test of a reformat for a triple quoted string will it\n"
    -            "# reformat to less than 70 characters for me?")
    -        Equal(result, expected)
    -
    -
    -class FormatClassTest(unittest.TestCase):
    -    def test_init_close(self):
    -        instance = fp.FormatParagraph('editor')
    -        self.assertEqual(instance.editwin, 'editor')
    -        instance.close()
    -        self.assertEqual(instance.editwin, None)
    -
    -
    -# For testing format_paragraph_event, Initialize FormatParagraph with
    -# a mock Editor with .text and  .get_selection_indices.  The text must
    -# be a Text wrapper that adds two methods
    -
    -# A real EditorWindow creates unneeded, time-consuming baggage and
    -# sometimes emits shutdown warnings like this:
    -# "warning: callback failed in WindowList 
    -# : invalid command name ".55131368.windows".
    -# Calling EditorWindow._close in tearDownClass prevents this but causes
    -# other problems (windows left open).
    -
    -class TextWrapper:
    -    def __init__(self, master):
    -        self.text = Text(master=master)
    -    def __getattr__(self, name):
    -        return getattr(self.text, name)
    -    def undo_block_start(self): pass
    -    def undo_block_stop(self): pass
    -
    -class Editor:
    -    def __init__(self, root):
    -        self.text = TextWrapper(root)
    -    get_selection_indices = EditorWindow. get_selection_indices.im_func
    -
    -class FormatEventTest(unittest.TestCase):
    -    """Test the formatting of text inside a Text widget.
    -
    -    This is done with FormatParagraph.format.paragraph_event,
    -    which calls functions in the module as appropriate.
    -    """
    -    test_string = (
    -        "    '''this is a test of a reformat for a triple "
    -        "quoted string will it reformat to less than 70 "
    -        "characters for me?'''\n")
    -    multiline_test_string = (
    -        "    '''The first line is under the max width.\n"
    -        "    The second line's length is way over the max width. It goes "
    -        "on and on until it is over 100 characters long.\n"
    -        "    Same thing with the third line. It is also way over the max "
    -        "width, but FormatParagraph will fix it.\n"
    -        "    '''\n")
    -    multiline_test_comment = (
    -        "# The first line is under the max width.\n"
    -        "# The second line's length is way over the max width. It goes on "
    -        "and on until it is over 100 characters long.\n"
    -        "# Same thing with the third line. It is also way over the max "
    -        "width, but FormatParagraph will fix it.\n"
    -        "# The fourth line is short like the first line.")
    -
    -    @classmethod
    -    def setUpClass(cls):
    -        requires('gui')
    -        cls.root = Tk()
    -        editor = Editor(root=cls.root)
    -        cls.text = editor.text.text  # Test code does not need the wrapper.
    -        cls.formatter = fp.FormatParagraph(editor).format_paragraph_event
    -        # Sets the insert mark just after the re-wrapped and inserted  text.
    -
    -    @classmethod
    -    def tearDownClass(cls):
    -        del cls.text, cls.formatter
    -        cls.root.destroy()
    -        del cls.root
    -
    -    def test_short_line(self):
    -        self.text.insert('1.0', "Short line\n")
    -        self.formatter("Dummy")
    -        self.assertEqual(self.text.get('1.0', 'insert'), "Short line\n" )
    -        self.text.delete('1.0', 'end')
    -
    -    def test_long_line(self):
    -        text = self.text
    -
    -        # Set cursor ('insert' mark) to '1.0', within text.
    -        text.insert('1.0', self.test_string)
    -        text.mark_set('insert', '1.0')
    -        self.formatter('ParameterDoesNothing', limit=70)
    -        result = text.get('1.0', 'insert')
    -        # find function includes \n
    -        expected = (
    -"    '''this is a test of a reformat for a triple quoted string will it\n"
    -"    reformat to less than 70 characters for me?'''\n")  # yes
    -        self.assertEqual(result, expected)
    -        text.delete('1.0', 'end')
    -
    -        # Select from 1.11 to line end.
    -        text.insert('1.0', self.test_string)
    -        text.tag_add('sel', '1.11', '1.end')
    -        self.formatter('ParameterDoesNothing', limit=70)
    -        result = text.get('1.0', 'insert')
    -        # selection excludes \n
    -        expected = (
    -"    '''this is a test of a reformat for a triple quoted string will it reformat\n"
    -" to less than 70 characters for me?'''")  # no
    -        self.assertEqual(result, expected)
    -        text.delete('1.0', 'end')
    -
    -    def test_multiple_lines(self):
    -        text = self.text
    -        #  Select 2 long lines.
    -        text.insert('1.0', self.multiline_test_string)
    -        text.tag_add('sel', '2.0', '4.0')
    -        self.formatter('ParameterDoesNothing', limit=70)
    -        result = text.get('2.0', 'insert')
    -        expected = (
    -"    The second line's length is way over the max width. It goes on and\n"
    -"    on until it is over 100 characters long. Same thing with the third\n"
    -"    line. It is also way over the max width, but FormatParagraph will\n"
    -"    fix it.\n")
    -        self.assertEqual(result, expected)
    -        text.delete('1.0', 'end')
    -
    -    def test_comment_block(self):
    -        text = self.text
    -
    -        # Set cursor ('insert') to '1.0', within block.
    -        text.insert('1.0', self.multiline_test_comment)
    -        self.formatter('ParameterDoesNothing', limit=70)
    -        result = text.get('1.0', 'insert')
    -        expected = (
    -"# The first line is under the max width. The second line's length is\n"
    -"# way over the max width. It goes on and on until it is over 100\n"
    -"# characters long. Same thing with the third line. It is also way over\n"
    -"# the max width, but FormatParagraph will fix it. The fourth line is\n"
    -"# short like the first line.\n")
    -        self.assertEqual(result, expected)
    -        text.delete('1.0', 'end')
    -
    -        # Select line 2, verify line 1 unaffected.
    -        text.insert('1.0', self.multiline_test_comment)
    -        text.tag_add('sel', '2.0', '3.0')
    -        self.formatter('ParameterDoesNothing', limit=70)
    -        result = text.get('1.0', 'insert')
    -        expected = (
    -"# The first line is under the max width.\n"
    -"# The second line's length is way over the max width. It goes on and\n"
    -"# on until it is over 100 characters long.\n")
    -        self.assertEqual(result, expected)
    -        text.delete('1.0', 'end')
    -
    -# The following block worked with EditorWindow but fails with the mock.
    -# Lines 2 and 3 get pasted together even though the previous block left
    -# the previous line alone. More investigation is needed.
    -##        # Select lines 3 and 4
    -##        text.insert('1.0', self.multiline_test_comment)
    -##        text.tag_add('sel', '3.0', '5.0')
    -##        self.formatter('ParameterDoesNothing')
    -##        result = text.get('3.0', 'insert')
    -##        expected = (
    -##"# Same thing with the third line. It is also way over the max width,\n"
    -##"# but FormatParagraph will fix it. The fourth line is short like the\n"
    -##"# first line.\n")
    -##        self.assertEqual(result, expected)
    -##        text.delete('1.0', 'end')
    -
    -
    -if __name__ == '__main__':
    -    unittest.main(verbosity=2, exit=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_grep.py b/PythonLib/full/idlelib/idle_test/test_grep.py
    index e9f4f22a..a0b5b691 100644
    --- a/PythonLib/full/idlelib/idle_test/test_grep.py
    +++ b/PythonLib/full/idlelib/idle_test/test_grep.py
    @@ -1,17 +1,17 @@
     """ !Changing this line will break Test_findfile.test_found!
    -Non-gui unit tests for idlelib.GrepDialog methods.
    +Non-gui unit tests for grep.GrepDialog methods.
     dummy_command calls grep_it calls findfiles.
     An exception raised in one method will fail callers.
     Otherwise, tests are mostly independent.
    -*** Currently only test grep_it.
    +Currently only test grep_it, coverage 51%.
     """
    +from idlelib import grep
     import unittest
    -from test.test_support import captured_stdout, findfile
    +from test.support import captured_stdout
     from idlelib.idle_test.mock_tk import Var
    -from idlelib.GrepDialog import GrepDialog
    +import os
     import re
     
    -__file__ = findfile('idlelib/idle_test') + '/test_grep.py'
     
     class Dummy_searchengine:
         '''GrepDialog.__init__ calls parent SearchDiabolBase which attaches the
    @@ -23,25 +23,97 @@ def getpat(self):
     
     searchengine = Dummy_searchengine()
     
    +
     class Dummy_grep:
         # Methods tested
         #default_command = GrepDialog.default_command
    -    grep_it = GrepDialog.grep_it.im_func
    -    findfiles = GrepDialog.findfiles.im_func
    +    grep_it = grep.GrepDialog.grep_it
         # Other stuff needed
         recvar = Var(False)
         engine = searchengine
         def close(self):  # gui method
             pass
     
    -grep = Dummy_grep()
    +_grep = Dummy_grep()
    +
     
     class FindfilesTest(unittest.TestCase):
    -    # findfiles is really a function, not a method, could be iterator
    -    # test that filename return filename
    -    # test that idlelib has many .py files
    -    # test that recursive flag adds idle_test .py files
    -    pass
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        cls.realpath = os.path.realpath(__file__)
    +        cls.path = os.path.dirname(cls.realpath)
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        del cls.realpath, cls.path
    +
    +    def test_invaliddir(self):
    +        with captured_stdout() as s:
    +            filelist = list(grep.findfiles('invaliddir', '*.*', False))
    +        self.assertEqual(filelist, [])
    +        self.assertIn('invalid', s.getvalue())
    +
    +    def test_curdir(self):
    +        # Test os.curdir.
    +        ff = grep.findfiles
    +        save_cwd = os.getcwd()
    +        os.chdir(self.path)
    +        filename = 'test_grep.py'
    +        filelist = list(ff(os.curdir, filename, False))
    +        self.assertIn(os.path.join(os.curdir, filename), filelist)
    +        os.chdir(save_cwd)
    +
    +    def test_base(self):
    +        ff = grep.findfiles
    +        readme = os.path.join(self.path, 'README.txt')
    +
    +        # Check for Python files in path where this file lives.
    +        filelist = list(ff(self.path, '*.py', False))
    +        # This directory has many Python files.
    +        self.assertGreater(len(filelist), 10)
    +        self.assertIn(self.realpath, filelist)
    +        self.assertNotIn(readme, filelist)
    +
    +        # Look for .txt files in path where this file lives.
    +        filelist = list(ff(self.path, '*.txt', False))
    +        self.assertNotEqual(len(filelist), 0)
    +        self.assertNotIn(self.realpath, filelist)
    +        self.assertIn(readme, filelist)
    +
    +        # Look for non-matching pattern.
    +        filelist = list(ff(self.path, 'grep.*', False))
    +        self.assertEqual(len(filelist), 0)
    +        self.assertNotIn(self.realpath, filelist)
    +
    +    def test_recurse(self):
    +        ff = grep.findfiles
    +        parent = os.path.dirname(self.path)
    +        grepfile = os.path.join(parent, 'grep.py')
    +        pat = '*.py'
    +
    +        # Get Python files only in parent directory.
    +        filelist = list(ff(parent, pat, False))
    +        parent_size = len(filelist)
    +        # Lots of Python files in idlelib.
    +        self.assertGreater(parent_size, 20)
    +        self.assertIn(grepfile, filelist)
    +        # Without subdirectories, this file isn't returned.
    +        self.assertNotIn(self.realpath, filelist)
    +
    +        # Include subdirectories.
    +        filelist = list(ff(parent, pat, True))
    +        # More files found now.
    +        self.assertGreater(len(filelist), parent_size)
    +        self.assertIn(grepfile, filelist)
    +        # This file exists in list now.
    +        self.assertIn(self.realpath, filelist)
    +
    +        # Check another level up the tree.
    +        parent = os.path.dirname(parent)
    +        filelist = list(ff(parent, '*.py', True))
    +        self.assertIn(self.realpath, filelist)
    +
     
     class Grep_itTest(unittest.TestCase):
         # Test captured reports with 0 and some hits.
    @@ -49,9 +121,9 @@ class Grep_itTest(unittest.TestCase):
         # from incomplete replacement, so 'later'.
     
         def report(self, pat):
    -        grep.engine._pat = pat
    +        _grep.engine._pat = pat
             with captured_stdout() as s:
    -            grep.grep_it(re.compile(pat), __file__)
    +            _grep.grep_it(re.compile(pat), __file__)
             lines = s.getvalue().split('\n')
             lines.pop()  # remove bogus '' after last \n
             return lines
    @@ -73,10 +145,12 @@ def test_found(self):
             self.assertIn('2', lines[3])  # hits found 2
             self.assertTrue(lines[4].startswith('(Hint:'))
     
    +
     class Default_commandTest(unittest.TestCase):
    -    # To write this, mode OutputWindow import to top of GrepDialog
    +    # To write this, move outwin import to top of GrepDialog
         # so it can be replaced by captured_stdout in class setup/teardown.
         pass
     
    +
     if __name__ == '__main__':
    -    unittest.main(verbosity=2, exit=False)
    +    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_help.py b/PythonLib/full/idlelib/idle_test/test_help.py
    new file mode 100644
    index 00000000..b5426599
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_help.py
    @@ -0,0 +1,34 @@
    +"Test help, coverage 87%."
    +
    +from idlelib import help
    +import unittest
    +from test.support import requires
    +requires('gui')
    +from os.path import abspath, dirname, join
    +from tkinter import Tk
    +
    +
    +class HelpFrameTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        "By itself, this tests that file parsed without exception."
    +        cls.root = root = Tk()
    +        root.withdraw()
    +        helpfile = join(dirname(dirname(abspath(__file__))), 'help.html')
    +        cls.frame = help.HelpFrame(root, helpfile)
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        del cls.frame
    +        cls.root.update_idletasks()
    +        cls.root.destroy()
    +        del cls.root
    +
    +    def test_line1(self):
    +        text = self.frame.text
    +        self.assertEqual(text.get('1.0', '1.end'), ' IDLE ')
    +
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_help_about.py b/PythonLib/full/idlelib/idle_test/test_help_about.py
    new file mode 100644
    index 00000000..7c148d23
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_help_about.py
    @@ -0,0 +1,182 @@
    +"""Test help_about, coverage 100%.
    +help_about.build_bits branches on sys.platform='darwin'.
    +'100% combines coverage on Mac and others.
    +"""
    +
    +from idlelib import help_about
    +import unittest
    +from test.support import requires, findfile
    +from tkinter import Tk, TclError
    +from idlelib.idle_test.mock_idle import Func
    +from idlelib.idle_test.mock_tk import Mbox_func
    +from idlelib import textview
    +import os.path
    +from platform import python_version
    +
    +About = help_about.AboutDialog
    +
    +
    +class LiveDialogTest(unittest.TestCase):
    +    """Simulate user clicking buttons other than [Close].
    +
    +    Test that invoked textview has text from source.
    +    """
    +    @classmethod
    +    def setUpClass(cls):
    +        requires('gui')
    +        cls.root = Tk()
    +        cls.root.withdraw()
    +        cls.dialog = About(cls.root, 'About IDLE', _utest=True)
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        del cls.dialog
    +        cls.root.update_idletasks()
    +        cls.root.destroy()
    +        del cls.root
    +
    +    def test_build_bits(self):
    +        self.assertIn(help_about.build_bits(), ('32', '64'))
    +
    +    def test_dialog_title(self):
    +        """Test about dialog title"""
    +        self.assertEqual(self.dialog.title(), 'About IDLE')
    +
    +    def test_dialog_logo(self):
    +        """Test about dialog logo."""
    +        path, file = os.path.split(self.dialog.icon_image['file'])
    +        fn, ext = os.path.splitext(file)
    +        self.assertEqual(fn, 'idle_48')
    +
    +    def test_printer_buttons(self):
    +        """Test buttons whose commands use printer function."""
    +        dialog = self.dialog
    +        button_sources = [(dialog.py_license, license, 'license'),
    +                          (dialog.py_copyright, copyright, 'copyright'),
    +                          (dialog.py_credits, credits, 'credits')]
    +
    +        for button, printer, name in button_sources:
    +            with self.subTest(name=name):
    +                printer._Printer__setup()
    +                button.invoke()
    +                get = dialog._current_textview.viewframe.textframe.text.get
    +                lines = printer._Printer__lines
    +                if len(lines) < 2:
    +                    self.fail(name + ' full text was not found')
    +                self.assertEqual(lines[0], get('1.0', '1.end'))
    +                self.assertEqual(lines[1], get('2.0', '2.end'))
    +                dialog._current_textview.destroy()
    +
    +    def test_file_buttons(self):
    +        """Test buttons that display files."""
    +        dialog = self.dialog
    +        button_sources = [(self.dialog.readme, 'README.txt', 'readme'),
    +                          (self.dialog.idle_news, 'NEWS.txt', 'news'),
    +                          (self.dialog.idle_credits, 'CREDITS.txt', 'credits')]
    +
    +        for button, filename, name in button_sources:
    +            with  self.subTest(name=name):
    +                button.invoke()
    +                fn = findfile(filename, subdir='idlelib')
    +                get = dialog._current_textview.viewframe.textframe.text.get
    +                with open(fn, encoding='utf-8') as f:
    +                    self.assertEqual(f.readline().strip(), get('1.0', '1.end'))
    +                    f.readline()
    +                    self.assertEqual(f.readline().strip(), get('3.0', '3.end'))
    +                dialog._current_textview.destroy()
    +
    +
    +class DefaultTitleTest(unittest.TestCase):
    +    "Test default title."
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        requires('gui')
    +        cls.root = Tk()
    +        cls.root.withdraw()
    +        cls.dialog = About(cls.root, _utest=True)
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        del cls.dialog
    +        cls.root.update_idletasks()
    +        cls.root.destroy()
    +        del cls.root
    +
    +    def test_dialog_title(self):
    +        """Test about dialog title"""
    +        self.assertEqual(self.dialog.title(),
    +                         f'About IDLE {python_version()}'
    +                         f' ({help_about.build_bits()} bit)')
    +
    +
    +class CloseTest(unittest.TestCase):
    +    """Simulate user clicking [Close] button"""
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        requires('gui')
    +        cls.root = Tk()
    +        cls.root.withdraw()
    +        cls.dialog = About(cls.root, 'About IDLE', _utest=True)
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        del cls.dialog
    +        cls.root.update_idletasks()
    +        cls.root.destroy()
    +        del cls.root
    +
    +    def test_close(self):
    +        self.assertEqual(self.dialog.winfo_class(), 'Toplevel')
    +        self.dialog.button_ok.invoke()
    +        with self.assertRaises(TclError):
    +            self.dialog.winfo_class()
    +
    +
    +class Dummy_about_dialog():
    +    # Dummy class for testing file display functions.
    +    idle_credits = About.show_idle_credits
    +    idle_readme = About.show_readme
    +    idle_news = About.show_idle_news
    +    # Called by the above
    +    display_file_text = About.display_file_text
    +    _utest = True
    +
    +
    +class DisplayFileTest(unittest.TestCase):
    +    """Test functions that display files.
    +
    +    While somewhat redundant with gui-based test_file_dialog,
    +    these unit tests run on all buildbots, not just a few.
    +    """
    +    dialog = Dummy_about_dialog()
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        cls.orig_error = textview.showerror
    +        cls.orig_view = textview.view_text
    +        cls.error = Mbox_func()
    +        cls.view = Func()
    +        textview.showerror = cls.error
    +        textview.view_text = cls.view
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        textview.showerror = cls.orig_error
    +        textview.view_text = cls.orig_view
    +
    +    def test_file_display(self):
    +        for handler in (self.dialog.idle_credits,
    +                        self.dialog.idle_readme,
    +                        self.dialog.idle_news):
    +            self.error.message = ''
    +            self.view.called = False
    +            with self.subTest(handler=handler):
    +                handler()
    +                self.assertEqual(self.error.message, '')
    +                self.assertEqual(self.view.called, True)
    +
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_helpabout.py b/PythonLib/full/idlelib/idle_test/test_helpabout.py
    deleted file mode 100644
    index 0046f877..00000000
    --- a/PythonLib/full/idlelib/idle_test/test_helpabout.py
    +++ /dev/null
    @@ -1,52 +0,0 @@
    -'''Test idlelib.help_about.
    -
    -Coverage:
    -'''
    -from idlelib import aboutDialog as help_about
    -from idlelib import textView as textview
    -from idlelib.idle_test.mock_idle import Func
    -from idlelib.idle_test.mock_tk import Mbox
    -import unittest
    -
    -About = help_about.AboutDialog
    -class Dummy_about_dialog():
    -    # Dummy class for testing file display functions.
    -    idle_credits = About.ShowIDLECredits.im_func
    -    idle_readme = About.ShowIDLEAbout.im_func
    -    idle_news = About.ShowIDLENEWS.im_func
    -    # Called by the above
    -    display_file_text = About.display_file_text.im_func
    -
    -
    -class DisplayFileTest(unittest.TestCase):
    -    "Test that .txt files are found and properly decoded."
    -    dialog = Dummy_about_dialog()
    -
    -    @classmethod
    -    def setUpClass(cls):
    -        cls.orig_mbox = textview.tkMessageBox
    -        cls.orig_view = textview.view_text
    -        cls.mbox = Mbox()
    -        cls.view = Func()
    -        textview.tkMessageBox = cls.mbox
    -        textview.view_text = cls.view
    -        cls.About = Dummy_about_dialog()
    -
    -    @classmethod
    -    def tearDownClass(cls):
    -        textview.tkMessageBox = cls.orig_mbox
    -        textview.view_text = cls.orig_view.im_func
    -
    -    def test_file_isplay(self):
    -        for handler in (self.dialog.idle_credits,
    -                        self.dialog.idle_readme,
    -                        self.dialog.idle_news):
    -            self.mbox.showerror.message = ''
    -            self.view.called = False
    -            handler()
    -            self.assertEqual(self.mbox.showerror.message, '')
    -            self.assertEqual(self.view.called, True)
    -
    -
    -if __name__ == '__main__':
    -    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_history.py b/PythonLib/full/idlelib/idle_test/test_history.py
    new file mode 100644
    index 00000000..67539651
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_history.py
    @@ -0,0 +1,172 @@
    +" Test history, coverage 100%."
    +
    +from idlelib.history import History
    +import unittest
    +from test.support import requires
    +
    +import tkinter as tk
    +from tkinter import Text as tkText
    +from idlelib.idle_test.mock_tk import Text as mkText
    +from idlelib.config import idleConf
    +
    +line1 = 'a = 7'
    +line2 = 'b = a'
    +
    +
    +class StoreTest(unittest.TestCase):
    +    '''Tests History.__init__ and History.store with mock Text'''
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        cls.text = mkText()
    +        cls.history = History(cls.text)
    +
    +    def tearDown(self):
    +        self.text.delete('1.0', 'end')
    +        self.history.history = []
    +
    +    def test_init(self):
    +        self.assertIs(self.history.text, self.text)
    +        self.assertEqual(self.history.history, [])
    +        self.assertIsNone(self.history.prefix)
    +        self.assertIsNone(self.history.pointer)
    +        self.assertEqual(self.history.cyclic,
    +                idleConf.GetOption("main", "History",  "cyclic", 1, "bool"))
    +
    +    def test_store_short(self):
    +        self.history.store('a')
    +        self.assertEqual(self.history.history, [])
    +        self.history.store('  a  ')
    +        self.assertEqual(self.history.history, [])
    +
    +    def test_store_dup(self):
    +        self.history.store(line1)
    +        self.assertEqual(self.history.history, [line1])
    +        self.history.store(line2)
    +        self.assertEqual(self.history.history, [line1, line2])
    +        self.history.store(line1)
    +        self.assertEqual(self.history.history, [line2, line1])
    +
    +    def test_store_reset(self):
    +        self.history.prefix = line1
    +        self.history.pointer = 0
    +        self.history.store(line2)
    +        self.assertIsNone(self.history.prefix)
    +        self.assertIsNone(self.history.pointer)
    +
    +
    +class TextWrapper:
    +    def __init__(self, master):
    +        self.text = tkText(master=master)
    +        self._bell = False
    +    def __getattr__(self, name):
    +        return getattr(self.text, name)
    +    def bell(self):
    +        self._bell = True
    +
    +
    +class FetchTest(unittest.TestCase):
    +    '''Test History.fetch with wrapped tk.Text.
    +    '''
    +    @classmethod
    +    def setUpClass(cls):
    +        requires('gui')
    +        cls.root = tk.Tk()
    +        cls.root.withdraw()
    +
    +    def setUp(self):
    +        self.text = text = TextWrapper(self.root)
    +        text.insert('1.0', ">>> ")
    +        text.mark_set('iomark', '1.4')
    +        text.mark_gravity('iomark', 'left')
    +        self.history = History(text)
    +        self.history.history = [line1, line2]
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        cls.root.destroy()
    +        del cls.root
    +
    +    def fetch_test(self, reverse, line, prefix, index, *, bell=False):
    +        # Perform one fetch as invoked by Alt-N or Alt-P
    +        # Test the result. The line test is the most important.
    +        # The last two are diagnostic of fetch internals.
    +        History = self.history
    +        History.fetch(reverse)
    +
    +        Equal = self.assertEqual
    +        Equal(self.text.get('iomark', 'end-1c'), line)
    +        Equal(self.text._bell, bell)
    +        if bell:
    +            self.text._bell = False
    +        Equal(History.prefix, prefix)
    +        Equal(History.pointer, index)
    +        Equal(self.text.compare("insert", '==', "end-1c"), 1)
    +
    +    def test_fetch_prev_cyclic(self):
    +        prefix = ''
    +        test = self.fetch_test
    +        test(True, line2, prefix, 1)
    +        test(True, line1, prefix, 0)
    +        test(True, prefix, None, None, bell=True)
    +
    +    def test_fetch_next_cyclic(self):
    +        prefix = ''
    +        test  = self.fetch_test
    +        test(False, line1, prefix, 0)
    +        test(False, line2, prefix, 1)
    +        test(False, prefix, None, None, bell=True)
    +
    +    # Prefix 'a' tests skip line2, which starts with 'b'
    +    def test_fetch_prev_prefix(self):
    +        prefix = 'a'
    +        self.text.insert('iomark', prefix)
    +        self.fetch_test(True, line1, prefix, 0)
    +        self.fetch_test(True, prefix, None, None, bell=True)
    +
    +    def test_fetch_next_prefix(self):
    +        prefix = 'a'
    +        self.text.insert('iomark', prefix)
    +        self.fetch_test(False, line1, prefix, 0)
    +        self.fetch_test(False, prefix, None, None, bell=True)
    +
    +    def test_fetch_prev_noncyclic(self):
    +        prefix = ''
    +        self.history.cyclic = False
    +        test = self.fetch_test
    +        test(True, line2, prefix, 1)
    +        test(True, line1, prefix, 0)
    +        test(True, line1, prefix, 0, bell=True)
    +
    +    def test_fetch_next_noncyclic(self):
    +        prefix = ''
    +        self.history.cyclic = False
    +        test  = self.fetch_test
    +        test(False, prefix, None, None, bell=True)
    +        test(True, line2, prefix, 1)
    +        test(False, prefix, None, None, bell=True)
    +        test(False, prefix, None, None, bell=True)
    +
    +    def test_fetch_cursor_move(self):
    +        # Move cursor after fetch
    +        self.history.fetch(reverse=True)  # initialization
    +        self.text.mark_set('insert', 'iomark')
    +        self.fetch_test(True, line2, None, None, bell=True)
    +
    +    def test_fetch_edit(self):
    +        # Edit after fetch
    +        self.history.fetch(reverse=True)  # initialization
    +        self.text.delete('iomark', 'insert', )
    +        self.text.insert('iomark', 'a =')
    +        self.fetch_test(True, line1, 'a =', 0)  # prefix is reset
    +
    +    def test_history_prev_next(self):
    +        # Minimally test functions bound to events
    +        self.history.history_prev('dummy event')
    +        self.assertEqual(self.history.pointer, 1)
    +        self.history.history_next('dummy event')
    +        self.assertEqual(self.history.pointer, None)
    +
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2, exit=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_hyperparser.py b/PythonLib/full/idlelib/idle_test/test_hyperparser.py
    index 0a1809d2..343843c4 100644
    --- a/PythonLib/full/idlelib/idle_test/test_hyperparser.py
    +++ b/PythonLib/full/idlelib/idle_test/test_hyperparser.py
    @@ -1,20 +1,21 @@
    -"""Unittest for idlelib.HyperParser"""
    +"Test hyperparser, coverage 98%."
    +
    +from idlelib.hyperparser import HyperParser
     import unittest
    -from test.test_support import requires
    -from Tkinter import Tk, Text
    -from idlelib.EditorWindow import EditorWindow
    -from idlelib.HyperParser import HyperParser
    +from test.support import requires
    +from tkinter import Tk, Text
    +from idlelib.editor import EditorWindow
     
     class DummyEditwin:
         def __init__(self, text):
             self.text = text
             self.indentwidth = 8
             self.tabwidth = 8
    -        self.context_use_ps1 = True
    +        self.prompt_last_line = '>>>'
             self.num_context_lines = 50, 500, 1000
     
    -    _build_char_in_string_func = EditorWindow._build_char_in_string_func.im_func
    -    is_char_in_string = EditorWindow.is_char_in_string.im_func
    +    _build_char_in_string_func = EditorWindow._build_char_in_string_func
    +    is_char_in_string = EditorWindow.is_char_in_string
     
     
     class HyperParserTest(unittest.TestCase):
    @@ -30,6 +31,7 @@ class HyperParserTest(unittest.TestCase):
                 "z = ((r'asdf')+('a')))\n"
                 '[x for x in\n'
                 'for = False\n'
    +            'cliché = "this is a string with unicode, what a cliché"'
                 )
     
         @classmethod
    @@ -51,7 +53,7 @@ def setUp(self):
     
         def tearDown(self):
             self.text.delete('1.0', 'end')
    -        self.editwin.context_use_ps1 = True
    +        self.editwin.prompt_last_line = '>>>'
     
         def get_parser(self, index):
             """
    @@ -69,7 +71,7 @@ def test_init(self):
             self.assertIn('precedes', str(ve.exception))
     
             # test without ps1
    -        self.editwin.context_use_ps1 = False
    +        self.editwin.prompt_last_line = ''
     
             # number of lines lesser than 50
             p = self.get_parser('end')
    @@ -94,6 +96,8 @@ def test_is_in_string(self):
             self.assertTrue(p.is_in_string())
             p = get('4.6')
             self.assertTrue(p.is_in_string())
    +        p = get('12.54')
    +        self.assertTrue(p.is_in_string())
     
         def test_is_in_code(self):
             get = self.get_parser
    @@ -181,12 +185,92 @@ def test_get_expression(self):
             p = get('10.0')
             self.assertEqual(p.get_expression(), '')
     
    +        p = get('10.6')
    +        self.assertEqual(p.get_expression(), '')
    +
    +        p = get('10.11')
    +        self.assertEqual(p.get_expression(), '')
    +
             p = get('11.3')
             self.assertEqual(p.get_expression(), '')
     
             p = get('11.11')
             self.assertEqual(p.get_expression(), 'False')
     
    +        p = get('12.6')
    +        self.assertEqual(p.get_expression(), 'cliché')
    +
    +    def test_eat_identifier(self):
    +        def is_valid_id(candidate):
    +            result = HyperParser._eat_identifier(candidate, 0, len(candidate))
    +            if result == len(candidate):
    +                return True
    +            elif result == 0:
    +                return False
    +            else:
    +                err_msg = "Unexpected result: {} (expected 0 or {}".format(
    +                    result, len(candidate)
    +                )
    +                raise Exception(err_msg)
    +
    +        # invalid first character which is valid elsewhere in an identifier
    +        self.assertFalse(is_valid_id('2notid'))
    +
    +        # ASCII-only valid identifiers
    +        self.assertTrue(is_valid_id('valid_id'))
    +        self.assertTrue(is_valid_id('_valid_id'))
    +        self.assertTrue(is_valid_id('valid_id_'))
    +        self.assertTrue(is_valid_id('_2valid_id'))
    +
    +        # keywords which should be "eaten"
    +        self.assertTrue(is_valid_id('True'))
    +        self.assertTrue(is_valid_id('False'))
    +        self.assertTrue(is_valid_id('None'))
    +
    +        # keywords which should not be "eaten"
    +        self.assertFalse(is_valid_id('for'))
    +        self.assertFalse(is_valid_id('import'))
    +        self.assertFalse(is_valid_id('return'))
    +
    +        # valid unicode identifiers
    +        self.assertTrue(is_valid_id('cliche'))
    +        self.assertTrue(is_valid_id('cliché'))
    +        self.assertTrue(is_valid_id('a٢'))
    +
    +        # invalid unicode identifiers
    +        self.assertFalse(is_valid_id('2a'))
    +        self.assertFalse(is_valid_id('٢a'))
    +        self.assertFalse(is_valid_id('a²'))
    +
    +        # valid identifier after "punctuation"
    +        self.assertEqual(HyperParser._eat_identifier('+ var', 0, 5), len('var'))
    +        self.assertEqual(HyperParser._eat_identifier('+var', 0, 4), len('var'))
    +        self.assertEqual(HyperParser._eat_identifier('.var', 0, 4), len('var'))
    +
    +        # invalid identifiers
    +        self.assertFalse(is_valid_id('+'))
    +        self.assertFalse(is_valid_id(' '))
    +        self.assertFalse(is_valid_id(':'))
    +        self.assertFalse(is_valid_id('?'))
    +        self.assertFalse(is_valid_id('^'))
    +        self.assertFalse(is_valid_id('\\'))
    +        self.assertFalse(is_valid_id('"'))
    +        self.assertFalse(is_valid_id('"a string"'))
    +
    +    def test_eat_identifier_various_lengths(self):
    +        eat_id = HyperParser._eat_identifier
    +
    +        for length in range(1, 21):
    +            self.assertEqual(eat_id('a' * length, 0, length), length)
    +            self.assertEqual(eat_id('é' * length, 0, length), length)
    +            self.assertEqual(eat_id('a' + '2' * (length - 1), 0, length), length)
    +            self.assertEqual(eat_id('é' + '2' * (length - 1), 0, length), length)
    +            self.assertEqual(eat_id('é' + 'a' * (length - 1), 0, length), length)
    +            self.assertEqual(eat_id('é' * (length - 1) + 'a', 0, length), length)
    +            self.assertEqual(eat_id('+' * length, 0, length), 0)
    +            self.assertEqual(eat_id('2' + 'a' * (length - 1), 0, length), 0)
    +            self.assertEqual(eat_id('2' + 'é' * (length - 1), 0, length), 0)
    +
     
     if __name__ == '__main__':
         unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_idlehistory.py b/PythonLib/full/idlelib/idle_test/test_idlehistory.py
    deleted file mode 100644
    index b0767570..00000000
    --- a/PythonLib/full/idlelib/idle_test/test_idlehistory.py
    +++ /dev/null
    @@ -1,168 +0,0 @@
    -import unittest
    -from test.test_support import requires
    -
    -import Tkinter as tk
    -from Tkinter import Text as tkText
    -from idlelib.idle_test.mock_tk import Text as mkText
    -from idlelib.IdleHistory import History
    -from idlelib.configHandler import idleConf
    -
    -line1 = 'a = 7'
    -line2 = 'b = a'
    -
    -class StoreTest(unittest.TestCase):
    -    '''Tests History.__init__ and History.store with mock Text'''
    -
    -    @classmethod
    -    def setUpClass(cls):
    -        cls.text = mkText()
    -        cls.history = History(cls.text)
    -
    -    def tearDown(self):
    -        self.text.delete('1.0', 'end')
    -        self.history.history = []
    -
    -    def test_init(self):
    -        self.assertIs(self.history.text, self.text)
    -        self.assertEqual(self.history.history, [])
    -        self.assertIsNone(self.history.prefix)
    -        self.assertIsNone(self.history.pointer)
    -        self.assertEqual(self.history.cyclic,
    -                idleConf.GetOption("main", "History",  "cyclic", 1, "bool"))
    -
    -    def test_store_short(self):
    -        self.history.store('a')
    -        self.assertEqual(self.history.history, [])
    -        self.history.store('  a  ')
    -        self.assertEqual(self.history.history, [])
    -
    -    def test_store_dup(self):
    -        self.history.store(line1)
    -        self.assertEqual(self.history.history, [line1])
    -        self.history.store(line2)
    -        self.assertEqual(self.history.history, [line1, line2])
    -        self.history.store(line1)
    -        self.assertEqual(self.history.history, [line2, line1])
    -
    -    def test_store_reset(self):
    -        self.history.prefix = line1
    -        self.history.pointer = 0
    -        self.history.store(line2)
    -        self.assertIsNone(self.history.prefix)
    -        self.assertIsNone(self.history.pointer)
    -
    -
    -class TextWrapper:
    -    def __init__(self, master):
    -        self.text = tkText(master=master)
    -        self._bell = False
    -    def __getattr__(self, name):
    -        return getattr(self.text, name)
    -    def bell(self):
    -        self._bell = True
    -
    -class FetchTest(unittest.TestCase):
    -    '''Test History.fetch with wrapped tk.Text.
    -    '''
    -    @classmethod
    -    def setUpClass(cls):
    -        requires('gui')
    -        cls.root = tk.Tk()
    -        cls.root.withdraw()
    -
    -    def setUp(self):
    -        self.text = text = TextWrapper(self.root)
    -        text.insert('1.0', ">>> ")
    -        text.mark_set('iomark', '1.4')
    -        text.mark_gravity('iomark', 'left')
    -        self.history = History(text)
    -        self.history.history = [line1, line2]
    -
    -    @classmethod
    -    def tearDownClass(cls):
    -        cls.root.destroy()
    -        del cls.root
    -
    -    def fetch_test(self, reverse, line, prefix, index, bell=False):
    -        # Perform one fetch as invoked by Alt-N or Alt-P
    -        # Test the result. The line test is the most important.
    -        # The last two are diagnostic of fetch internals.
    -        History = self.history
    -        History.fetch(reverse)
    -
    -        Equal = self.assertEqual
    -        Equal(self.text.get('iomark', 'end-1c'), line)
    -        Equal(self.text._bell, bell)
    -        if bell:
    -            self.text._bell = False
    -        Equal(History.prefix, prefix)
    -        Equal(History.pointer, index)
    -        Equal(self.text.compare("insert", '==', "end-1c"), 1)
    -
    -    def test_fetch_prev_cyclic(self):
    -        prefix = ''
    -        test = self.fetch_test
    -        test(True, line2, prefix, 1)
    -        test(True, line1, prefix, 0)
    -        test(True, prefix, None, None, bell=True)
    -
    -    def test_fetch_next_cyclic(self):
    -        prefix = ''
    -        test  = self.fetch_test
    -        test(False, line1, prefix, 0)
    -        test(False, line2, prefix, 1)
    -        test(False, prefix, None, None, bell=True)
    -
    -    # Prefix 'a' tests skip line2, which starts with 'b'
    -    def test_fetch_prev_prefix(self):
    -        prefix = 'a'
    -        self.text.insert('iomark', prefix)
    -        self.fetch_test(True, line1, prefix, 0)
    -        self.fetch_test(True, prefix, None, None, bell=True)
    -
    -    def test_fetch_next_prefix(self):
    -        prefix = 'a'
    -        self.text.insert('iomark', prefix)
    -        self.fetch_test(False, line1, prefix, 0)
    -        self.fetch_test(False, prefix, None, None, bell=True)
    -
    -    def test_fetch_prev_noncyclic(self):
    -        prefix = ''
    -        self.history.cyclic = False
    -        test = self.fetch_test
    -        test(True, line2, prefix, 1)
    -        test(True, line1, prefix, 0)
    -        test(True, line1, prefix, 0, bell=True)
    -
    -    def test_fetch_next_noncyclic(self):
    -        prefix = ''
    -        self.history.cyclic = False
    -        test  = self.fetch_test
    -        test(False, prefix, None, None, bell=True)
    -        test(True, line2, prefix, 1)
    -        test(False, prefix, None, None, bell=True)
    -        test(False, prefix, None, None, bell=True)
    -
    -    def test_fetch_cursor_move(self):
    -        # Move cursor after fetch
    -        self.history.fetch(reverse=True)  # initialization
    -        self.text.mark_set('insert', 'iomark')
    -        self.fetch_test(True, line2, None, None, bell=True)
    -
    -    def test_fetch_edit(self):
    -        # Edit after fetch
    -        self.history.fetch(reverse=True)  # initialization
    -        self.text.delete('iomark', 'insert', )
    -        self.text.insert('iomark', 'a =')
    -        self.fetch_test(True, line1, 'a =', 0)  # prefix is reset
    -
    -    def test_history_prev_next(self):
    -        # Minimally test functions bound to events
    -        self.history.history_prev('dummy event')
    -        self.assertEqual(self.history.pointer, 1)
    -        self.history.history_next('dummy event')
    -        self.assertEqual(self.history.pointer, None)
    -
    -
    -if __name__ == '__main__':
    -    unittest.main(verbosity=2, exit=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_io.py b/PythonLib/full/idlelib/idle_test/test_io.py
    deleted file mode 100644
    index ee017bb8..00000000
    --- a/PythonLib/full/idlelib/idle_test/test_io.py
    +++ /dev/null
    @@ -1,267 +0,0 @@
    -import unittest
    -import io
    -from idlelib.PyShell import PseudoInputFile, PseudoOutputFile
    -from test import test_support as support
    -
    -
    -class Base(object):
    -    def __str__(self):
    -        return '%s:str' % type(self).__name__
    -    def __unicode__(self):
    -        return '%s:unicode' % type(self).__name__
    -    def __len__(self):
    -        return 3
    -    def __iter__(self):
    -        return iter('abc')
    -    def __getitem__(self, *args):
    -        return '%s:item' % type(self).__name__
    -    def __getslice__(self, *args):
    -        return '%s:slice' % type(self).__name__
    -
    -class S(Base, str):
    -    pass
    -
    -class U(Base, unicode):
    -    pass
    -
    -class BA(Base, bytearray):
    -    pass
    -
    -class MockShell:
    -    def __init__(self):
    -        self.reset()
    -
    -    def write(self, *args):
    -        self.written.append(args)
    -
    -    def readline(self):
    -        return self.lines.pop()
    -
    -    def close(self):
    -        pass
    -
    -    def reset(self):
    -        self.written = []
    -
    -    def push(self, lines):
    -        self.lines = list(lines)[::-1]
    -
    -
    -class PseudeOutputFilesTest(unittest.TestCase):
    -    def test_misc(self):
    -        shell = MockShell()
    -        f = PseudoOutputFile(shell, 'stdout', 'utf-8')
    -        self.assertIsInstance(f, io.TextIOBase)
    -        self.assertEqual(f.encoding, 'utf-8')
    -        self.assertIsNone(f.errors)
    -        self.assertIsNone(f.newlines)
    -        self.assertEqual(f.name, '')
    -        self.assertFalse(f.closed)
    -        self.assertTrue(f.isatty())
    -        self.assertFalse(f.readable())
    -        self.assertTrue(f.writable())
    -        self.assertFalse(f.seekable())
    -
    -    def test_unsupported(self):
    -        shell = MockShell()
    -        f = PseudoOutputFile(shell, 'stdout', 'utf-8')
    -        self.assertRaises(IOError, f.fileno)
    -        self.assertRaises(IOError, f.tell)
    -        self.assertRaises(IOError, f.seek, 0)
    -        self.assertRaises(IOError, f.read, 0)
    -        self.assertRaises(IOError, f.readline, 0)
    -
    -    def test_write(self):
    -        shell = MockShell()
    -        f = PseudoOutputFile(shell, 'stdout', 'utf-8')
    -        f.write('test')
    -        self.assertEqual(shell.written, [('test', 'stdout')])
    -        shell.reset()
    -        f.write('t\xe8st')
    -        self.assertEqual(shell.written, [('t\xe8st', 'stdout')])
    -        shell.reset()
    -        f.write(u't\xe8st')
    -        self.assertEqual(shell.written, [(u't\xe8st', 'stdout')])
    -        shell.reset()
    -
    -        f.write(S('t\xe8st'))
    -        self.assertEqual(shell.written, [('t\xe8st', 'stdout')])
    -        self.assertEqual(type(shell.written[0][0]), str)
    -        shell.reset()
    -        f.write(BA('t\xe8st'))
    -        self.assertEqual(shell.written, [('t\xe8st', 'stdout')])
    -        self.assertEqual(type(shell.written[0][0]), str)
    -        shell.reset()
    -        f.write(U(u't\xe8st'))
    -        self.assertEqual(shell.written, [(u't\xe8st', 'stdout')])
    -        self.assertEqual(type(shell.written[0][0]), unicode)
    -        shell.reset()
    -
    -        self.assertRaises(TypeError, f.write)
    -        self.assertEqual(shell.written, [])
    -        self.assertRaises(TypeError, f.write, 123)
    -        self.assertEqual(shell.written, [])
    -        self.assertRaises(TypeError, f.write, 'test', 'spam')
    -        self.assertEqual(shell.written, [])
    -
    -    def test_writelines(self):
    -        shell = MockShell()
    -        f = PseudoOutputFile(shell, 'stdout', 'utf-8')
    -        f.writelines([])
    -        self.assertEqual(shell.written, [])
    -        shell.reset()
    -        f.writelines(['one\n', 'two'])
    -        self.assertEqual(shell.written,
    -                         [('one\n', 'stdout'), ('two', 'stdout')])
    -        shell.reset()
    -        f.writelines(['on\xe8\n', 'tw\xf2'])
    -        self.assertEqual(shell.written,
    -                         [('on\xe8\n', 'stdout'), ('tw\xf2', 'stdout')])
    -        shell.reset()
    -        f.writelines([u'on\xe8\n', u'tw\xf2'])
    -        self.assertEqual(shell.written,
    -                         [(u'on\xe8\n', 'stdout'), (u'tw\xf2', 'stdout')])
    -        shell.reset()
    -
    -        f.writelines([S('t\xe8st')])
    -        self.assertEqual(shell.written, [('t\xe8st', 'stdout')])
    -        self.assertEqual(type(shell.written[0][0]), str)
    -        shell.reset()
    -        f.writelines([BA('t\xe8st')])
    -        self.assertEqual(shell.written, [('t\xe8st', 'stdout')])
    -        self.assertEqual(type(shell.written[0][0]), str)
    -        shell.reset()
    -        f.writelines([U(u't\xe8st')])
    -        self.assertEqual(shell.written, [(u't\xe8st', 'stdout')])
    -        self.assertEqual(type(shell.written[0][0]), unicode)
    -        shell.reset()
    -
    -        self.assertRaises(TypeError, f.writelines)
    -        self.assertEqual(shell.written, [])
    -        self.assertRaises(TypeError, f.writelines, 123)
    -        self.assertEqual(shell.written, [])
    -        self.assertRaises(TypeError, f.writelines, [123])
    -        self.assertEqual(shell.written, [])
    -        self.assertRaises(TypeError, f.writelines, [], [])
    -        self.assertEqual(shell.written, [])
    -
    -    def test_close(self):
    -        shell = MockShell()
    -        f = PseudoOutputFile(shell, 'stdout', 'utf-8')
    -        self.assertFalse(f.closed)
    -        f.write('test')
    -        f.close()
    -        self.assertTrue(f.closed)
    -        self.assertRaises(ValueError, f.write, 'x')
    -        self.assertEqual(shell.written, [('test', 'stdout')])
    -        f.close()
    -        self.assertRaises(TypeError, f.close, 1)
    -
    -
    -class PseudeInputFilesTest(unittest.TestCase):
    -    def test_misc(self):
    -        shell = MockShell()
    -        f = PseudoInputFile(shell, 'stdin', 'utf-8')
    -        self.assertIsInstance(f, io.TextIOBase)
    -        self.assertEqual(f.encoding, 'utf-8')
    -        self.assertIsNone(f.errors)
    -        self.assertIsNone(f.newlines)
    -        self.assertEqual(f.name, '')
    -        self.assertFalse(f.closed)
    -        self.assertTrue(f.isatty())
    -        self.assertTrue(f.readable())
    -        self.assertFalse(f.writable())
    -        self.assertFalse(f.seekable())
    -
    -    def test_unsupported(self):
    -        shell = MockShell()
    -        f = PseudoInputFile(shell, 'stdin', 'utf-8')
    -        self.assertRaises(IOError, f.fileno)
    -        self.assertRaises(IOError, f.tell)
    -        self.assertRaises(IOError, f.seek, 0)
    -        self.assertRaises(IOError, f.write, 'x')
    -        self.assertRaises(IOError, f.writelines, ['x'])
    -
    -    def test_read(self):
    -        shell = MockShell()
    -        f = PseudoInputFile(shell, 'stdin', 'utf-8')
    -        shell.push(['one\n', 'two\n', ''])
    -        self.assertEqual(f.read(), 'one\ntwo\n')
    -        shell.push(['one\n', 'two\n', ''])
    -        self.assertEqual(f.read(-1), 'one\ntwo\n')
    -        shell.push(['one\n', 'two\n', ''])
    -        self.assertEqual(f.read(None), 'one\ntwo\n')
    -        shell.push(['one\n', 'two\n', 'three\n', ''])
    -        self.assertEqual(f.read(2), 'on')
    -        self.assertEqual(f.read(3), 'e\nt')
    -        self.assertEqual(f.read(10), 'wo\nthree\n')
    -
    -        shell.push(['one\n', 'two\n'])
    -        self.assertEqual(f.read(0), '')
    -        self.assertRaises(TypeError, f.read, 1.5)
    -        self.assertRaises(TypeError, f.read, '1')
    -        self.assertRaises(TypeError, f.read, 1, 1)
    -
    -    def test_readline(self):
    -        shell = MockShell()
    -        f = PseudoInputFile(shell, 'stdin', 'utf-8')
    -        shell.push(['one\n', 'two\n', 'three\n', 'four\n'])
    -        self.assertEqual(f.readline(), 'one\n')
    -        self.assertEqual(f.readline(-1), 'two\n')
    -        self.assertEqual(f.readline(None), 'three\n')
    -        shell.push(['one\ntwo\n'])
    -        self.assertEqual(f.readline(), 'one\n')
    -        self.assertEqual(f.readline(), 'two\n')
    -        shell.push(['one', 'two', 'three'])
    -        self.assertEqual(f.readline(), 'one')
    -        self.assertEqual(f.readline(), 'two')
    -        shell.push(['one\n', 'two\n', 'three\n'])
    -        self.assertEqual(f.readline(2), 'on')
    -        self.assertEqual(f.readline(1), 'e')
    -        self.assertEqual(f.readline(1), '\n')
    -        self.assertEqual(f.readline(10), 'two\n')
    -
    -        shell.push(['one\n', 'two\n'])
    -        self.assertEqual(f.readline(0), '')
    -        self.assertRaises(TypeError, f.readlines, 1.5)
    -        self.assertRaises(TypeError, f.readlines, '1')
    -        self.assertRaises(TypeError, f.readlines, 1, 1)
    -
    -    def test_readlines(self):
    -        shell = MockShell()
    -        f = PseudoInputFile(shell, 'stdin', 'utf-8')
    -        shell.push(['one\n', 'two\n', ''])
    -        self.assertEqual(f.readlines(), ['one\n', 'two\n'])
    -        shell.push(['one\n', 'two\n', ''])
    -        self.assertEqual(f.readlines(-1), ['one\n', 'two\n'])
    -        shell.push(['one\n', 'two\n', ''])
    -        self.assertEqual(f.readlines(None), ['one\n', 'two\n'])
    -        shell.push(['one\n', 'two\n', ''])
    -        self.assertEqual(f.readlines(0), ['one\n', 'two\n'])
    -        shell.push(['one\n', 'two\n', ''])
    -        self.assertEqual(f.readlines(3), ['one\n'])
    -        shell.push(['one\n', 'two\n', ''])
    -        self.assertEqual(f.readlines(4), ['one\n', 'two\n'])
    -
    -        shell.push(['one\n', 'two\n', ''])
    -        self.assertRaises(TypeError, f.readlines, 1.5)
    -        self.assertRaises(TypeError, f.readlines, '1')
    -        self.assertRaises(TypeError, f.readlines, 1, 1)
    -
    -    def test_close(self):
    -        shell = MockShell()
    -        f = PseudoInputFile(shell, 'stdin', 'utf-8')
    -        shell.push(['one\n', 'two\n', ''])
    -        self.assertFalse(f.closed)
    -        self.assertEqual(f.readline(), 'one\n')
    -        f.close()
    -        self.assertFalse(f.closed)
    -        self.assertEqual(f.readline(), 'two\n')
    -        self.assertRaises(TypeError, f.close, 1)
    -
    -
    -def test_main():
    -    support.run_unittest(PseudeOutputFilesTest, PseudeInputFilesTest)
    -
    -if __name__ == '__main__':
    -    test_main()
    diff --git a/PythonLib/full/idlelib/idle_test/test_iomenu.py b/PythonLib/full/idlelib/idle_test/test_iomenu.py
    new file mode 100644
    index 00000000..99f40487
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_iomenu.py
    @@ -0,0 +1,49 @@
    +"Test , coverage 17%."
    +
    +from idlelib import iomenu
    +import unittest
    +from test.support import requires
    +from tkinter import Tk
    +from idlelib.editor import EditorWindow
    +
    +
    +class IOBindingTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        requires('gui')
    +        cls.root = Tk()
    +        cls.root.withdraw()
    +        cls.editwin = EditorWindow(root=cls.root)
    +        cls.io = iomenu.IOBinding(cls.editwin)
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        cls.io.close()
    +        cls.editwin._close()
    +        del cls.editwin
    +        cls.root.update_idletasks()
    +        for id in cls.root.tk.call('after', 'info'):
    +            cls.root.after_cancel(id)  # Need for EditorWindow.
    +        cls.root.destroy()
    +        del cls.root
    +
    +    def test_init(self):
    +        self.assertIs(self.io.editwin, self.editwin)
    +
    +    def test_fixnewlines_end(self):
    +        eq = self.assertEqual
    +        io = self.io
    +        fix = io.fixnewlines
    +        text = io.editwin.text
    +        self.editwin.interp = None
    +        eq(fix(), '')
    +        del self.editwin.interp
    +        text.insert(1.0, 'a')
    +        eq(fix(), 'a'+io.eol_convention)
    +        eq(text.get('1.0', 'end-1c'), 'a\n')
    +        eq(fix(), 'a'+io.eol_convention)
    +
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_macosx.py b/PythonLib/full/idlelib/idle_test/test_macosx.py
    new file mode 100644
    index 00000000..b6bd922e
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_macosx.py
    @@ -0,0 +1,104 @@
    +"Test macosx, coverage 45% on Windows."
    +
    +from idlelib import macosx
    +import unittest
    +from test.support import requires
    +import tkinter as tk
    +import unittest.mock as mock
    +from idlelib.filelist import FileList
    +
    +mactypes = {'carbon', 'cocoa', 'xquartz'}
    +nontypes = {'other'}
    +alltypes = mactypes | nontypes
    +
    +
    +class InitTktypeTest(unittest.TestCase):
    +    "Test _init_tk_type."
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        requires('gui')
    +        cls.root = tk.Tk()
    +        cls.root.withdraw()
    +        cls.orig_platform = macosx.platform
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        cls.root.update_idletasks()
    +        cls.root.destroy()
    +        del cls.root
    +        macosx.platform = cls.orig_platform
    +
    +    def test_init_sets_tktype(self):
    +        "Test that _init_tk_type sets _tk_type according to platform."
    +        for platform, types in ('darwin', alltypes), ('other', nontypes):
    +            with self.subTest(platform=platform):
    +                macosx.platform = platform
    +                macosx._tk_type == None
    +                macosx._init_tk_type()
    +                self.assertIn(macosx._tk_type, types)
    +
    +
    +class IsTypeTkTest(unittest.TestCase):
    +    "Test each of the four isTypeTk predecates."
    +    isfuncs = ((macosx.isAquaTk, ('carbon', 'cocoa')),
    +               (macosx.isCarbonTk, ('carbon')),
    +               (macosx.isCocoaTk, ('cocoa')),
    +               (macosx.isXQuartz, ('xquartz')),
    +               )
    +
    +    @mock.patch('idlelib.macosx._init_tk_type')
    +    def test_is_calls_init(self, mockinit):
    +        "Test that each isTypeTk calls _init_tk_type when _tk_type is None."
    +        macosx._tk_type = None
    +        for func, whentrue in self.isfuncs:
    +            with self.subTest(func=func):
    +                func()
    +                self.assertTrue(mockinit.called)
    +                mockinit.reset_mock()
    +
    +    def test_isfuncs(self):
    +        "Test that each isTypeTk return correct bool."
    +        for func, whentrue in self.isfuncs:
    +            for tktype in alltypes:
    +                with self.subTest(func=func, whentrue=whentrue, tktype=tktype):
    +                    macosx._tk_type = tktype
    +                    (self.assertTrue if tktype in whentrue else self.assertFalse)\
    +                                     (func())
    +
    +
    +class SetupTest(unittest.TestCase):
    +    "Test setupApp."
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        requires('gui')
    +        cls.root = tk.Tk()
    +        cls.root.withdraw()
    +        def cmd(tkpath, func):
    +            assert isinstance(tkpath, str)
    +            assert isinstance(func, type(cmd))
    +        cls.root.createcommand = cmd
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        cls.root.update_idletasks()
    +        cls.root.destroy()
    +        del cls.root
    +
    +    @mock.patch('idlelib.macosx.overrideRootMenu')  #27312
    +    def test_setupapp(self, overrideRootMenu):
    +        "Call setupApp with each possible graphics type."
    +        root = self.root
    +        flist = FileList(root)
    +        for tktype in alltypes:
    +            with self.subTest(tktype=tktype):
    +                macosx._tk_type = tktype
    +                macosx.setupApp(root, flist)
    +                if tktype in ('carbon', 'cocoa'):
    +                    self.assertTrue(overrideRootMenu.called)
    +                overrideRootMenu.reset_mock()
    +
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_mainmenu.py b/PythonLib/full/idlelib/idle_test/test_mainmenu.py
    new file mode 100644
    index 00000000..7ec03683
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_mainmenu.py
    @@ -0,0 +1,21 @@
    +"Test mainmenu, coverage 100%."
    +# Reported as 88%; mocking turtledemo absence would have no point.
    +
    +from idlelib import mainmenu
    +import unittest
    +
    +
    +class MainMenuTest(unittest.TestCase):
    +
    +    def test_menudefs(self):
    +        actual = [item[0] for item in mainmenu.menudefs]
    +        expect = ['file', 'edit', 'format', 'run', 'shell',
    +                  'debug', 'options', 'window', 'help']
    +        self.assertEqual(actual, expect)
    +
    +    def test_default_keydefs(self):
    +        self.assertGreaterEqual(len(mainmenu.default_keydefs), 50)
    +
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_multicall.py b/PythonLib/full/idlelib/idle_test/test_multicall.py
    new file mode 100644
    index 00000000..ba582bb3
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_multicall.py
    @@ -0,0 +1,48 @@
    +"Test multicall, coverage 33%."
    +
    +from idlelib import multicall
    +import unittest
    +from test.support import requires
    +from tkinter import Tk, Text
    +
    +
    +class MultiCallTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        requires('gui')
    +        cls.root = Tk()
    +        cls.root.withdraw()
    +        cls.mc = multicall.MultiCallCreator(Text)
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        del cls.mc
    +        cls.root.update_idletasks()
    +##        for id in cls.root.tk.call('after', 'info'):
    +##            cls.root.after_cancel(id)  # Need for EditorWindow.
    +        cls.root.destroy()
    +        del cls.root
    +
    +    def test_creator(self):
    +        mc = self.mc
    +        self.assertIs(multicall._multicall_dict[Text], mc)
    +        self.assertTrue(issubclass(mc, Text))
    +        mc2 = multicall.MultiCallCreator(Text)
    +        self.assertIs(mc, mc2)
    +
    +    def test_init(self):
    +        mctext = self.mc(self.root)
    +        self.assertIsInstance(mctext._MultiCall__binders, list)
    +
    +    def test_yview(self):
    +        # Added for tree.wheel_event
    +        # (it depends on yview to not be overriden)
    +        mc = self.mc
    +        self.assertIs(mc.yview, Text.yview)
    +        mctext = self.mc(self.root)
    +        self.assertIs(mctext.yview.__func__, Text.yview)
    +
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_outwin.py b/PythonLib/full/idlelib/idle_test/test_outwin.py
    new file mode 100644
    index 00000000..cd099ecd
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_outwin.py
    @@ -0,0 +1,171 @@
    +"Test outwin, coverage 76%."
    +
    +from idlelib import outwin
    +import unittest
    +from test.support import requires
    +from tkinter import Tk, Text
    +from idlelib.idle_test.mock_tk import Mbox_func
    +from idlelib.idle_test.mock_idle import Func
    +from unittest import mock
    +
    +
    +class OutputWindowTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        requires('gui')
    +        root = cls.root = Tk()
    +        root.withdraw()
    +        w = cls.window = outwin.OutputWindow(None, None, None, root)
    +        cls.text = w.text = Text(root)
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        cls.window.close()
    +        del cls.text, cls.window
    +        cls.root.destroy()
    +        del cls.root
    +
    +    def setUp(self):
    +        self.text.delete('1.0', 'end')
    +
    +    def test_ispythonsource(self):
    +        # OutputWindow overrides ispythonsource to always return False.
    +        w = self.window
    +        self.assertFalse(w.ispythonsource('test.txt'))
    +        self.assertFalse(w.ispythonsource(__file__))
    +
    +    def test_window_title(self):
    +        self.assertEqual(self.window.top.title(), 'Output')
    +
    +    def test_maybesave(self):
    +        w = self.window
    +        eq = self.assertEqual
    +        w.get_saved = Func()
    +
    +        w.get_saved.result = False
    +        eq(w.maybesave(), 'no')
    +        eq(w.get_saved.called, 1)
    +
    +        w.get_saved.result = True
    +        eq(w.maybesave(), 'yes')
    +        eq(w.get_saved.called, 2)
    +        del w.get_saved
    +
    +    def test_write(self):
    +        eq = self.assertEqual
    +        delete = self.text.delete
    +        get = self.text.get
    +        write = self.window.write
    +
    +        # Test bytes.
    +        b = b'Test bytes.'
    +        eq(write(b), len(b))
    +        eq(get('1.0', '1.end'), b.decode())
    +
    +        # No new line - insert stays on same line.
    +        delete('1.0', 'end')
    +        test_text = 'test text'
    +        eq(write(test_text), len(test_text))
    +        eq(get('1.0', '1.end'), 'test text')
    +        eq(get('insert linestart', 'insert lineend'), 'test text')
    +
    +        # New line - insert moves to next line.
    +        delete('1.0', 'end')
    +        test_text = 'test text\n'
    +        eq(write(test_text), len(test_text))
    +        eq(get('1.0', '1.end'), 'test text')
    +        eq(get('insert linestart', 'insert lineend'), '')
    +
    +        # Text after new line is tagged for second line of Text widget.
    +        delete('1.0', 'end')
    +        test_text = 'test text\nLine 2'
    +        eq(write(test_text), len(test_text))
    +        eq(get('1.0', '1.end'), 'test text')
    +        eq(get('2.0', '2.end'), 'Line 2')
    +        eq(get('insert linestart', 'insert lineend'), 'Line 2')
    +
    +        # Test tags.
    +        delete('1.0', 'end')
    +        test_text = 'test text\n'
    +        test_text2 = 'Line 2\n'
    +        eq(write(test_text, tags='mytag'), len(test_text))
    +        eq(write(test_text2, tags='secondtag'), len(test_text2))
    +        eq(get('mytag.first', 'mytag.last'), test_text)
    +        eq(get('secondtag.first', 'secondtag.last'), test_text2)
    +        eq(get('1.0', '1.end'), test_text.rstrip('\n'))
    +        eq(get('2.0', '2.end'), test_text2.rstrip('\n'))
    +
    +    def test_writelines(self):
    +        eq = self.assertEqual
    +        get = self.text.get
    +        writelines = self.window.writelines
    +
    +        writelines(('Line 1\n', 'Line 2\n', 'Line 3\n'))
    +        eq(get('1.0', '1.end'), 'Line 1')
    +        eq(get('2.0', '2.end'), 'Line 2')
    +        eq(get('3.0', '3.end'), 'Line 3')
    +        eq(get('insert linestart', 'insert lineend'), '')
    +
    +    def test_goto_file_line(self):
    +        eq = self.assertEqual
    +        w = self.window
    +        text = self.text
    +
    +        w.flist = mock.Mock()
    +        gfl = w.flist.gotofileline = Func()
    +        showerror = w.showerror = Mbox_func()
    +
    +        # No file/line number.
    +        w.write('Not a file line')
    +        self.assertIsNone(w.goto_file_line())
    +        eq(gfl.called, 0)
    +        eq(showerror.title, 'No special line')
    +
    +        # Current file/line number.
    +        w.write(f'{str(__file__)}: 42: spam\n')
    +        w.write(f'{str(__file__)}: 21: spam')
    +        self.assertIsNone(w.goto_file_line())
    +        eq(gfl.args, (str(__file__), 21))
    +
    +        # Previous line has file/line number.
    +        text.delete('1.0', 'end')
    +        w.write(f'{str(__file__)}: 42: spam\n')
    +        w.write('Not a file line')
    +        self.assertIsNone(w.goto_file_line())
    +        eq(gfl.args, (str(__file__), 42))
    +
    +        del w.flist.gotofileline, w.showerror
    +
    +
    +class ModuleFunctionTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUp(cls):
    +        outwin.file_line_progs = None
    +
    +    def test_compile_progs(self):
    +        outwin.compile_progs()
    +        for pat, regex in zip(outwin.file_line_pats, outwin.file_line_progs):
    +            self.assertEqual(regex.pattern, pat)
    +
    +    @mock.patch('builtins.open')
    +    def test_file_line_helper(self, mock_open):
    +        flh = outwin.file_line_helper
    +        test_lines = (
    +            (r'foo file "testfile1", line 42, bar', ('testfile1', 42)),
    +            (r'foo testfile2(21) bar', ('testfile2', 21)),
    +            (r'  testfile3  : 42: foo bar\n', ('  testfile3  ', 42)),
    +            (r'foo testfile4.py :1: ', ('foo testfile4.py ', 1)),
    +            ('testfile5: \u19D4\u19D2: ', ('testfile5', 42)),
    +            (r'testfile6: 42', None),       # only one `:`
    +            (r'testfile7 42 text', None)    # no separators
    +            )
    +        for line, expected_output in test_lines:
    +            self.assertEqual(flh(line), expected_output)
    +            if expected_output:
    +                mock_open.assert_called_with(expected_output[0], 'r')
    +
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_parenmatch.py b/PythonLib/full/idlelib/idle_test/test_parenmatch.py
    index 16219815..4a41d843 100644
    --- a/PythonLib/full/idlelib/idle_test/test_parenmatch.py
    +++ b/PythonLib/full/idlelib/idle_test/test_parenmatch.py
    @@ -1,39 +1,31 @@
    -"""Test idlelib.ParenMatch."""
    -# This must currently be a gui test because ParenMatch methods use
    -# several text methods not defined on idlelib.idle_test.mock_tk.Text.
    +"""Test parenmatch, coverage 91%.
     
    -import unittest
    -from test.test_support import requires
    -from Tkinter import Tk, Text
    -from idlelib.ParenMatch import ParenMatch
    -
    -class Mock:  # 2.7 does not have unittest.mock
    -    def __init__(self, *args, **kwargs):
    -        self.called = False
    -
    -    def __call__(self, *args, **kwargs):
    -        self.called = True
    +This must currently be a gui test because ParenMatch methods use
    +several text methods not defined on idlelib.idle_test.mock_tk.Text.
    +"""
    +from idlelib.parenmatch import ParenMatch
    +from test.support import requires
    +requires('gui')
     
    -    def reset_mock(self, *args, **kwargs):
    -        self.called = False
    +import unittest
    +from unittest.mock import Mock
    +from tkinter import Tk, Text
     
    -    def after(self, *args, **kwargs):
    -        pass
     
     class DummyEditwin:
         def __init__(self, text):
             self.text = text
             self.indentwidth = 8
             self.tabwidth = 8
    -        self.context_use_ps1 = True
    +        self.prompt_last_line = '>>>' # Currently not used by parenmatch.
     
     
     class ParenMatchTest(unittest.TestCase):
     
         @classmethod
         def setUpClass(cls):
    -        requires('gui')
             cls.root = Tk()
    +        cls.root.withdraw()
             cls.text = Text(cls.root)
             cls.editwin = DummyEditwin(cls.text)
             cls.editwin.text_frame = Mock()
    @@ -41,52 +33,51 @@ def setUpClass(cls):
         @classmethod
         def tearDownClass(cls):
             del cls.text, cls.editwin
    +        cls.root.update_idletasks()
             cls.root.destroy()
             del cls.root
     
         def tearDown(self):
             self.text.delete('1.0', 'end')
     
    -    def test_paren_expression(self):
    -        """
    -        Test ParenMatch with 'expression' style.
    -        """
    -        text = self.text
    +    def get_parenmatch(self):
             pm = ParenMatch(self.editwin)
    -        pm.set_style('expression')
    -
    -        text.insert('insert', 'def foobar(a, b')
    -        pm.flash_paren_event('event')
    -        self.assertIn('<>', text.event_info())
    -        self.assertTupleEqual(text.tag_prevrange('paren', 'end'),
    -                             ('1.10', '1.15'))
    -        text.insert('insert', ')')
    -        pm.restore_event()
    -        self.assertNotIn('<>', text.event_info())
    -        self.assertEqual(text.tag_prevrange('paren', 'end'), ())
    -
    -        # paren_closed_event can only be tested as below
    -        pm.paren_closed_event('event')
    -        self.assertTupleEqual(text.tag_prevrange('paren', 'end'),
    -                                                ('1.10', '1.16'))
    +        pm.bell = lambda: None
    +        return pm
     
    -    def test_paren_default(self):
    +    def test_paren_styles(self):
             """
    -        Test ParenMatch with 'default' style.
    +        Test ParenMatch with each style.
             """
             text = self.text
    -        pm = ParenMatch(self.editwin)
    -        pm.set_style('default')
    -
    -        text.insert('insert', 'def foobar(a, b')
    -        pm.flash_paren_event('event')
    -        self.assertIn('<>', text.event_info())
    -        self.assertTupleEqual(text.tag_prevrange('paren', 'end'),
    -                             ('1.10', '1.11'))
    -        text.insert('insert', ')')
    -        pm.restore_event()
    -        self.assertNotIn('<>', text.event_info())
    -        self.assertEqual(text.tag_prevrange('paren', 'end'), ())
    +        pm = self.get_parenmatch()
    +        for style, range1, range2 in (
    +                ('opener', ('1.10', '1.11'), ('1.10', '1.11')),
    +                ('default',('1.10', '1.11'),('1.10', '1.11')),
    +                ('parens', ('1.14', '1.15'), ('1.15', '1.16')),
    +                ('expression', ('1.10', '1.15'), ('1.10', '1.16'))):
    +            with self.subTest(style=style):
    +                text.delete('1.0', 'end')
    +                pm.STYLE = style
    +                text.insert('insert', 'def foobar(a, b')
    +
    +                pm.flash_paren_event('event')
    +                self.assertIn('<>', text.event_info())
    +                if style == 'parens':
    +                    self.assertTupleEqual(text.tag_nextrange('paren', '1.0'),
    +                                          ('1.10', '1.11'))
    +                self.assertTupleEqual(
    +                        text.tag_prevrange('paren', 'end'), range1)
    +
    +                text.insert('insert', ')')
    +                pm.restore_event()
    +                self.assertNotIn('<>',
    +                                 text.event_info())
    +                self.assertEqual(text.tag_prevrange('paren', 'end'), ())
    +
    +                pm.paren_closed_event('event')
    +                self.assertTupleEqual(
    +                        text.tag_prevrange('paren', 'end'), range2)
     
         def test_paren_corner(self):
             """
    @@ -95,20 +86,20 @@ def test_paren_corner(self):
             These cases force conditional expression and alternate paths.
             """
             text = self.text
    -        pm = ParenMatch(self.editwin)
    +        pm = self.get_parenmatch()
     
             text.insert('insert', '# this is a commen)')
    -        self.assertIsNone(pm.paren_closed_event('event'))
    +        pm.paren_closed_event('event')
     
             text.insert('insert', '\ndef')
    -        self.assertIsNone(pm.flash_paren_event('event'))
    -        self.assertIsNone(pm.paren_closed_event('event'))
    +        pm.flash_paren_event('event')
    +        pm.paren_closed_event('event')
     
             text.insert('insert', ' a, *arg)')
    -        self.assertIsNone(pm.paren_closed_event('event'))
    +        pm.paren_closed_event('event')
     
         def test_handle_restore_timer(self):
    -        pm = ParenMatch(self.editwin)
    +        pm = self.get_parenmatch()
             pm.restore_event = Mock()
             pm.handle_restore_timer(0)
             self.assertTrue(pm.restore_event.called)
    diff --git a/PythonLib/full/idlelib/idle_test/test_pathbrowser.py b/PythonLib/full/idlelib/idle_test/test_pathbrowser.py
    index f0284148..13d8b9e1 100644
    --- a/PythonLib/full/idlelib/idle_test/test_pathbrowser.py
    +++ b/PythonLib/full/idlelib/idle_test/test_pathbrowser.py
    @@ -1,14 +1,69 @@
    +"Test pathbrowser, coverage 95%."
    +
    +from idlelib import pathbrowser
     import unittest
    -import os
    -import sys
    -import idlelib
    -from idlelib import PathBrowser
    +from test.support import requires
    +from tkinter import Tk
    +
    +import os.path
    +import pyclbr  # for _modules
    +import sys  # for sys.path
    +
    +from idlelib.idle_test.mock_idle import Func
    +import idlelib  # for __file__
    +from idlelib import browser
    +from idlelib.tree import TreeNode
    +
     
     class PathBrowserTest(unittest.TestCase):
     
    +    @classmethod
    +    def setUpClass(cls):
    +        requires('gui')
    +        cls.root = Tk()
    +        cls.root.withdraw()
    +        cls.pb = pathbrowser.PathBrowser(cls.root, _utest=True)
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        cls.pb.close()
    +        cls.root.update_idletasks()
    +        cls.root.destroy()
    +        del cls.root, cls.pb
    +
    +    def test_init(self):
    +        pb = self.pb
    +        eq = self.assertEqual
    +        eq(pb.master, self.root)
    +        eq(pyclbr._modules, {})
    +        self.assertIsInstance(pb.node, TreeNode)
    +        self.assertIsNotNone(browser.file_open)
    +
    +    def test_settitle(self):
    +        pb = self.pb
    +        self.assertEqual(pb.top.title(), 'Path Browser')
    +        self.assertEqual(pb.top.iconname(), 'Path Browser')
    +
    +    def test_rootnode(self):
    +        pb = self.pb
    +        rn = pb.rootnode()
    +        self.assertIsInstance(rn, pathbrowser.PathBrowserTreeItem)
    +
    +    def test_close(self):
    +        pb = self.pb
    +        pb.top.destroy = Func()
    +        pb.node.destroy = Func()
    +        pb.close()
    +        self.assertTrue(pb.top.destroy.called)
    +        self.assertTrue(pb.node.destroy.called)
    +        del pb.top.destroy, pb.node.destroy
    +
    +
    +class DirBrowserTreeItemTest(unittest.TestCase):
    +
         def test_DirBrowserTreeItem(self):
             # Issue16226 - make sure that getting a sublist works
    -        d = PathBrowser.DirBrowserTreeItem('')
    +        d = pathbrowser.DirBrowserTreeItem('')
             d.GetSubList()
             self.assertEqual('', d.GetText())
     
    @@ -16,13 +71,16 @@ def test_DirBrowserTreeItem(self):
             self.assertEqual(d.ispackagedir(dir), True)
             self.assertEqual(d.ispackagedir(dir + '/Icons'), False)
     
    +
    +class PathBrowserTreeItemTest(unittest.TestCase):
    +
         def test_PathBrowserTreeItem(self):
    -        p = PathBrowser.PathBrowserTreeItem()
    +        p = pathbrowser.PathBrowserTreeItem()
             self.assertEqual(p.GetText(), 'sys.path')
             sub = p.GetSubList()
             self.assertEqual(len(sub), len(sys.path))
    -        # Following fails in 2.7 because old-style class
    -        #self.assertEqual(type(sub[0]), PathBrowser.DirBrowserTreeItem)
    +        self.assertEqual(type(sub[0]), pathbrowser.DirBrowserTreeItem)
    +
     
     if __name__ == '__main__':
         unittest.main(verbosity=2, exit=False)
    diff --git a/PythonLib/full/idlelib/idle_test/test_percolator.py b/PythonLib/full/idlelib/idle_test/test_percolator.py
    new file mode 100644
    index 00000000..17668ccd
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_percolator.py
    @@ -0,0 +1,118 @@
    +"Test percolator, coverage 100%."
    +
    +from idlelib.percolator import Percolator, Delegator
    +import unittest
    +from test.support import requires
    +requires('gui')
    +from tkinter import Text, Tk, END
    +
    +
    +class MyFilter(Delegator):
    +    def __init__(self):
    +        Delegator.__init__(self, None)
    +
    +    def insert(self, *args):
    +        self.insert_called_with = args
    +        self.delegate.insert(*args)
    +
    +    def delete(self, *args):
    +        self.delete_called_with = args
    +        self.delegate.delete(*args)
    +
    +    def uppercase_insert(self, index, chars, tags=None):
    +        chars = chars.upper()
    +        self.delegate.insert(index, chars)
    +
    +    def lowercase_insert(self, index, chars, tags=None):
    +        chars = chars.lower()
    +        self.delegate.insert(index, chars)
    +
    +    def dont_insert(self, index, chars, tags=None):
    +        pass
    +
    +
    +class PercolatorTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        cls.root = Tk()
    +        cls.text = Text(cls.root)
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        del cls.text
    +        cls.root.destroy()
    +        del cls.root
    +
    +    def setUp(self):
    +        self.percolator = Percolator(self.text)
    +        self.filter_one = MyFilter()
    +        self.filter_two = MyFilter()
    +        self.percolator.insertfilter(self.filter_one)
    +        self.percolator.insertfilter(self.filter_two)
    +
    +    def tearDown(self):
    +        self.percolator.close()
    +        self.text.delete('1.0', END)
    +
    +    def test_insertfilter(self):
    +        self.assertIsNotNone(self.filter_one.delegate)
    +        self.assertEqual(self.percolator.top, self.filter_two)
    +        self.assertEqual(self.filter_two.delegate, self.filter_one)
    +        self.assertEqual(self.filter_one.delegate, self.percolator.bottom)
    +
    +    def test_removefilter(self):
    +        filter_three = MyFilter()
    +        self.percolator.removefilter(self.filter_two)
    +        self.assertEqual(self.percolator.top, self.filter_one)
    +        self.assertIsNone(self.filter_two.delegate)
    +
    +        filter_three = MyFilter()
    +        self.percolator.insertfilter(self.filter_two)
    +        self.percolator.insertfilter(filter_three)
    +        self.percolator.removefilter(self.filter_one)
    +        self.assertEqual(self.percolator.top, filter_three)
    +        self.assertEqual(filter_three.delegate, self.filter_two)
    +        self.assertEqual(self.filter_two.delegate, self.percolator.bottom)
    +        self.assertIsNone(self.filter_one.delegate)
    +
    +    def test_insert(self):
    +        self.text.insert('insert', 'foo')
    +        self.assertEqual(self.text.get('1.0', END), 'foo\n')
    +        self.assertTupleEqual(self.filter_one.insert_called_with,
    +                              ('insert', 'foo', None))
    +
    +    def test_modify_insert(self):
    +        self.filter_one.insert = self.filter_one.uppercase_insert
    +        self.text.insert('insert', 'bAr')
    +        self.assertEqual(self.text.get('1.0', END), 'BAR\n')
    +
    +    def test_modify_chain_insert(self):
    +        filter_three = MyFilter()
    +        self.percolator.insertfilter(filter_three)
    +        self.filter_two.insert = self.filter_two.uppercase_insert
    +        self.filter_one.insert = self.filter_one.lowercase_insert
    +        self.text.insert('insert', 'BaR')
    +        self.assertEqual(self.text.get('1.0', END), 'bar\n')
    +
    +    def test_dont_insert(self):
    +        self.filter_one.insert = self.filter_one.dont_insert
    +        self.text.insert('insert', 'foo bar')
    +        self.assertEqual(self.text.get('1.0', END), '\n')
    +        self.filter_one.insert = self.filter_one.dont_insert
    +        self.text.insert('insert', 'foo bar')
    +        self.assertEqual(self.text.get('1.0', END), '\n')
    +
    +    def test_without_filter(self):
    +        self.text.insert('insert', 'hello')
    +        self.assertEqual(self.text.get('1.0', 'end'), 'hello\n')
    +
    +    def test_delete(self):
    +        self.text.insert('insert', 'foo')
    +        self.text.delete('1.0', '1.2')
    +        self.assertEqual(self.text.get('1.0', END), 'o\n')
    +        self.assertTupleEqual(self.filter_one.delete_called_with,
    +                              ('1.0', '1.2'))
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_pyparse.py b/PythonLib/full/idlelib/idle_test/test_pyparse.py
    new file mode 100644
    index 00000000..f21baf75
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_pyparse.py
    @@ -0,0 +1,481 @@
    +"Test pyparse, coverage 96%."
    +
    +from idlelib import pyparse
    +import unittest
    +from collections import namedtuple
    +
    +
    +class ParseMapTest(unittest.TestCase):
    +
    +    def test_parsemap(self):
    +        keepwhite = {ord(c): ord(c) for c in ' \t\n\r'}
    +        mapping = pyparse.ParseMap(keepwhite)
    +        self.assertEqual(mapping[ord('\t')], ord('\t'))
    +        self.assertEqual(mapping[ord('a')], ord('x'))
    +        self.assertEqual(mapping[1000], ord('x'))
    +
    +    def test_trans(self):
    +        # trans is the production instance of ParseMap, used in _study1
    +        parser = pyparse.Parser(4, 4)
    +        self.assertEqual('\t a([{b}])b"c\'d\n'.translate(pyparse.trans),
    +                         'xxx(((x)))x"x\'x\n')
    +
    +
    +class PyParseTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        cls.parser = pyparse.Parser(indentwidth=4, tabwidth=4)
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        del cls.parser
    +
    +    def test_init(self):
    +        self.assertEqual(self.parser.indentwidth, 4)
    +        self.assertEqual(self.parser.tabwidth, 4)
    +
    +    def test_set_code(self):
    +        eq = self.assertEqual
    +        p = self.parser
    +        setcode = p.set_code
    +
    +        # Not empty and doesn't end with newline.
    +        with self.assertRaises(AssertionError):
    +            setcode('a')
    +
    +        tests = ('',
    +                 'a\n')
    +
    +        for string in tests:
    +            with self.subTest(string=string):
    +                setcode(string)
    +                eq(p.code, string)
    +                eq(p.study_level, 0)
    +
    +    def test_find_good_parse_start(self):
    +        eq = self.assertEqual
    +        p = self.parser
    +        setcode = p.set_code
    +        start = p.find_good_parse_start
    +        def char_in_string_false(index): return False
    +
    +        # First line starts with 'def' and ends with ':', then 0 is the pos.
    +        setcode('def spam():\n')
    +        eq(start(char_in_string_false), 0)
    +
    +        # First line begins with a keyword in the list and ends
    +        # with an open brace, then 0 is the pos.  This is how
    +        # hyperparser calls this function as the newline is not added
    +        # in the editor, but rather on the call to setcode.
    +        setcode('class spam( ' + ' \n')
    +        eq(start(char_in_string_false), 0)
    +
    +        # Split def across lines.
    +        setcode('"""This is a module docstring"""\n'
    +                'class C():\n'
    +                '    def __init__(self, a,\n'
    +                '                 b=True):\n'
    +                '        pass\n'
    +                )
    +
    +        # Passing no value or non-callable should fail (issue 32989).
    +        with self.assertRaises(TypeError):
    +            start()
    +        with self.assertRaises(TypeError):
    +            start(False)
    +
    +        # Make text look like a string.  This returns pos as the start
    +        # position, but it's set to None.
    +        self.assertIsNone(start(is_char_in_string=lambda index: True))
    +
    +        # Make all text look like it's not in a string.  This means that it
    +        # found a good start position.
    +        eq(start(char_in_string_false), 44)
    +
    +        # If the beginning of the def line is not in a string, then it
    +        # returns that as the index.
    +        eq(start(is_char_in_string=lambda index: index > 44), 44)
    +        # If the beginning of the def line is in a string, then it
    +        # looks for a previous index.
    +        eq(start(is_char_in_string=lambda index: index >= 44), 33)
    +        # If everything before the 'def' is in a string, then returns None.
    +        # The non-continuation def line returns 44 (see below).
    +        eq(start(is_char_in_string=lambda index: index < 44), None)
    +
    +        # Code without extra line break in def line - mostly returns the same
    +        # values.
    +        setcode('"""This is a module docstring"""\n'
    +                'class C():\n'
    +                '    def __init__(self, a, b=True):\n'
    +                '        pass\n'
    +                )
    +        eq(start(char_in_string_false), 44)
    +        eq(start(is_char_in_string=lambda index: index > 44), 44)
    +        eq(start(is_char_in_string=lambda index: index >= 44), 33)
    +        # When the def line isn't split, this returns which doesn't match the
    +        # split line test.
    +        eq(start(is_char_in_string=lambda index: index < 44), 44)
    +
    +    def test_set_lo(self):
    +        code = (
    +                '"""This is a module docstring"""\n'
    +                'class C():\n'
    +                '    def __init__(self, a,\n'
    +                '                 b=True):\n'
    +                '        pass\n'
    +                )
    +        p = self.parser
    +        p.set_code(code)
    +
    +        # Previous character is not a newline.
    +        with self.assertRaises(AssertionError):
    +            p.set_lo(5)
    +
    +        # A value of 0 doesn't change self.code.
    +        p.set_lo(0)
    +        self.assertEqual(p.code, code)
    +
    +        # An index that is preceded by a newline.
    +        p.set_lo(44)
    +        self.assertEqual(p.code, code[44:])
    +
    +    def test_study1(self):
    +        eq = self.assertEqual
    +        p = self.parser
    +        setcode = p.set_code
    +        study = p._study1
    +
    +        (NONE, BACKSLASH, FIRST, NEXT, BRACKET) = range(5)
    +        TestInfo = namedtuple('TestInfo', ['string', 'goodlines',
    +                                           'continuation'])
    +        tests = (
    +            TestInfo('', [0], NONE),
    +            # Docstrings.
    +            TestInfo('"""This is a complete docstring."""\n', [0, 1], NONE),
    +            TestInfo("'''This is a complete docstring.'''\n", [0, 1], NONE),
    +            TestInfo('"""This is a continued docstring.\n', [0, 1], FIRST),
    +            TestInfo("'''This is a continued docstring.\n", [0, 1], FIRST),
    +            TestInfo('"""Closing quote does not match."\n', [0, 1], FIRST),
    +            TestInfo('"""Bracket in docstring [\n', [0, 1], FIRST),
    +            TestInfo("'''Incomplete two line docstring.\n\n", [0, 2], NEXT),
    +            # Single-quoted strings.
    +            TestInfo('"This is a complete string."\n', [0, 1], NONE),
    +            TestInfo('"This is an incomplete string.\n', [0, 1], NONE),
    +            TestInfo("'This is more incomplete.\n\n", [0, 1, 2], NONE),
    +            # Comment (backslash does not continue comments).
    +            TestInfo('# Comment\\\n', [0, 1], NONE),
    +            # Brackets.
    +            TestInfo('("""Complete string in bracket"""\n', [0, 1], BRACKET),
    +            TestInfo('("""Open string in bracket\n', [0, 1], FIRST),
    +            TestInfo('a = (1 + 2) - 5 *\\\n', [0, 1], BACKSLASH),  # No bracket.
    +            TestInfo('\n   def function1(self, a,\n                 b):\n',
    +                     [0, 1, 3], NONE),
    +            TestInfo('\n   def function1(self, a,\\\n', [0, 1, 2], BRACKET),
    +            TestInfo('\n   def function1(self, a,\n', [0, 1, 2], BRACKET),
    +            TestInfo('())\n', [0, 1], NONE),                    # Extra closer.
    +            TestInfo(')(\n', [0, 1], BRACKET),                  # Extra closer.
    +            # For the mismatched example, it doesn't look like continuation.
    +            TestInfo('{)(]\n', [0, 1], NONE),                   # Mismatched.
    +            )
    +
    +        for test in tests:
    +            with self.subTest(string=test.string):
    +                setcode(test.string)  # resets study_level
    +                study()
    +                eq(p.study_level, 1)
    +                eq(p.goodlines, test.goodlines)
    +                eq(p.continuation, test.continuation)
    +
    +        # Called again, just returns without reprocessing.
    +        self.assertIsNone(study())
    +
    +    def test_get_continuation_type(self):
    +        eq = self.assertEqual
    +        p = self.parser
    +        setcode = p.set_code
    +        gettype = p.get_continuation_type
    +
    +        (NONE, BACKSLASH, FIRST, NEXT, BRACKET) = range(5)
    +        TestInfo = namedtuple('TestInfo', ['string', 'continuation'])
    +        tests = (
    +            TestInfo('', NONE),
    +            TestInfo('"""This is a continuation docstring.\n', FIRST),
    +            TestInfo("'''This is a multiline-continued docstring.\n\n", NEXT),
    +            TestInfo('a = (1 + 2) - 5 *\\\n', BACKSLASH),
    +            TestInfo('\n   def function1(self, a,\\\n', BRACKET)
    +            )
    +
    +        for test in tests:
    +            with self.subTest(string=test.string):
    +                setcode(test.string)
    +                eq(gettype(), test.continuation)
    +
    +    def test_study2(self):
    +        eq = self.assertEqual
    +        p = self.parser
    +        setcode = p.set_code
    +        study = p._study2
    +
    +        TestInfo = namedtuple('TestInfo', ['string', 'start', 'end', 'lastch',
    +                                           'openbracket', 'bracketing'])
    +        tests = (
    +            TestInfo('', 0, 0, '', None, ((0, 0),)),
    +            TestInfo("'''This is a multiline continuation docstring.\n\n",
    +                     0, 48, "'", None, ((0, 0), (0, 1), (48, 0))),
    +            TestInfo(' # Comment\\\n',
    +                     0, 12, '', None, ((0, 0), (1, 1), (12, 0))),
    +            # A comment without a space is a special case
    +            TestInfo(' #Comment\\\n',
    +                     0, 0, '', None, ((0, 0),)),
    +            # Backslash continuation.
    +            TestInfo('a = (1 + 2) - 5 *\\\n',
    +                     0, 19, '*', None, ((0, 0), (4, 1), (11, 0))),
    +            # Bracket continuation with close.
    +            TestInfo('\n   def function1(self, a,\n                 b):\n',
    +                     1, 48, ':', None, ((1, 0), (17, 1), (46, 0))),
    +            # Bracket continuation with unneeded backslash.
    +            TestInfo('\n   def function1(self, a,\\\n',
    +                     1, 28, ',', 17, ((1, 0), (17, 1))),
    +            # Bracket continuation.
    +            TestInfo('\n   def function1(self, a,\n',
    +                     1, 27, ',', 17, ((1, 0), (17, 1))),
    +            # Bracket continuation with comment at end of line with text.
    +            TestInfo('\n   def function1(self, a,  # End of line comment.\n',
    +                     1, 51, ',', 17, ((1, 0), (17, 1), (28, 2), (51, 1))),
    +            # Multi-line statement with comment line in between code lines.
    +            TestInfo('  a = ["first item",\n  # Comment line\n    "next item",\n',
    +                     0, 55, ',', 6, ((0, 0), (6, 1), (7, 2), (19, 1),
    +                                     (23, 2), (38, 1), (42, 2), (53, 1))),
    +            TestInfo('())\n',
    +                     0, 4, ')', None, ((0, 0), (0, 1), (2, 0), (3, 0))),
    +            TestInfo(')(\n', 0, 3, '(', 1, ((0, 0), (1, 0), (1, 1))),
    +            # Wrong closers still decrement stack level.
    +            TestInfo('{)(]\n',
    +                     0, 5, ']', None, ((0, 0), (0, 1), (2, 0), (2, 1), (4, 0))),
    +            # Character after backslash.
    +            TestInfo(':\\a\n', 0, 4, '\\a', None, ((0, 0),)),
    +            TestInfo('\n', 0, 0, '', None, ((0, 0),)),
    +            )
    +
    +        for test in tests:
    +            with self.subTest(string=test.string):
    +                setcode(test.string)
    +                study()
    +                eq(p.study_level, 2)
    +                eq(p.stmt_start, test.start)
    +                eq(p.stmt_end, test.end)
    +                eq(p.lastch, test.lastch)
    +                eq(p.lastopenbracketpos, test.openbracket)
    +                eq(p.stmt_bracketing, test.bracketing)
    +
    +        # Called again, just returns without reprocessing.
    +        self.assertIsNone(study())
    +
    +    def test_get_num_lines_in_stmt(self):
    +        eq = self.assertEqual
    +        p = self.parser
    +        setcode = p.set_code
    +        getlines = p.get_num_lines_in_stmt
    +
    +        TestInfo = namedtuple('TestInfo', ['string', 'lines'])
    +        tests = (
    +            TestInfo('[x for x in a]\n', 1),      # Closed on one line.
    +            TestInfo('[x\nfor x in a\n', 2),      # Not closed.
    +            TestInfo('[x\\\nfor x in a\\\n', 2),  # "", uneeded backslashes.
    +            TestInfo('[x\nfor x in a\n]\n', 3),   # Closed on multi-line.
    +            TestInfo('\n"""Docstring comment L1"""\nL2\nL3\nL4\n', 1),
    +            TestInfo('\n"""Docstring comment L1\nL2"""\nL3\nL4\n', 1),
    +            TestInfo('\n"""Docstring comment L1\\\nL2\\\nL3\\\nL4\\\n', 4),
    +            TestInfo('\n\n"""Docstring comment L1\\\nL2\\\nL3\\\nL4\\\n"""\n', 5)
    +            )
    +
    +        # Blank string doesn't have enough elements in goodlines.
    +        setcode('')
    +        with self.assertRaises(IndexError):
    +            getlines()
    +
    +        for test in tests:
    +            with self.subTest(string=test.string):
    +                setcode(test.string)
    +                eq(getlines(), test.lines)
    +
    +    def test_compute_bracket_indent(self):
    +        eq = self.assertEqual
    +        p = self.parser
    +        setcode = p.set_code
    +        indent = p.compute_bracket_indent
    +
    +        TestInfo = namedtuple('TestInfo', ['string', 'spaces'])
    +        tests = (
    +            TestInfo('def function1(self, a,\n', 14),
    +            # Characters after bracket.
    +            TestInfo('\n    def function1(self, a,\n', 18),
    +            TestInfo('\n\tdef function1(self, a,\n', 18),
    +            # No characters after bracket.
    +            TestInfo('\n    def function1(\n', 8),
    +            TestInfo('\n\tdef function1(\n', 8),
    +            TestInfo('\n    def function1(  \n', 8),  # Ignore extra spaces.
    +            TestInfo('[\n"first item",\n  # Comment line\n    "next item",\n', 0),
    +            TestInfo('[\n  "first item",\n  # Comment line\n    "next item",\n', 2),
    +            TestInfo('["first item",\n  # Comment line\n    "next item",\n', 1),
    +            TestInfo('(\n', 4),
    +            TestInfo('(a\n', 1),
    +             )
    +
    +        # Must be C_BRACKET continuation type.
    +        setcode('def function1(self, a, b):\n')
    +        with self.assertRaises(AssertionError):
    +            indent()
    +
    +        for test in tests:
    +            setcode(test.string)
    +            eq(indent(), test.spaces)
    +
    +    def test_compute_backslash_indent(self):
    +        eq = self.assertEqual
    +        p = self.parser
    +        setcode = p.set_code
    +        indent = p.compute_backslash_indent
    +
    +        # Must be C_BACKSLASH continuation type.
    +        errors = (('def function1(self, a, b\\\n'),  # Bracket.
    +                  ('    """ (\\\n'),                 # Docstring.
    +                  ('a = #\\\n'),                     # Inline comment.
    +                  )
    +        for string in errors:
    +            with self.subTest(string=string):
    +                setcode(string)
    +                with self.assertRaises(AssertionError):
    +                    indent()
    +
    +        TestInfo = namedtuple('TestInfo', ('string', 'spaces'))
    +        tests = (TestInfo('a = (1 + 2) - 5 *\\\n', 4),
    +                 TestInfo('a = 1 + 2 - 5 *\\\n', 4),
    +                 TestInfo('    a = 1 + 2 - 5 *\\\n', 8),
    +                 TestInfo('  a = "spam"\\\n', 6),
    +                 TestInfo('  a = \\\n"a"\\\n', 4),
    +                 TestInfo('  a = #\\\n"a"\\\n', 5),
    +                 TestInfo('a == \\\n', 2),
    +                 TestInfo('a != \\\n', 2),
    +                 # Difference between containing = and those not.
    +                 TestInfo('\\\n', 2),
    +                 TestInfo('    \\\n', 6),
    +                 TestInfo('\t\\\n', 6),
    +                 TestInfo('a\\\n', 3),
    +                 TestInfo('{}\\\n', 4),
    +                 TestInfo('(1 + 2) - 5 *\\\n', 3),
    +                 )
    +        for test in tests:
    +            with self.subTest(string=test.string):
    +                setcode(test.string)
    +                eq(indent(), test.spaces)
    +
    +    def test_get_base_indent_string(self):
    +        eq = self.assertEqual
    +        p = self.parser
    +        setcode = p.set_code
    +        baseindent = p.get_base_indent_string
    +
    +        TestInfo = namedtuple('TestInfo', ['string', 'indent'])
    +        tests = (TestInfo('', ''),
    +                 TestInfo('def a():\n', ''),
    +                 TestInfo('\tdef a():\n', '\t'),
    +                 TestInfo('    def a():\n', '    '),
    +                 TestInfo('    def a(\n', '    '),
    +                 TestInfo('\t\n    def a(\n', '    '),
    +                 TestInfo('\t\n    # Comment.\n', '    '),
    +                 )
    +
    +        for test in tests:
    +            with self.subTest(string=test.string):
    +                setcode(test.string)
    +                eq(baseindent(), test.indent)
    +
    +    def test_is_block_opener(self):
    +        yes = self.assertTrue
    +        no = self.assertFalse
    +        p = self.parser
    +        setcode = p.set_code
    +        opener = p.is_block_opener
    +
    +        TestInfo = namedtuple('TestInfo', ['string', 'assert_'])
    +        tests = (
    +            TestInfo('def a():\n', yes),
    +            TestInfo('\n   def function1(self, a,\n                 b):\n', yes),
    +            TestInfo(':\n', yes),
    +            TestInfo('a:\n', yes),
    +            TestInfo('):\n', yes),
    +            TestInfo('(:\n', yes),
    +            TestInfo('":\n', no),
    +            TestInfo('\n   def function1(self, a,\n', no),
    +            TestInfo('def function1(self, a):\n    pass\n', no),
    +            TestInfo('# A comment:\n', no),
    +            TestInfo('"""A docstring:\n', no),
    +            TestInfo('"""A docstring:\n', no),
    +            )
    +
    +        for test in tests:
    +            with self.subTest(string=test.string):
    +                setcode(test.string)
    +                test.assert_(opener())
    +
    +    def test_is_block_closer(self):
    +        yes = self.assertTrue
    +        no = self.assertFalse
    +        p = self.parser
    +        setcode = p.set_code
    +        closer = p.is_block_closer
    +
    +        TestInfo = namedtuple('TestInfo', ['string', 'assert_'])
    +        tests = (
    +            TestInfo('return\n', yes),
    +            TestInfo('\tbreak\n', yes),
    +            TestInfo('  continue\n', yes),
    +            TestInfo('     raise\n', yes),
    +            TestInfo('pass    \n', yes),
    +            TestInfo('pass\t\n', yes),
    +            TestInfo('return #\n', yes),
    +            TestInfo('raised\n', no),
    +            TestInfo('returning\n', no),
    +            TestInfo('# return\n', no),
    +            TestInfo('"""break\n', no),
    +            TestInfo('"continue\n', no),
    +            TestInfo('def function1(self, a):\n    pass\n', yes),
    +            )
    +
    +        for test in tests:
    +            with self.subTest(string=test.string):
    +                setcode(test.string)
    +                test.assert_(closer())
    +
    +    def test_get_last_stmt_bracketing(self):
    +        eq = self.assertEqual
    +        p = self.parser
    +        setcode = p.set_code
    +        bracketing = p.get_last_stmt_bracketing
    +
    +        TestInfo = namedtuple('TestInfo', ['string', 'bracket'])
    +        tests = (
    +            TestInfo('', ((0, 0),)),
    +            TestInfo('a\n', ((0, 0),)),
    +            TestInfo('()()\n', ((0, 0), (0, 1), (2, 0), (2, 1), (4, 0))),
    +            TestInfo('(\n)()\n', ((0, 0), (0, 1), (3, 0), (3, 1), (5, 0))),
    +            TestInfo('()\n()\n', ((3, 0), (3, 1), (5, 0))),
    +            TestInfo('()(\n)\n', ((0, 0), (0, 1), (2, 0), (2, 1), (5, 0))),
    +            TestInfo('(())\n', ((0, 0), (0, 1), (1, 2), (3, 1), (4, 0))),
    +            TestInfo('(\n())\n', ((0, 0), (0, 1), (2, 2), (4, 1), (5, 0))),
    +            # Same as matched test.
    +            TestInfo('{)(]\n', ((0, 0), (0, 1), (2, 0), (2, 1), (4, 0))),
    +            TestInfo('(((())\n',
    +                     ((0, 0), (0, 1), (1, 2), (2, 3), (3, 4), (5, 3), (6, 2))),
    +            )
    +
    +        for test in tests:
    +            with self.subTest(string=test.string):
    +                setcode(test.string)
    +                eq(bracketing(), test.bracket)
    +
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_pyshell.py b/PythonLib/full/idlelib/idle_test/test_pyshell.py
    new file mode 100644
    index 00000000..4a096676
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_pyshell.py
    @@ -0,0 +1,64 @@
    +"Test pyshell, coverage 12%."
    +# Plus coverage of test_warning.  Was 20% with test_openshell.
    +
    +from idlelib import pyshell
    +import unittest
    +from test.support import requires
    +from tkinter import Tk
    +
    +
    +class FunctionTest(unittest.TestCase):
    +    # Test stand-alone module level non-gui functions.
    +
    +    def test_restart_line_wide(self):
    +        eq = self.assertEqual
    +        for file, mul, extra in (('', 22, ''), ('finame', 21, '=')):
    +            width = 60
    +            bar = mul * '='
    +            with self.subTest(file=file, bar=bar):
    +                file = file or 'Shell'
    +                line = pyshell.restart_line(width, file)
    +                eq(len(line), width)
    +                eq(line, f"{bar+extra} RESTART: {file} {bar}")
    +
    +    def test_restart_line_narrow(self):
    +        expect, taglen = "= RESTART: Shell", 16
    +        for width in (taglen-1, taglen, taglen+1):
    +            with self.subTest(width=width):
    +                self.assertEqual(pyshell.restart_line(width, ''), expect)
    +        self.assertEqual(pyshell.restart_line(taglen+2, ''), expect+' =')
    +
    +
    +class PyShellFileListTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        requires('gui')
    +        cls.root = Tk()
    +        cls.root.withdraw()
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        #cls.root.update_idletasks()
    +##        for id in cls.root.tk.call('after', 'info'):
    +##            cls.root.after_cancel(id)  # Need for EditorWindow.
    +        cls.root.destroy()
    +        del cls.root
    +
    +    def test_init(self):
    +        psfl = pyshell.PyShellFileList(self.root)
    +        self.assertEqual(psfl.EditorWindow, pyshell.PyShellEditorWindow)
    +        self.assertIsNone(psfl.pyshell)
    +
    +# The following sometimes causes 'invalid command name "109734456recolorize"'.
    +# Uncommenting after_cancel above prevents this, but results in
    +# TclError: bad window path name ".!listedtoplevel.!frame.text"
    +# which is normally prevented by after_cancel.
    +##    def test_openshell(self):
    +##        pyshell.use_subprocess = False
    +##        ps = pyshell.PyShellFileList(self.root).open_shell()
    +##        self.assertIsInstance(ps, pyshell.PyShell)
    +
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_query.py b/PythonLib/full/idlelib/idle_test/test_query.py
    new file mode 100644
    index 00000000..f9575851
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_query.py
    @@ -0,0 +1,405 @@
    +"""Test query, coverage 93%).
    +
    +Non-gui tests for Query, SectionName, ModuleName, and HelpSource use
    +dummy versions that extract the non-gui methods and add other needed
    +attributes.  GUI tests create an instance of each class and simulate
    +entries and button clicks.  Subclass tests only target the new code in
    +the subclass definition.
    +
    +The appearance of the widgets is checked by the Query and
    +HelpSource htests.  These are run by running query.py.
    +"""
    +from idlelib import query
    +import unittest
    +from test.support import requires
    +from tkinter import Tk, END
    +
    +import sys
    +from unittest import mock
    +from idlelib.idle_test.mock_tk import Var
    +
    +
    +# NON-GUI TESTS
    +
    +class QueryTest(unittest.TestCase):
    +    "Test Query base class."
    +
    +    class Dummy_Query:
    +        # Test the following Query methods.
    +        entry_ok = query.Query.entry_ok
    +        ok = query.Query.ok
    +        cancel = query.Query.cancel
    +        # Add attributes and initialization needed for tests.
    +        def __init__(self, dummy_entry):
    +            self.entry = Var(value=dummy_entry)
    +            self.entry_error = {'text': ''}
    +            self.result = None
    +            self.destroyed = False
    +        def showerror(self, message):
    +            self.entry_error['text'] = message
    +        def destroy(self):
    +            self.destroyed = True
    +
    +    def test_entry_ok_blank(self):
    +        dialog = self.Dummy_Query(' ')
    +        self.assertEqual(dialog.entry_ok(), None)
    +        self.assertEqual((dialog.result, dialog.destroyed), (None, False))
    +        self.assertIn('blank line', dialog.entry_error['text'])
    +
    +    def test_entry_ok_good(self):
    +        dialog = self.Dummy_Query('  good ')
    +        Equal = self.assertEqual
    +        Equal(dialog.entry_ok(), 'good')
    +        Equal((dialog.result, dialog.destroyed), (None, False))
    +        Equal(dialog.entry_error['text'], '')
    +
    +    def test_ok_blank(self):
    +        dialog = self.Dummy_Query('')
    +        dialog.entry.focus_set = mock.Mock()
    +        self.assertEqual(dialog.ok(), None)
    +        self.assertTrue(dialog.entry.focus_set.called)
    +        del dialog.entry.focus_set
    +        self.assertEqual((dialog.result, dialog.destroyed), (None, False))
    +
    +    def test_ok_good(self):
    +        dialog = self.Dummy_Query('good')
    +        self.assertEqual(dialog.ok(), None)
    +        self.assertEqual((dialog.result, dialog.destroyed), ('good', True))
    +
    +    def test_cancel(self):
    +        dialog = self.Dummy_Query('does not matter')
    +        self.assertEqual(dialog.cancel(), None)
    +        self.assertEqual((dialog.result, dialog.destroyed), (None, True))
    +
    +
    +class SectionNameTest(unittest.TestCase):
    +    "Test SectionName subclass of Query."
    +
    +    class Dummy_SectionName:
    +        entry_ok = query.SectionName.entry_ok  # Function being tested.
    +        used_names = ['used']
    +        def __init__(self, dummy_entry):
    +            self.entry = Var(value=dummy_entry)
    +            self.entry_error = {'text': ''}
    +        def showerror(self, message):
    +            self.entry_error['text'] = message
    +
    +    def test_blank_section_name(self):
    +        dialog = self.Dummy_SectionName(' ')
    +        self.assertEqual(dialog.entry_ok(), None)
    +        self.assertIn('no name', dialog.entry_error['text'])
    +
    +    def test_used_section_name(self):
    +        dialog = self.Dummy_SectionName('used')
    +        self.assertEqual(dialog.entry_ok(), None)
    +        self.assertIn('use', dialog.entry_error['text'])
    +
    +    def test_long_section_name(self):
    +        dialog = self.Dummy_SectionName('good'*8)
    +        self.assertEqual(dialog.entry_ok(), None)
    +        self.assertIn('longer than 30', dialog.entry_error['text'])
    +
    +    def test_good_section_name(self):
    +        dialog = self.Dummy_SectionName('  good ')
    +        self.assertEqual(dialog.entry_ok(), 'good')
    +        self.assertEqual(dialog.entry_error['text'], '')
    +
    +
    +class ModuleNameTest(unittest.TestCase):
    +    "Test ModuleName subclass of Query."
    +
    +    class Dummy_ModuleName:
    +        entry_ok = query.ModuleName.entry_ok  # Function being tested.
    +        text0 = ''
    +        def __init__(self, dummy_entry):
    +            self.entry = Var(value=dummy_entry)
    +            self.entry_error = {'text': ''}
    +        def showerror(self, message):
    +            self.entry_error['text'] = message
    +
    +    def test_blank_module_name(self):
    +        dialog = self.Dummy_ModuleName(' ')
    +        self.assertEqual(dialog.entry_ok(), None)
    +        self.assertIn('no name', dialog.entry_error['text'])
    +
    +    def test_bogus_module_name(self):
    +        dialog = self.Dummy_ModuleName('__name_xyz123_should_not_exist__')
    +        self.assertEqual(dialog.entry_ok(), None)
    +        self.assertIn('not found', dialog.entry_error['text'])
    +
    +    def test_c_source_name(self):
    +        dialog = self.Dummy_ModuleName('itertools')
    +        self.assertEqual(dialog.entry_ok(), None)
    +        self.assertIn('source-based', dialog.entry_error['text'])
    +
    +    def test_good_module_name(self):
    +        dialog = self.Dummy_ModuleName('idlelib')
    +        self.assertTrue(dialog.entry_ok().endswith('__init__.py'))
    +        self.assertEqual(dialog.entry_error['text'], '')
    +
    +
    +# 3 HelpSource test classes each test one method.
    +
    +class HelpsourceBrowsefileTest(unittest.TestCase):
    +    "Test browse_file method of ModuleName subclass of Query."
    +
    +    class Dummy_HelpSource:
    +        browse_file = query.HelpSource.browse_file
    +        pathvar = Var()
    +
    +    def test_file_replaces_path(self):
    +        dialog = self.Dummy_HelpSource()
    +        # Path is widget entry, either '' or something.
    +        # Func return is file dialog return, either '' or something.
    +        # Func return should override widget entry.
    +        # We need all 4 combinations to test all (most) code paths.
    +        for path, func, result in (
    +                ('', lambda a,b,c:'', ''),
    +                ('', lambda a,b,c: __file__, __file__),
    +                ('htest', lambda a,b,c:'', 'htest'),
    +                ('htest', lambda a,b,c: __file__, __file__)):
    +            with self.subTest():
    +                dialog.pathvar.set(path)
    +                dialog.askfilename = func
    +                dialog.browse_file()
    +                self.assertEqual(dialog.pathvar.get(), result)
    +
    +
    +class HelpsourcePathokTest(unittest.TestCase):
    +    "Test path_ok method of HelpSource subclass of Query."
    +
    +    class Dummy_HelpSource:
    +        path_ok = query.HelpSource.path_ok
    +        def __init__(self, dummy_path):
    +            self.path = Var(value=dummy_path)
    +            self.path_error = {'text': ''}
    +        def showerror(self, message, widget=None):
    +            self.path_error['text'] = message
    +
    +    orig_platform = query.platform  # Set in test_path_ok_file.
    +    @classmethod
    +    def tearDownClass(cls):
    +        query.platform = cls.orig_platform
    +
    +    def test_path_ok_blank(self):
    +        dialog = self.Dummy_HelpSource(' ')
    +        self.assertEqual(dialog.path_ok(), None)
    +        self.assertIn('no help file', dialog.path_error['text'])
    +
    +    def test_path_ok_bad(self):
    +        dialog = self.Dummy_HelpSource(__file__ + 'bad-bad-bad')
    +        self.assertEqual(dialog.path_ok(), None)
    +        self.assertIn('not exist', dialog.path_error['text'])
    +
    +    def test_path_ok_web(self):
    +        dialog = self.Dummy_HelpSource('')
    +        Equal = self.assertEqual
    +        for url in 'www.py.org', 'http://py.org':
    +            with self.subTest():
    +                dialog.path.set(url)
    +                self.assertEqual(dialog.path_ok(), url)
    +                self.assertEqual(dialog.path_error['text'], '')
    +
    +    def test_path_ok_file(self):
    +        dialog = self.Dummy_HelpSource('')
    +        for platform, prefix in ('darwin', 'file://'), ('other', ''):
    +            with self.subTest():
    +                query.platform = platform
    +                dialog.path.set(__file__)
    +                self.assertEqual(dialog.path_ok(), prefix + __file__)
    +                self.assertEqual(dialog.path_error['text'], '')
    +
    +
    +class HelpsourceEntryokTest(unittest.TestCase):
    +    "Test entry_ok method of HelpSource subclass of Query."
    +
    +    class Dummy_HelpSource:
    +        entry_ok = query.HelpSource.entry_ok
    +        entry_error = {}
    +        path_error = {}
    +        def item_ok(self):
    +            return self.name
    +        def path_ok(self):
    +            return self.path
    +
    +    def test_entry_ok_helpsource(self):
    +        dialog = self.Dummy_HelpSource()
    +        for name, path, result in ((None, None, None),
    +                                   (None, 'doc.txt', None),
    +                                   ('doc', None, None),
    +                                   ('doc', 'doc.txt', ('doc', 'doc.txt'))):
    +            with self.subTest():
    +                dialog.name, dialog.path = name, path
    +                self.assertEqual(dialog.entry_ok(), result)
    +
    +
    +# 2 CustomRun test classes each test one method.
    +
    +class CustomRunCLIargsokTest(unittest.TestCase):
    +    "Test cli_ok method of the CustomRun subclass of Query."
    +
    +    class Dummy_CustomRun:
    +        cli_args_ok = query.CustomRun.cli_args_ok
    +        def __init__(self, dummy_entry):
    +            self.entry = Var(value=dummy_entry)
    +            self.entry_error = {'text': ''}
    +        def showerror(self, message):
    +            self.entry_error['text'] = message
    +
    +    def test_blank_args(self):
    +        dialog = self.Dummy_CustomRun(' ')
    +        self.assertEqual(dialog.cli_args_ok(), [])
    +
    +    def test_invalid_args(self):
    +        dialog = self.Dummy_CustomRun("'no-closing-quote")
    +        self.assertEqual(dialog.cli_args_ok(), None)
    +        self.assertIn('No closing', dialog.entry_error['text'])
    +
    +    def test_good_args(self):
    +        args = ['-n', '10', '--verbose', '-p', '/path', '--name']
    +        dialog = self.Dummy_CustomRun(' '.join(args) + ' "my name"')
    +        self.assertEqual(dialog.cli_args_ok(), args + ["my name"])
    +        self.assertEqual(dialog.entry_error['text'], '')
    +
    +
    +class CustomRunEntryokTest(unittest.TestCase):
    +    "Test entry_ok method of the CustomRun subclass of Query."
    +
    +    class Dummy_CustomRun:
    +        entry_ok = query.CustomRun.entry_ok
    +        entry_error = {}
    +        restartvar = Var()
    +        def cli_args_ok(self):
    +            return self.cli_args
    +
    +    def test_entry_ok_customrun(self):
    +        dialog = self.Dummy_CustomRun()
    +        for restart in {True, False}:
    +            dialog.restartvar.set(restart)
    +            for cli_args, result in ((None, None),
    +                                     (['my arg'], (['my arg'], restart))):
    +                with self.subTest(restart=restart, cli_args=cli_args):
    +                    dialog.cli_args = cli_args
    +                    self.assertEqual(dialog.entry_ok(), result)
    +
    +
    +# GUI TESTS
    +
    +class QueryGuiTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        requires('gui')
    +        cls.root = root = Tk()
    +        cls.root.withdraw()
    +        cls.dialog = query.Query(root, 'TEST', 'test', _utest=True)
    +        cls.dialog.destroy = mock.Mock()
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        del cls.dialog.destroy
    +        del cls.dialog
    +        cls.root.destroy()
    +        del cls.root
    +
    +    def setUp(self):
    +        self.dialog.entry.delete(0, 'end')
    +        self.dialog.result = None
    +        self.dialog.destroy.reset_mock()
    +
    +    def test_click_ok(self):
    +        dialog = self.dialog
    +        dialog.entry.insert(0, 'abc')
    +        dialog.button_ok.invoke()
    +        self.assertEqual(dialog.result, 'abc')
    +        self.assertTrue(dialog.destroy.called)
    +
    +    def test_click_blank(self):
    +        dialog = self.dialog
    +        dialog.button_ok.invoke()
    +        self.assertEqual(dialog.result, None)
    +        self.assertFalse(dialog.destroy.called)
    +
    +    def test_click_cancel(self):
    +        dialog = self.dialog
    +        dialog.entry.insert(0, 'abc')
    +        dialog.button_cancel.invoke()
    +        self.assertEqual(dialog.result, None)
    +        self.assertTrue(dialog.destroy.called)
    +
    +
    +class SectionnameGuiTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        requires('gui')
    +
    +    def test_click_section_name(self):
    +        root = Tk()
    +        root.withdraw()
    +        dialog =  query.SectionName(root, 'T', 't', {'abc'}, _utest=True)
    +        Equal = self.assertEqual
    +        self.assertEqual(dialog.used_names, {'abc'})
    +        dialog.entry.insert(0, 'okay')
    +        dialog.button_ok.invoke()
    +        self.assertEqual(dialog.result, 'okay')
    +        root.destroy()
    +
    +
    +class ModulenameGuiTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        requires('gui')
    +
    +    def test_click_module_name(self):
    +        root = Tk()
    +        root.withdraw()
    +        dialog =  query.ModuleName(root, 'T', 't', 'idlelib', _utest=True)
    +        self.assertEqual(dialog.text0, 'idlelib')
    +        self.assertEqual(dialog.entry.get(), 'idlelib')
    +        dialog.button_ok.invoke()
    +        self.assertTrue(dialog.result.endswith('__init__.py'))
    +        root.destroy()
    +
    +
    +class HelpsourceGuiTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        requires('gui')
    +
    +    def test_click_help_source(self):
    +        root = Tk()
    +        root.withdraw()
    +        dialog =  query.HelpSource(root, 'T', menuitem='__test__',
    +                                   filepath=__file__, _utest=True)
    +        Equal = self.assertEqual
    +        Equal(dialog.entry.get(), '__test__')
    +        Equal(dialog.path.get(), __file__)
    +        dialog.button_ok.invoke()
    +        prefix = "file://" if sys.platform == 'darwin' else ''
    +        Equal(dialog.result, ('__test__', prefix + __file__))
    +        root.destroy()
    +
    +
    +class CustomRunGuiTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        requires('gui')
    +
    +    def test_click_args(self):
    +        root = Tk()
    +        root.withdraw()
    +        dialog =  query.CustomRun(root, 'Title',
    +                                  cli_args=['a', 'b=1'], _utest=True)
    +        self.assertEqual(dialog.entry.get(), 'a b=1')
    +        dialog.entry.insert(END, ' c')
    +        dialog.button_ok.invoke()
    +        self.assertEqual(dialog.result, (['a', 'b=1', 'c'], True))
    +        root.destroy()
    +
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2, exit=False)
    diff --git a/PythonLib/full/idlelib/idle_test/test_redirector.py b/PythonLib/full/idlelib/idle_test/test_redirector.py
    new file mode 100644
    index 00000000..a97b3002
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_redirector.py
    @@ -0,0 +1,122 @@
    +"Test redirector, coverage 100%."
    +
    +from idlelib.redirector import WidgetRedirector
    +import unittest
    +from test.support import requires
    +from tkinter import Tk, Text, TclError
    +from idlelib.idle_test.mock_idle import Func
    +
    +
    +class InitCloseTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        requires('gui')
    +        cls.root = Tk()
    +        cls.root.withdraw()
    +        cls.text = Text(cls.root)
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        del cls.text
    +        cls.root.destroy()
    +        del cls.root
    +
    +    def test_init(self):
    +        redir = WidgetRedirector(self.text)
    +        self.assertEqual(redir.widget, self.text)
    +        self.assertEqual(redir.tk, self.text.tk)
    +        self.assertRaises(TclError, WidgetRedirector, self.text)
    +        redir.close()  # restore self.tk, self.text
    +
    +    def test_close(self):
    +        redir = WidgetRedirector(self.text)
    +        redir.register('insert', Func)
    +        redir.close()
    +        self.assertEqual(redir._operations, {})
    +        self.assertFalse(hasattr(self.text, 'widget'))
    +
    +
    +class WidgetRedirectorTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        requires('gui')
    +        cls.root = Tk()
    +        cls.root.withdraw()
    +        cls.text = Text(cls.root)
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        del cls.text
    +        cls.root.update_idletasks()
    +        cls.root.destroy()
    +        del cls.root
    +
    +    def setUp(self):
    +        self.redir = WidgetRedirector(self.text)
    +        self.func = Func()
    +        self.orig_insert = self.redir.register('insert', self.func)
    +        self.text.insert('insert', 'asdf')  # leaves self.text empty
    +
    +    def tearDown(self):
    +        self.text.delete('1.0', 'end')
    +        self.redir.close()
    +
    +    def test_repr(self):  # partly for 100% coverage
    +        self.assertIn('Redirector', repr(self.redir))
    +        self.assertIn('Original', repr(self.orig_insert))
    +
    +    def test_register(self):
    +        self.assertEqual(self.text.get('1.0', 'end'), '\n')
    +        self.assertEqual(self.func.args, ('insert', 'asdf'))
    +        self.assertIn('insert', self.redir._operations)
    +        self.assertIn('insert', self.text.__dict__)
    +        self.assertEqual(self.text.insert, self.func)
    +
    +    def test_original_command(self):
    +        self.assertEqual(self.orig_insert.operation, 'insert')
    +        self.assertEqual(self.orig_insert.tk_call, self.text.tk.call)
    +        self.orig_insert('insert', 'asdf')
    +        self.assertEqual(self.text.get('1.0', 'end'), 'asdf\n')
    +
    +    def test_unregister(self):
    +        self.assertIsNone(self.redir.unregister('invalid operation name'))
    +        self.assertEqual(self.redir.unregister('insert'), self.func)
    +        self.assertNotIn('insert', self.redir._operations)
    +        self.assertNotIn('insert', self.text.__dict__)
    +
    +    def test_unregister_no_attribute(self):
    +        del self.text.insert
    +        self.assertEqual(self.redir.unregister('insert'), self.func)
    +
    +    def test_dispatch_intercept(self):
    +        self.func.__init__(True)
    +        self.assertTrue(self.redir.dispatch('insert', False))
    +        self.assertFalse(self.func.args[0])
    +
    +    def test_dispatch_bypass(self):
    +        self.orig_insert('insert', 'asdf')
    +        # tk.call returns '' where Python would return None
    +        self.assertEqual(self.redir.dispatch('delete', '1.0', 'end'), '')
    +        self.assertEqual(self.text.get('1.0', 'end'), '\n')
    +
    +    def test_dispatch_error(self):
    +        self.func.__init__(TclError())
    +        self.assertEqual(self.redir.dispatch('insert', False), '')
    +        self.assertEqual(self.redir.dispatch('invalid'), '')
    +
    +    def test_command_dispatch(self):
    +        # Test that .__init__ causes redirection of tk calls
    +        # through redir.dispatch
    +        self.root.call(self.text._w, 'insert', 'hello')
    +        self.assertEqual(self.func.args, ('hello',))
    +        self.assertEqual(self.text.get('1.0', 'end'), '\n')
    +        # Ensure that called through redir .dispatch and not through
    +        # self.text.insert by having mock raise TclError.
    +        self.func.__init__(TclError())
    +        self.assertEqual(self.root.call(self.text._w, 'insert', 'boo'), '')
    +
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_replace.py b/PythonLib/full/idlelib/idle_test/test_replace.py
    new file mode 100644
    index 00000000..c3c5d2ee
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_replace.py
    @@ -0,0 +1,294 @@
    +"Test replace, coverage 78%."
    +
    +from idlelib.replace import ReplaceDialog
    +import unittest
    +from test.support import requires
    +requires('gui')
    +from tkinter import Tk, Text
    +
    +from unittest.mock import Mock
    +from idlelib.idle_test.mock_tk import Mbox
    +import idlelib.searchengine as se
    +
    +orig_mbox = se.tkMessageBox
    +showerror = Mbox.showerror
    +
    +
    +class ReplaceDialogTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        cls.root = Tk()
    +        cls.root.withdraw()
    +        se.tkMessageBox = Mbox
    +        cls.engine = se.SearchEngine(cls.root)
    +        cls.dialog = ReplaceDialog(cls.root, cls.engine)
    +        cls.dialog.bell = lambda: None
    +        cls.dialog.ok = Mock()
    +        cls.text = Text(cls.root)
    +        cls.text.undo_block_start = Mock()
    +        cls.text.undo_block_stop = Mock()
    +        cls.dialog.text = cls.text
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        se.tkMessageBox = orig_mbox
    +        del cls.text, cls.dialog, cls.engine
    +        cls.root.destroy()
    +        del cls.root
    +
    +    def setUp(self):
    +        self.text.insert('insert', 'This is a sample sTring')
    +
    +    def tearDown(self):
    +        self.engine.patvar.set('')
    +        self.dialog.replvar.set('')
    +        self.engine.wordvar.set(False)
    +        self.engine.casevar.set(False)
    +        self.engine.revar.set(False)
    +        self.engine.wrapvar.set(True)
    +        self.engine.backvar.set(False)
    +        showerror.title = ''
    +        showerror.message = ''
    +        self.text.delete('1.0', 'end')
    +
    +    def test_replace_simple(self):
    +        # Test replace function with all options at default setting.
    +        # Wrap around - True
    +        # Regular Expression - False
    +        # Match case - False
    +        # Match word - False
    +        # Direction - Forwards
    +        text = self.text
    +        equal = self.assertEqual
    +        pv = self.engine.patvar
    +        rv = self.dialog.replvar
    +        replace = self.dialog.replace_it
    +
    +        # test accessor method
    +        self.engine.setpat('asdf')
    +        equal(self.engine.getpat(), pv.get())
    +
    +        # text found and replaced
    +        pv.set('a')
    +        rv.set('asdf')
    +        replace()
    +        equal(text.get('1.8', '1.12'), 'asdf')
    +
    +        # don't "match word" case
    +        text.mark_set('insert', '1.0')
    +        pv.set('is')
    +        rv.set('hello')
    +        replace()
    +        equal(text.get('1.2', '1.7'), 'hello')
    +
    +        # don't "match case" case
    +        pv.set('string')
    +        rv.set('world')
    +        replace()
    +        equal(text.get('1.23', '1.28'), 'world')
    +
    +        # without "regular expression" case
    +        text.mark_set('insert', 'end')
    +        text.insert('insert', '\nline42:')
    +        before_text = text.get('1.0', 'end')
    +        pv.set(r'[a-z][\d]+')
    +        replace()
    +        after_text = text.get('1.0', 'end')
    +        equal(before_text, after_text)
    +
    +        # test with wrap around selected and complete a cycle
    +        text.mark_set('insert', '1.9')
    +        pv.set('i')
    +        rv.set('j')
    +        replace()
    +        equal(text.get('1.8'), 'i')
    +        equal(text.get('2.1'), 'j')
    +        replace()
    +        equal(text.get('2.1'), 'j')
    +        equal(text.get('1.8'), 'j')
    +        before_text = text.get('1.0', 'end')
    +        replace()
    +        after_text = text.get('1.0', 'end')
    +        equal(before_text, after_text)
    +
    +        # text not found
    +        before_text = text.get('1.0', 'end')
    +        pv.set('foobar')
    +        replace()
    +        after_text = text.get('1.0', 'end')
    +        equal(before_text, after_text)
    +
    +        # test access method
    +        self.dialog.find_it(0)
    +
    +    def test_replace_wrap_around(self):
    +        text = self.text
    +        equal = self.assertEqual
    +        pv = self.engine.patvar
    +        rv = self.dialog.replvar
    +        replace = self.dialog.replace_it
    +        self.engine.wrapvar.set(False)
    +
    +        # replace candidate found both after and before 'insert'
    +        text.mark_set('insert', '1.4')
    +        pv.set('i')
    +        rv.set('j')
    +        replace()
    +        equal(text.get('1.2'), 'i')
    +        equal(text.get('1.5'), 'j')
    +        replace()
    +        equal(text.get('1.2'), 'i')
    +        equal(text.get('1.20'), 'j')
    +        replace()
    +        equal(text.get('1.2'), 'i')
    +
    +        # replace candidate found only before 'insert'
    +        text.mark_set('insert', '1.8')
    +        pv.set('is')
    +        before_text = text.get('1.0', 'end')
    +        replace()
    +        after_text = text.get('1.0', 'end')
    +        equal(before_text, after_text)
    +
    +    def test_replace_whole_word(self):
    +        text = self.text
    +        equal = self.assertEqual
    +        pv = self.engine.patvar
    +        rv = self.dialog.replvar
    +        replace = self.dialog.replace_it
    +        self.engine.wordvar.set(True)
    +
    +        pv.set('is')
    +        rv.set('hello')
    +        replace()
    +        equal(text.get('1.0', '1.4'), 'This')
    +        equal(text.get('1.5', '1.10'), 'hello')
    +
    +    def test_replace_match_case(self):
    +        equal = self.assertEqual
    +        text = self.text
    +        pv = self.engine.patvar
    +        rv = self.dialog.replvar
    +        replace = self.dialog.replace_it
    +        self.engine.casevar.set(True)
    +
    +        before_text = self.text.get('1.0', 'end')
    +        pv.set('this')
    +        rv.set('that')
    +        replace()
    +        after_text = self.text.get('1.0', 'end')
    +        equal(before_text, after_text)
    +
    +        pv.set('This')
    +        replace()
    +        equal(text.get('1.0', '1.4'), 'that')
    +
    +    def test_replace_regex(self):
    +        equal = self.assertEqual
    +        text = self.text
    +        pv = self.engine.patvar
    +        rv = self.dialog.replvar
    +        replace = self.dialog.replace_it
    +        self.engine.revar.set(True)
    +
    +        before_text = text.get('1.0', 'end')
    +        pv.set(r'[a-z][\d]+')
    +        rv.set('hello')
    +        replace()
    +        after_text = text.get('1.0', 'end')
    +        equal(before_text, after_text)
    +
    +        text.insert('insert', '\nline42')
    +        replace()
    +        equal(text.get('2.0', '2.8'), 'linhello')
    +
    +        pv.set('')
    +        replace()
    +        self.assertIn('error', showerror.title)
    +        self.assertIn('Empty', showerror.message)
    +
    +        pv.set(r'[\d')
    +        replace()
    +        self.assertIn('error', showerror.title)
    +        self.assertIn('Pattern', showerror.message)
    +
    +        showerror.title = ''
    +        showerror.message = ''
    +        pv.set('[a]')
    +        rv.set('test\\')
    +        replace()
    +        self.assertIn('error', showerror.title)
    +        self.assertIn('Invalid Replace Expression', showerror.message)
    +
    +        # test access method
    +        self.engine.setcookedpat("?")
    +        equal(pv.get(), "\\?")
    +
    +    def test_replace_backwards(self):
    +        equal = self.assertEqual
    +        text = self.text
    +        pv = self.engine.patvar
    +        rv = self.dialog.replvar
    +        replace = self.dialog.replace_it
    +        self.engine.backvar.set(True)
    +
    +        text.insert('insert', '\nis as ')
    +
    +        pv.set('is')
    +        rv.set('was')
    +        replace()
    +        equal(text.get('1.2', '1.4'), 'is')
    +        equal(text.get('2.0', '2.3'), 'was')
    +        replace()
    +        equal(text.get('1.5', '1.8'), 'was')
    +        replace()
    +        equal(text.get('1.2', '1.5'), 'was')
    +
    +    def test_replace_all(self):
    +        text = self.text
    +        pv = self.engine.patvar
    +        rv = self.dialog.replvar
    +        replace_all = self.dialog.replace_all
    +
    +        text.insert('insert', '\n')
    +        text.insert('insert', text.get('1.0', 'end')*100)
    +        pv.set('is')
    +        rv.set('was')
    +        replace_all()
    +        self.assertNotIn('is', text.get('1.0', 'end'))
    +
    +        self.engine.revar.set(True)
    +        pv.set('')
    +        replace_all()
    +        self.assertIn('error', showerror.title)
    +        self.assertIn('Empty', showerror.message)
    +
    +        pv.set('[s][T]')
    +        rv.set('\\')
    +        replace_all()
    +
    +        self.engine.revar.set(False)
    +        pv.set('text which is not present')
    +        rv.set('foobar')
    +        replace_all()
    +
    +    def test_default_command(self):
    +        text = self.text
    +        pv = self.engine.patvar
    +        rv = self.dialog.replvar
    +        replace_find = self.dialog.default_command
    +        equal = self.assertEqual
    +
    +        pv.set('This')
    +        rv.set('was')
    +        replace_find()
    +        equal(text.get('sel.first', 'sel.last'), 'was')
    +
    +        self.engine.revar.set(True)
    +        pv.set('')
    +        replace_find()
    +
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_rpc.py b/PythonLib/full/idlelib/idle_test/test_rpc.py
    new file mode 100644
    index 00000000..81eff398
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_rpc.py
    @@ -0,0 +1,29 @@
    +"Test rpc, coverage 20%."
    +
    +from idlelib import rpc
    +import unittest
    +
    +
    +
    +class CodePicklerTest(unittest.TestCase):
    +
    +    def test_pickle_unpickle(self):
    +        def f(): return a + b + c
    +        func, (cbytes,) = rpc.pickle_code(f.__code__)
    +        self.assertIs(func, rpc.unpickle_code)
    +        self.assertIn(b'test_rpc.py', cbytes)
    +        code = rpc.unpickle_code(cbytes)
    +        self.assertEqual(code.co_names, ('a', 'b', 'c'))
    +
    +    def test_code_pickler(self):
    +        self.assertIn(type((lambda:None).__code__),
    +                      rpc.CodePickler.dispatch_table)
    +
    +    def test_dumps(self):
    +        def f(): pass
    +        # The main test here is that pickling code does not raise.
    +        self.assertIn(b'test_rpc.py', rpc.dumps(f.__code__))
    +
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_rstrip.py b/PythonLib/full/idlelib/idle_test/test_rstrip.py
    deleted file mode 100644
    index 1c90b93d..00000000
    --- a/PythonLib/full/idlelib/idle_test/test_rstrip.py
    +++ /dev/null
    @@ -1,49 +0,0 @@
    -import unittest
    -import idlelib.RstripExtension as rs
    -from idlelib.idle_test.mock_idle import Editor
    -
    -class rstripTest(unittest.TestCase):
    -
    -    def test_rstrip_line(self):
    -        editor = Editor()
    -        text = editor.text
    -        do_rstrip = rs.RstripExtension(editor).do_rstrip
    -
    -        do_rstrip()
    -        self.assertEqual(text.get('1.0', 'insert'), '')
    -        text.insert('1.0', '     ')
    -        do_rstrip()
    -        self.assertEqual(text.get('1.0', 'insert'), '')
    -        text.insert('1.0', '     \n')
    -        do_rstrip()
    -        self.assertEqual(text.get('1.0', 'insert'), '\n')
    -
    -    def test_rstrip_multiple(self):
    -        editor = Editor()
    -        #  Uncomment following to verify that test passes with real widgets.
    -##        from idlelib.EditorWindow import EditorWindow as Editor
    -##        from tkinter import Tk
    -##        editor = Editor(root=Tk())
    -        text = editor.text
    -        do_rstrip = rs.RstripExtension(editor).do_rstrip
    -
    -        original = (
    -            "Line with an ending tab    \n"
    -            "Line ending in 5 spaces     \n"
    -            "Linewithnospaces\n"
    -            "    indented line\n"
    -            "    indented line with trailing space \n"
    -            "    ")
    -        stripped = (
    -            "Line with an ending tab\n"
    -            "Line ending in 5 spaces\n"
    -            "Linewithnospaces\n"
    -            "    indented line\n"
    -            "    indented line with trailing space\n")
    -
    -        text.insert('1.0', original)
    -        do_rstrip()
    -        self.assertEqual(text.get('1.0', 'insert'), stripped)
    -
    -if __name__ == '__main__':
    -    unittest.main(verbosity=2, exit=False)
    diff --git a/PythonLib/full/idlelib/idle_test/test_run.py b/PythonLib/full/idlelib/idle_test/test_run.py
    new file mode 100644
    index 00000000..9995dbe2
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_run.py
    @@ -0,0 +1,325 @@
    +"Test run, coverage 42%."
    +
    +from idlelib import run
    +import unittest
    +from unittest import mock
    +from test.support import captured_stderr
    +
    +import io
    +import sys
    +
    +
    +class RunTest(unittest.TestCase):
    +
    +    def test_print_exception_unhashable(self):
    +        class UnhashableException(Exception):
    +            def __eq__(self, other):
    +                return True
    +
    +        ex1 = UnhashableException('ex1')
    +        ex2 = UnhashableException('ex2')
    +        try:
    +            raise ex2 from ex1
    +        except UnhashableException:
    +            try:
    +                raise ex1
    +            except UnhashableException:
    +                with captured_stderr() as output:
    +                    with mock.patch.object(run,
    +                                           'cleanup_traceback') as ct:
    +                        ct.side_effect = lambda t, e: t
    +                        run.print_exception()
    +
    +        tb = output.getvalue().strip().splitlines()
    +        self.assertEqual(11, len(tb))
    +        self.assertIn('UnhashableException: ex2', tb[3])
    +        self.assertIn('UnhashableException: ex1', tb[10])
    +
    +
    +# StdioFile tests.
    +
    +class S(str):
    +    def __str__(self):
    +        return '%s:str' % type(self).__name__
    +    def __unicode__(self):
    +        return '%s:unicode' % type(self).__name__
    +    def __len__(self):
    +        return 3
    +    def __iter__(self):
    +        return iter('abc')
    +    def __getitem__(self, *args):
    +        return '%s:item' % type(self).__name__
    +    def __getslice__(self, *args):
    +        return '%s:slice' % type(self).__name__
    +
    +
    +class MockShell:
    +    def __init__(self):
    +        self.reset()
    +    def write(self, *args):
    +        self.written.append(args)
    +    def readline(self):
    +        return self.lines.pop()
    +    def close(self):
    +        pass
    +    def reset(self):
    +        self.written = []
    +    def push(self, lines):
    +        self.lines = list(lines)[::-1]
    +
    +
    +class StdInputFilesTest(unittest.TestCase):
    +
    +    def test_misc(self):
    +        shell = MockShell()
    +        f = run.StdInputFile(shell, 'stdin')
    +        self.assertIsInstance(f, io.TextIOBase)
    +        self.assertEqual(f.encoding, 'utf-8')
    +        self.assertEqual(f.errors, 'strict')
    +        self.assertIsNone(f.newlines)
    +        self.assertEqual(f.name, '')
    +        self.assertFalse(f.closed)
    +        self.assertTrue(f.isatty())
    +        self.assertTrue(f.readable())
    +        self.assertFalse(f.writable())
    +        self.assertFalse(f.seekable())
    +
    +    def test_unsupported(self):
    +        shell = MockShell()
    +        f = run.StdInputFile(shell, 'stdin')
    +        self.assertRaises(OSError, f.fileno)
    +        self.assertRaises(OSError, f.tell)
    +        self.assertRaises(OSError, f.seek, 0)
    +        self.assertRaises(OSError, f.write, 'x')
    +        self.assertRaises(OSError, f.writelines, ['x'])
    +
    +    def test_read(self):
    +        shell = MockShell()
    +        f = run.StdInputFile(shell, 'stdin')
    +        shell.push(['one\n', 'two\n', ''])
    +        self.assertEqual(f.read(), 'one\ntwo\n')
    +        shell.push(['one\n', 'two\n', ''])
    +        self.assertEqual(f.read(-1), 'one\ntwo\n')
    +        shell.push(['one\n', 'two\n', ''])
    +        self.assertEqual(f.read(None), 'one\ntwo\n')
    +        shell.push(['one\n', 'two\n', 'three\n', ''])
    +        self.assertEqual(f.read(2), 'on')
    +        self.assertEqual(f.read(3), 'e\nt')
    +        self.assertEqual(f.read(10), 'wo\nthree\n')
    +
    +        shell.push(['one\n', 'two\n'])
    +        self.assertEqual(f.read(0), '')
    +        self.assertRaises(TypeError, f.read, 1.5)
    +        self.assertRaises(TypeError, f.read, '1')
    +        self.assertRaises(TypeError, f.read, 1, 1)
    +
    +    def test_readline(self):
    +        shell = MockShell()
    +        f = run.StdInputFile(shell, 'stdin')
    +        shell.push(['one\n', 'two\n', 'three\n', 'four\n'])
    +        self.assertEqual(f.readline(), 'one\n')
    +        self.assertEqual(f.readline(-1), 'two\n')
    +        self.assertEqual(f.readline(None), 'three\n')
    +        shell.push(['one\ntwo\n'])
    +        self.assertEqual(f.readline(), 'one\n')
    +        self.assertEqual(f.readline(), 'two\n')
    +        shell.push(['one', 'two', 'three'])
    +        self.assertEqual(f.readline(), 'one')
    +        self.assertEqual(f.readline(), 'two')
    +        shell.push(['one\n', 'two\n', 'three\n'])
    +        self.assertEqual(f.readline(2), 'on')
    +        self.assertEqual(f.readline(1), 'e')
    +        self.assertEqual(f.readline(1), '\n')
    +        self.assertEqual(f.readline(10), 'two\n')
    +
    +        shell.push(['one\n', 'two\n'])
    +        self.assertEqual(f.readline(0), '')
    +        self.assertRaises(TypeError, f.readlines, 1.5)
    +        self.assertRaises(TypeError, f.readlines, '1')
    +        self.assertRaises(TypeError, f.readlines, 1, 1)
    +
    +    def test_readlines(self):
    +        shell = MockShell()
    +        f = run.StdInputFile(shell, 'stdin')
    +        shell.push(['one\n', 'two\n', ''])
    +        self.assertEqual(f.readlines(), ['one\n', 'two\n'])
    +        shell.push(['one\n', 'two\n', ''])
    +        self.assertEqual(f.readlines(-1), ['one\n', 'two\n'])
    +        shell.push(['one\n', 'two\n', ''])
    +        self.assertEqual(f.readlines(None), ['one\n', 'two\n'])
    +        shell.push(['one\n', 'two\n', ''])
    +        self.assertEqual(f.readlines(0), ['one\n', 'two\n'])
    +        shell.push(['one\n', 'two\n', ''])
    +        self.assertEqual(f.readlines(3), ['one\n'])
    +        shell.push(['one\n', 'two\n', ''])
    +        self.assertEqual(f.readlines(4), ['one\n', 'two\n'])
    +
    +        shell.push(['one\n', 'two\n', ''])
    +        self.assertRaises(TypeError, f.readlines, 1.5)
    +        self.assertRaises(TypeError, f.readlines, '1')
    +        self.assertRaises(TypeError, f.readlines, 1, 1)
    +
    +    def test_close(self):
    +        shell = MockShell()
    +        f = run.StdInputFile(shell, 'stdin')
    +        shell.push(['one\n', 'two\n', ''])
    +        self.assertFalse(f.closed)
    +        self.assertEqual(f.readline(), 'one\n')
    +        f.close()
    +        self.assertFalse(f.closed)
    +        self.assertEqual(f.readline(), 'two\n')
    +        self.assertRaises(TypeError, f.close, 1)
    +
    +
    +class StdOutputFilesTest(unittest.TestCase):
    +
    +    def test_misc(self):
    +        shell = MockShell()
    +        f = run.StdOutputFile(shell, 'stdout')
    +        self.assertIsInstance(f, io.TextIOBase)
    +        self.assertEqual(f.encoding, 'utf-8')
    +        self.assertEqual(f.errors, 'strict')
    +        self.assertIsNone(f.newlines)
    +        self.assertEqual(f.name, '')
    +        self.assertFalse(f.closed)
    +        self.assertTrue(f.isatty())
    +        self.assertFalse(f.readable())
    +        self.assertTrue(f.writable())
    +        self.assertFalse(f.seekable())
    +
    +    def test_unsupported(self):
    +        shell = MockShell()
    +        f = run.StdOutputFile(shell, 'stdout')
    +        self.assertRaises(OSError, f.fileno)
    +        self.assertRaises(OSError, f.tell)
    +        self.assertRaises(OSError, f.seek, 0)
    +        self.assertRaises(OSError, f.read, 0)
    +        self.assertRaises(OSError, f.readline, 0)
    +
    +    def test_write(self):
    +        shell = MockShell()
    +        f = run.StdOutputFile(shell, 'stdout')
    +        f.write('test')
    +        self.assertEqual(shell.written, [('test', 'stdout')])
    +        shell.reset()
    +        f.write('t\xe8\u015b\U0001d599')
    +        self.assertEqual(shell.written, [('t\xe8\u015b\U0001d599', 'stdout')])
    +        shell.reset()
    +
    +        f.write(S('t\xe8\u015b\U0001d599'))
    +        self.assertEqual(shell.written, [('t\xe8\u015b\U0001d599', 'stdout')])
    +        self.assertEqual(type(shell.written[0][0]), str)
    +        shell.reset()
    +
    +        self.assertRaises(TypeError, f.write)
    +        self.assertEqual(shell.written, [])
    +        self.assertRaises(TypeError, f.write, b'test')
    +        self.assertRaises(TypeError, f.write, 123)
    +        self.assertEqual(shell.written, [])
    +        self.assertRaises(TypeError, f.write, 'test', 'spam')
    +        self.assertEqual(shell.written, [])
    +
    +    def test_write_stderr_nonencodable(self):
    +        shell = MockShell()
    +        f = run.StdOutputFile(shell, 'stderr', 'iso-8859-15', 'backslashreplace')
    +        f.write('t\xe8\u015b\U0001d599\xa4')
    +        self.assertEqual(shell.written, [('t\xe8\\u015b\\U0001d599\\xa4', 'stderr')])
    +        shell.reset()
    +
    +        f.write(S('t\xe8\u015b\U0001d599\xa4'))
    +        self.assertEqual(shell.written, [('t\xe8\\u015b\\U0001d599\\xa4', 'stderr')])
    +        self.assertEqual(type(shell.written[0][0]), str)
    +        shell.reset()
    +
    +        self.assertRaises(TypeError, f.write)
    +        self.assertEqual(shell.written, [])
    +        self.assertRaises(TypeError, f.write, b'test')
    +        self.assertRaises(TypeError, f.write, 123)
    +        self.assertEqual(shell.written, [])
    +        self.assertRaises(TypeError, f.write, 'test', 'spam')
    +        self.assertEqual(shell.written, [])
    +
    +    def test_writelines(self):
    +        shell = MockShell()
    +        f = run.StdOutputFile(shell, 'stdout')
    +        f.writelines([])
    +        self.assertEqual(shell.written, [])
    +        shell.reset()
    +        f.writelines(['one\n', 'two'])
    +        self.assertEqual(shell.written,
    +                         [('one\n', 'stdout'), ('two', 'stdout')])
    +        shell.reset()
    +        f.writelines(['on\xe8\n', 'tw\xf2'])
    +        self.assertEqual(shell.written,
    +                         [('on\xe8\n', 'stdout'), ('tw\xf2', 'stdout')])
    +        shell.reset()
    +
    +        f.writelines([S('t\xe8st')])
    +        self.assertEqual(shell.written, [('t\xe8st', 'stdout')])
    +        self.assertEqual(type(shell.written[0][0]), str)
    +        shell.reset()
    +
    +        self.assertRaises(TypeError, f.writelines)
    +        self.assertEqual(shell.written, [])
    +        self.assertRaises(TypeError, f.writelines, 123)
    +        self.assertEqual(shell.written, [])
    +        self.assertRaises(TypeError, f.writelines, [b'test'])
    +        self.assertRaises(TypeError, f.writelines, [123])
    +        self.assertEqual(shell.written, [])
    +        self.assertRaises(TypeError, f.writelines, [], [])
    +        self.assertEqual(shell.written, [])
    +
    +    def test_close(self):
    +        shell = MockShell()
    +        f = run.StdOutputFile(shell, 'stdout')
    +        self.assertFalse(f.closed)
    +        f.write('test')
    +        f.close()
    +        self.assertTrue(f.closed)
    +        self.assertRaises(ValueError, f.write, 'x')
    +        self.assertEqual(shell.written, [('test', 'stdout')])
    +        f.close()
    +        self.assertRaises(TypeError, f.close, 1)
    +
    +
    +class TestSysRecursionLimitWrappers(unittest.TestCase):
    +
    +    def test_bad_setrecursionlimit_calls(self):
    +        run.install_recursionlimit_wrappers()
    +        self.addCleanup(run.uninstall_recursionlimit_wrappers)
    +        f = sys.setrecursionlimit
    +        self.assertRaises(TypeError, f, limit=100)
    +        self.assertRaises(TypeError, f, 100, 1000)
    +        self.assertRaises(ValueError, f, 0)
    +
    +    def test_roundtrip(self):
    +        run.install_recursionlimit_wrappers()
    +        self.addCleanup(run.uninstall_recursionlimit_wrappers)
    +
    +        # check that setting the recursion limit works
    +        orig_reclimit = sys.getrecursionlimit()
    +        self.addCleanup(sys.setrecursionlimit, orig_reclimit)
    +        sys.setrecursionlimit(orig_reclimit + 3)
    +
    +        # check that the new limit is returned by sys.getrecursionlimit()
    +        new_reclimit = sys.getrecursionlimit()
    +        self.assertEqual(new_reclimit, orig_reclimit + 3)
    +
    +    def test_default_recursion_limit_preserved(self):
    +        orig_reclimit = sys.getrecursionlimit()
    +        run.install_recursionlimit_wrappers()
    +        self.addCleanup(run.uninstall_recursionlimit_wrappers)
    +        new_reclimit = sys.getrecursionlimit()
    +        self.assertEqual(new_reclimit, orig_reclimit)
    +
    +    def test_fixdoc(self):
    +        def func(): "docstring"
    +        run.fixdoc(func, "more")
    +        self.assertEqual(func.__doc__, "docstring\n\nmore")
    +        func.__doc__ = None
    +        run.fixdoc(func, "more")
    +        self.assertEqual(func.__doc__, "more")
    +
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_runscript.py b/PythonLib/full/idlelib/idle_test/test_runscript.py
    new file mode 100644
    index 00000000..5fc60185
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_runscript.py
    @@ -0,0 +1,33 @@
    +"Test runscript, coverage 16%."
    +
    +from idlelib import runscript
    +import unittest
    +from test.support import requires
    +from tkinter import Tk
    +from idlelib.editor import EditorWindow
    +
    +
    +class ScriptBindingTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        requires('gui')
    +        cls.root = Tk()
    +        cls.root.withdraw()
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        cls.root.update_idletasks()
    +        for id in cls.root.tk.call('after', 'info'):
    +            cls.root.after_cancel(id)  # Need for EditorWindow.
    +        cls.root.destroy()
    +        del cls.root
    +
    +    def test_init(self):
    +        ew = EditorWindow(root=self.root)
    +        sb = runscript.ScriptBinding(ew)
    +        ew._close()
    +
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_scrolledlist.py b/PythonLib/full/idlelib/idle_test/test_scrolledlist.py
    new file mode 100644
    index 00000000..2f819fda
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_scrolledlist.py
    @@ -0,0 +1,27 @@
    +"Test scrolledlist, coverage 38%."
    +
    +from idlelib.scrolledlist import ScrolledList
    +import unittest
    +from test.support import requires
    +requires('gui')
    +from tkinter import Tk
    +
    +
    +class ScrolledListTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        cls.root = Tk()
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        cls.root.destroy()
    +        del cls.root
    +
    +
    +    def test_init(self):
    +        ScrolledList(self.root)
    +
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_search.py b/PythonLib/full/idlelib/idle_test/test_search.py
    new file mode 100644
    index 00000000..de703c19
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_search.py
    @@ -0,0 +1,80 @@
    +"Test search, coverage 69%."
    +
    +from idlelib import search
    +import unittest
    +from test.support import requires
    +requires('gui')
    +from tkinter import Tk, Text, BooleanVar
    +from idlelib import searchengine
    +
    +# Does not currently test the event handler wrappers.
    +# A usage test should simulate clicks and check highlighting.
    +# Tests need to be coordinated with SearchDialogBase tests
    +# to avoid duplication.
    +
    +
    +class SearchDialogTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        cls.root = Tk()
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        cls.root.destroy()
    +        del cls.root
    +
    +    def setUp(self):
    +        self.engine = searchengine.SearchEngine(self.root)
    +        self.dialog = search.SearchDialog(self.root, self.engine)
    +        self.dialog.bell = lambda: None
    +        self.text = Text(self.root)
    +        self.text.insert('1.0', 'Hello World!')
    +
    +    def test_find_again(self):
    +        # Search for various expressions
    +        text = self.text
    +
    +        self.engine.setpat('')
    +        self.assertFalse(self.dialog.find_again(text))
    +        self.dialog.bell = lambda: None
    +
    +        self.engine.setpat('Hello')
    +        self.assertTrue(self.dialog.find_again(text))
    +
    +        self.engine.setpat('Goodbye')
    +        self.assertFalse(self.dialog.find_again(text))
    +
    +        self.engine.setpat('World!')
    +        self.assertTrue(self.dialog.find_again(text))
    +
    +        self.engine.setpat('Hello World!')
    +        self.assertTrue(self.dialog.find_again(text))
    +
    +        # Regular expression
    +        self.engine.revar = BooleanVar(self.root, True)
    +        self.engine.setpat('W[aeiouy]r')
    +        self.assertTrue(self.dialog.find_again(text))
    +
    +    def test_find_selection(self):
    +        # Select some text and make sure it's found
    +        text = self.text
    +        # Add additional line to find
    +        self.text.insert('2.0', 'Hello World!')
    +
    +        text.tag_add('sel', '1.0', '1.4')       # Select 'Hello'
    +        self.assertTrue(self.dialog.find_selection(text))
    +
    +        text.tag_remove('sel', '1.0', 'end')
    +        text.tag_add('sel', '1.6', '1.11')      # Select 'World!'
    +        self.assertTrue(self.dialog.find_selection(text))
    +
    +        text.tag_remove('sel', '1.0', 'end')
    +        text.tag_add('sel', '1.0', '1.11')      # Select 'Hello World!'
    +        self.assertTrue(self.dialog.find_selection(text))
    +
    +        # Remove additional line
    +        text.delete('2.0', 'end')
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2, exit=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_searchbase.py b/PythonLib/full/idlelib/idle_test/test_searchbase.py
    new file mode 100644
    index 00000000..aee0c4c6
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_searchbase.py
    @@ -0,0 +1,160 @@
    +"Test searchbase, coverage 98%."
    +# The only thing not covered is inconsequential --
    +# testing skipping of suite when self.needwrapbutton is false.
    +
    +import unittest
    +from test.support import requires
    +from tkinter import Text, Tk, Toplevel
    +from tkinter.ttk import Frame
    +from idlelib import searchengine as se
    +from idlelib import searchbase as sdb
    +from idlelib.idle_test.mock_idle import Func
    +## from idlelib.idle_test.mock_tk import Var
    +
    +# The ## imports above & following could help make some tests gui-free.
    +# However, they currently make radiobutton tests fail.
    +##def setUpModule():
    +##    # Replace tk objects used to initialize se.SearchEngine.
    +##    se.BooleanVar = Var
    +##    se.StringVar = Var
    +##
    +##def tearDownModule():
    +##    se.BooleanVar = BooleanVar
    +##    se.StringVar = StringVar
    +
    +
    +class SearchDialogBaseTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        requires('gui')
    +        cls.root = Tk()
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        cls.root.update_idletasks()
    +        cls.root.destroy()
    +        del cls.root
    +
    +    def setUp(self):
    +        self.engine = se.SearchEngine(self.root)  # None also seems to work
    +        self.dialog = sdb.SearchDialogBase(root=self.root, engine=self.engine)
    +
    +    def tearDown(self):
    +        self.dialog.close()
    +
    +    def test_open_and_close(self):
    +        # open calls create_widgets, which needs default_command
    +        self.dialog.default_command = None
    +
    +        toplevel = Toplevel(self.root)
    +        text = Text(toplevel)
    +        self.dialog.open(text)
    +        self.assertEqual(self.dialog.top.state(), 'normal')
    +        self.dialog.close()
    +        self.assertEqual(self.dialog.top.state(), 'withdrawn')
    +
    +        self.dialog.open(text, searchphrase="hello")
    +        self.assertEqual(self.dialog.ent.get(), 'hello')
    +        toplevel.update_idletasks()
    +        toplevel.destroy()
    +
    +    def test_create_widgets(self):
    +        self.dialog.create_entries = Func()
    +        self.dialog.create_option_buttons = Func()
    +        self.dialog.create_other_buttons = Func()
    +        self.dialog.create_command_buttons = Func()
    +
    +        self.dialog.default_command = None
    +        self.dialog.create_widgets()
    +
    +        self.assertTrue(self.dialog.create_entries.called)
    +        self.assertTrue(self.dialog.create_option_buttons.called)
    +        self.assertTrue(self.dialog.create_other_buttons.called)
    +        self.assertTrue(self.dialog.create_command_buttons.called)
    +
    +    def test_make_entry(self):
    +        equal = self.assertEqual
    +        self.dialog.row = 0
    +        self.dialog.top = self.root
    +        entry, label = self.dialog.make_entry("Test:", 'hello')
    +        equal(label['text'], 'Test:')
    +
    +        self.assertIn(entry.get(), 'hello')
    +        egi = entry.grid_info()
    +        equal(int(egi['row']), 0)
    +        equal(int(egi['column']), 1)
    +        equal(int(egi['rowspan']), 1)
    +        equal(int(egi['columnspan']), 1)
    +        equal(self.dialog.row, 1)
    +
    +    def test_create_entries(self):
    +        self.dialog.top = self.root
    +        self.dialog.row = 0
    +        self.engine.setpat('hello')
    +        self.dialog.create_entries()
    +        self.assertIn(self.dialog.ent.get(), 'hello')
    +
    +    def test_make_frame(self):
    +        self.dialog.row = 0
    +        self.dialog.top = self.root
    +        frame, label = self.dialog.make_frame()
    +        self.assertEqual(label, '')
    +        self.assertEqual(str(type(frame)), "")
    +        # self.assertIsInstance(frame, Frame) fails when test is run by
    +        # test_idle not run from IDLE editor.  See issue 33987 PR.
    +
    +        frame, label = self.dialog.make_frame('testlabel')
    +        self.assertEqual(label['text'], 'testlabel')
    +
    +    def btn_test_setup(self, meth):
    +        self.dialog.top = self.root
    +        self.dialog.row = 0
    +        return meth()
    +
    +    def test_create_option_buttons(self):
    +        e = self.engine
    +        for state in (0, 1):
    +            for var in (e.revar, e.casevar, e.wordvar, e.wrapvar):
    +                var.set(state)
    +            frame, options = self.btn_test_setup(
    +                    self.dialog.create_option_buttons)
    +            for spec, button in zip (options, frame.pack_slaves()):
    +                var, label = spec
    +                self.assertEqual(button['text'], label)
    +                self.assertEqual(var.get(), state)
    +
    +    def test_create_other_buttons(self):
    +        for state in (False, True):
    +            var = self.engine.backvar
    +            var.set(state)
    +            frame, others = self.btn_test_setup(
    +                self.dialog.create_other_buttons)
    +            buttons = frame.pack_slaves()
    +            for spec, button in zip(others, buttons):
    +                val, label = spec
    +                self.assertEqual(button['text'], label)
    +                if val == state:
    +                    # hit other button, then this one
    +                    # indexes depend on button order
    +                    self.assertEqual(var.get(), state)
    +
    +    def test_make_button(self):
    +        self.dialog.top = self.root
    +        self.dialog.buttonframe = Frame(self.dialog.top)
    +        btn = self.dialog.make_button('Test', self.dialog.close)
    +        self.assertEqual(btn['text'], 'Test')
    +
    +    def test_create_command_buttons(self):
    +        self.dialog.top = self.root
    +        self.dialog.create_command_buttons()
    +        # Look for close button command in buttonframe
    +        closebuttoncommand = ''
    +        for child in self.dialog.buttonframe.winfo_children():
    +            if child['text'] == 'Close':
    +                closebuttoncommand = child['command']
    +        self.assertIn('close', closebuttoncommand)
    +
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2, exit=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_searchdialogbase.py b/PythonLib/full/idlelib/idle_test/test_searchdialogbase.py
    deleted file mode 100644
    index 59b9bbf3..00000000
    --- a/PythonLib/full/idlelib/idle_test/test_searchdialogbase.py
    +++ /dev/null
    @@ -1,165 +0,0 @@
    -'''Unittests for idlelib/SearchDialogBase.py
    -
    -Coverage: 99%. The only thing not covered is inconsequential --
    -testing skipping of suite when self.needwrapbutton is false.
    -
    -'''
    -import unittest
    -from test.test_support import requires
    -from Tkinter import Text, Tk, Toplevel, Frame ## BooleanVar, StringVar
    -from idlelib import SearchEngine as se
    -from idlelib import SearchDialogBase as sdb
    -from idlelib.idle_test.mock_idle import Func
    -##from idlelib.idle_test.mock_tk import Var
    -
    -# The ## imports above & following could help make some tests gui-free.# However, they currently make radiobutton tests fail.
    -##def setUpModule():
    -##    # Replace tk objects used to initialize se.SearchEngine.
    -##    se.BooleanVar = Var
    -##    se.StringVar = Var
    -##
    -##def tearDownModule():
    -##    se.BooleanVar = BooleanVar
    -##    se.StringVar = StringVar
    -
    -class SearchDialogBaseTest(unittest.TestCase):
    -
    -    @classmethod
    -    def setUpClass(cls):
    -        requires('gui')
    -        cls.root = Tk()
    -
    -    @classmethod
    -    def tearDownClass(cls):
    -        cls.root.destroy()
    -        del cls.root
    -
    -    def setUp(self):
    -        self.engine = se.SearchEngine(self.root)  # None also seems to work
    -        self.dialog = sdb.SearchDialogBase(root=self.root, engine=self.engine)
    -
    -    def tearDown(self):
    -        self.dialog.close()
    -
    -    def test_open_and_close(self):
    -        # open calls create_widgets, which needs default_command
    -        self.dialog.default_command = None
    -
    -        toplevel = Toplevel(self.root)
    -        text = Text(toplevel)
    -        self.dialog.open(text)
    -        self.assertEqual(self.dialog.top.state(), 'normal')
    -        self.dialog.close()
    -        self.assertEqual(self.dialog.top.state(), 'withdrawn')
    -
    -        self.dialog.open(text, searchphrase="hello")
    -        self.assertEqual(self.dialog.ent.get(), 'hello')
    -        toplevel.update_idletasks()
    -        toplevel.destroy()
    -
    -    def test_create_widgets(self):
    -        self.dialog.create_entries = Func()
    -        self.dialog.create_option_buttons = Func()
    -        self.dialog.create_other_buttons = Func()
    -        self.dialog.create_command_buttons = Func()
    -
    -        self.dialog.default_command = None
    -        self.dialog.create_widgets()
    -
    -        self.assertTrue(self.dialog.create_entries.called)
    -        self.assertTrue(self.dialog.create_option_buttons.called)
    -        self.assertTrue(self.dialog.create_other_buttons.called)
    -        self.assertTrue(self.dialog.create_command_buttons.called)
    -
    -    def test_make_entry(self):
    -        equal = self.assertEqual
    -        self.dialog.row = 0
    -        self.dialog.top = Toplevel(self.root)
    -        entry, label = self.dialog.make_entry("Test:", 'hello')
    -        equal(label['text'], 'Test:')
    -
    -        self.assertIn(entry.get(), 'hello')
    -        egi = entry.grid_info()
    -        equal(int(egi['row']), 0)
    -        equal(int(egi['column']), 1)
    -        equal(int(egi['rowspan']), 1)
    -        equal(int(egi['columnspan']), 1)
    -        equal(self.dialog.row, 1)
    -
    -    def test_create_entries(self):
    -        self.dialog.row = 0
    -        self.engine.setpat('hello')
    -        self.dialog.create_entries()
    -        self.assertIn(self.dialog.ent.get(), 'hello')
    -
    -    def test_make_frame(self):
    -        self.dialog.row = 0
    -        self.dialog.top = Toplevel(self.root)
    -        frame, label = self.dialog.make_frame()
    -        self.assertEqual(label, '')
    -        self.assertIsInstance(frame, Frame)
    -
    -        frame, label = self.dialog.make_frame('testlabel')
    -        self.assertEqual(label['text'], 'testlabel')
    -        self.assertIsInstance(frame, Frame)
    -
    -    def btn_test_setup(self, meth):
    -        self.dialog.top = Toplevel(self.root)
    -        self.dialog.row = 0
    -        return meth()
    -
    -    def test_create_option_buttons(self):
    -        e = self.engine
    -        for state in (0, 1):
    -            for var in (e.revar, e.casevar, e.wordvar, e.wrapvar):
    -                var.set(state)
    -            frame, options = self.btn_test_setup(
    -                    self.dialog.create_option_buttons)
    -            for spec, button in zip (options, frame.pack_slaves()):
    -                var, label = spec
    -                self.assertEqual(button['text'], label)
    -                self.assertEqual(var.get(), state)
    -                if state == 1:
    -                    button.deselect()
    -                else:
    -                    button.select()
    -                self.assertEqual(var.get(), 1 - state)
    -
    -    def test_create_other_buttons(self):
    -        for state in (False, True):
    -            var = self.engine.backvar
    -            var.set(state)
    -            frame, others = self.btn_test_setup(
    -                self.dialog.create_other_buttons)
    -            buttons = frame.pack_slaves()
    -            for spec, button in zip(others, buttons):
    -                val, label = spec
    -                self.assertEqual(button['text'], label)
    -                if val == state:
    -                    # hit other button, then this one
    -                    # indexes depend on button order
    -                    self.assertEqual(var.get(), state)
    -                    buttons[val].select()
    -                    self.assertEqual(var.get(), 1 - state)
    -                    buttons[1-val].select()
    -                    self.assertEqual(var.get(), state)
    -
    -    def test_make_button(self):
    -        self.dialog.top = Toplevel(self.root)
    -        self.dialog.buttonframe = Frame(self.dialog.top)
    -        btn = self.dialog.make_button('Test', self.dialog.close)
    -        self.assertEqual(btn['text'], 'Test')
    -
    -    def test_create_command_buttons(self):
    -        self.dialog.create_command_buttons()
    -        # Look for close button command in buttonframe
    -        closebuttoncommand = ''
    -        for child in self.dialog.buttonframe.winfo_children():
    -            if child['text'] == 'close':
    -                closebuttoncommand = child['command']
    -        self.assertIn('close', closebuttoncommand)
    -
    -
    -
    -if __name__ == '__main__':
    -    unittest.main(verbosity=2, exit=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_searchengine.py b/PythonLib/full/idlelib/idle_test/test_searchengine.py
    index 8bf9d472..3d26d62a 100644
    --- a/PythonLib/full/idlelib/idle_test/test_searchengine.py
    +++ b/PythonLib/full/idlelib/idle_test/test_searchengine.py
    @@ -1,18 +1,19 @@
    -'''Test functions and SearchEngine class in SearchEngine.py.'''
    +"Test searchengine, coverage 99%."
    +
    +from idlelib import searchengine as se
    +import unittest
    +# from test.support import requires
    +from tkinter import  BooleanVar, StringVar, TclError  # ,Tk, Text
    +import tkinter.messagebox as tkMessageBox
    +from idlelib.idle_test.mock_tk import Var, Mbox
    +from idlelib.idle_test.mock_tk import Text as mockText
    +import re
     
     # With mock replacements, the module does not use any gui widgets.
     # The use of tk.Text is avoided (for now, until mock Text is improved)
     # by patching instances with an index function returning what is needed.
     # This works because mock Text.get does not use .index.
    -
    -import re
    -import unittest
    -#from test.test_support import requires
    -from Tkinter import  BooleanVar, StringVar, TclError  # ,Tk, Text
    -import tkMessageBox
    -from idlelib import SearchEngine as se
    -from idlelib.idle_test.mock_tk import Var, Mbox
    -from idlelib.idle_test.mock_tk import Text as mockText
    +# The tkinter imports are used to restore searchengine.
     
     def setUpModule():
         # Replace s-e module tkinter imports other than non-gui TclError.
    @@ -139,10 +140,10 @@ def test_is_get(self):
     
         def test_setcookedpat(self):
             engine = self.engine
    -        engine.setcookedpat('\s')
    -        self.assertEqual(engine.getpat(), '\s')
    +        engine.setcookedpat(r'\s')
    +        self.assertEqual(engine.getpat(), r'\s')
             engine.revar.set(1)
    -        engine.setcookedpat('\s')
    +        engine.setcookedpat(r'\s')
             self.assertEqual(engine.getpat(), r'\\s')
     
         def test_getcookedpat(self):
    @@ -156,10 +157,10 @@ def test_getcookedpat(self):
             Equal(engine.getcookedpat(), r'\bhello\b')
             engine.wordvar.set(False)
     
    -        engine.setpat('\s')
    +        engine.setpat(r'\s')
             Equal(engine.getcookedpat(), r'\\s')
             engine.revar.set(True)
    -        Equal(engine.getcookedpat(), '\s')
    +        Equal(engine.getcookedpat(), r'\s')
     
         def test_getprog(self):
             engine = self.engine
    @@ -178,7 +179,7 @@ def test_getprog(self):
             engine.revar.set(1)
             Equal(engine.getprog(), None)
             self.assertEqual(Mbox.showerror.message,
    -                          'Error: nothing to repeat\nPattern: +')
    +                         'Error: nothing to repeat at position 0\nPattern: +')
     
         def test_report_error(self):
             showerror = Mbox.showerror
    @@ -282,7 +283,7 @@ def setUpClass(cls):
             cls.pat = re.compile('target')
             cls.res = (2, (10, 16))  # line, slice indexes of 'target'
             cls.failpat = re.compile('xyz')  # not in text
    -        cls.emptypat = re.compile('\w*')  # empty match possible
    +        cls.emptypat = re.compile(r'\w*')  # empty match possible
     
         def make_search(self, func):
             def search(pat, line, col, wrap, ok=0):
    @@ -326,4 +327,4 @@ def test_search_backward(self):
     
     
     if __name__ == '__main__':
    -    unittest.main(verbosity=2, exit=2)
    +    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_sidebar.py b/PythonLib/full/idlelib/idle_test/test_sidebar.py
    new file mode 100644
    index 00000000..0f5b4c71
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_sidebar.py
    @@ -0,0 +1,375 @@
    +"""Test sidebar, coverage 93%"""
    +import idlelib.sidebar
    +from sys import platform
    +from itertools import chain
    +import unittest
    +import unittest.mock
    +from test.support import requires
    +import tkinter as tk
    +
    +from idlelib.delegator import Delegator
    +from idlelib.percolator import Percolator
    +
    +
    +class Dummy_editwin:
    +    def __init__(self, text):
    +        self.text = text
    +        self.text_frame = self.text.master
    +        self.per = Percolator(text)
    +        self.undo = Delegator()
    +        self.per.insertfilter(self.undo)
    +
    +    def setvar(self, name, value):
    +        pass
    +
    +    def getlineno(self, index):
    +        return int(float(self.text.index(index)))
    +
    +
    +class LineNumbersTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        requires('gui')
    +        cls.root = tk.Tk()
    +
    +        cls.text_frame = tk.Frame(cls.root)
    +        cls.text_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
    +        cls.text_frame.rowconfigure(1, weight=1)
    +        cls.text_frame.columnconfigure(1, weight=1)
    +
    +        cls.text = tk.Text(cls.text_frame, width=80, height=24, wrap=tk.NONE)
    +        cls.text.grid(row=1, column=1, sticky=tk.NSEW)
    +
    +        cls.editwin = Dummy_editwin(cls.text)
    +        cls.editwin.vbar = tk.Scrollbar(cls.text_frame)
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        cls.editwin.per.close()
    +        cls.root.update()
    +        cls.root.destroy()
    +        del cls.text, cls.text_frame, cls.editwin, cls.root
    +
    +    def setUp(self):
    +        self.linenumber = idlelib.sidebar.LineNumbers(self.editwin)
    +
    +        self.highlight_cfg = {"background": '#abcdef',
    +                              "foreground": '#123456'}
    +        orig_idleConf_GetHighlight = idlelib.sidebar.idleConf.GetHighlight
    +        def mock_idleconf_GetHighlight(theme, element):
    +            if element == 'linenumber':
    +                return self.highlight_cfg
    +            return orig_idleConf_GetHighlight(theme, element)
    +        GetHighlight_patcher = unittest.mock.patch.object(
    +            idlelib.sidebar.idleConf, 'GetHighlight', mock_idleconf_GetHighlight)
    +        GetHighlight_patcher.start()
    +        self.addCleanup(GetHighlight_patcher.stop)
    +
    +        self.font_override = 'TkFixedFont'
    +        def mock_idleconf_GetFont(root, configType, section):
    +            return self.font_override
    +        GetFont_patcher = unittest.mock.patch.object(
    +            idlelib.sidebar.idleConf, 'GetFont', mock_idleconf_GetFont)
    +        GetFont_patcher.start()
    +        self.addCleanup(GetFont_patcher.stop)
    +
    +    def tearDown(self):
    +        self.text.delete('1.0', 'end')
    +
    +    def get_selection(self):
    +        return tuple(map(str, self.text.tag_ranges('sel')))
    +
    +    def get_line_screen_position(self, line):
    +        bbox = self.linenumber.sidebar_text.bbox(f'{line}.end -1c')
    +        x = bbox[0] + 2
    +        y = bbox[1] + 2
    +        return x, y
    +
    +    def assert_state_disabled(self):
    +        state = self.linenumber.sidebar_text.config()['state']
    +        self.assertEqual(state[-1], tk.DISABLED)
    +
    +    def get_sidebar_text_contents(self):
    +        return self.linenumber.sidebar_text.get('1.0', tk.END)
    +
    +    def assert_sidebar_n_lines(self, n_lines):
    +        expected = '\n'.join(chain(map(str, range(1, n_lines + 1)), ['']))
    +        self.assertEqual(self.get_sidebar_text_contents(), expected)
    +
    +    def assert_text_equals(self, expected):
    +        return self.assertEqual(self.text.get('1.0', 'end'), expected)
    +
    +    def test_init_empty(self):
    +        self.assert_sidebar_n_lines(1)
    +
    +    def test_init_not_empty(self):
    +        self.text.insert('insert', 'foo bar\n'*3)
    +        self.assert_text_equals('foo bar\n'*3 + '\n')
    +        self.assert_sidebar_n_lines(4)
    +
    +    def test_toggle_linenumbering(self):
    +        self.assertEqual(self.linenumber.is_shown, False)
    +        self.linenumber.show_sidebar()
    +        self.assertEqual(self.linenumber.is_shown, True)
    +        self.linenumber.hide_sidebar()
    +        self.assertEqual(self.linenumber.is_shown, False)
    +        self.linenumber.hide_sidebar()
    +        self.assertEqual(self.linenumber.is_shown, False)
    +        self.linenumber.show_sidebar()
    +        self.assertEqual(self.linenumber.is_shown, True)
    +        self.linenumber.show_sidebar()
    +        self.assertEqual(self.linenumber.is_shown, True)
    +
    +    def test_insert(self):
    +        self.text.insert('insert', 'foobar')
    +        self.assert_text_equals('foobar\n')
    +        self.assert_sidebar_n_lines(1)
    +        self.assert_state_disabled()
    +
    +        self.text.insert('insert', '\nfoo')
    +        self.assert_text_equals('foobar\nfoo\n')
    +        self.assert_sidebar_n_lines(2)
    +        self.assert_state_disabled()
    +
    +        self.text.insert('insert', 'hello\n'*2)
    +        self.assert_text_equals('foobar\nfoohello\nhello\n\n')
    +        self.assert_sidebar_n_lines(4)
    +        self.assert_state_disabled()
    +
    +        self.text.insert('insert', '\nworld')
    +        self.assert_text_equals('foobar\nfoohello\nhello\n\nworld\n')
    +        self.assert_sidebar_n_lines(5)
    +        self.assert_state_disabled()
    +
    +    def test_delete(self):
    +        self.text.insert('insert', 'foobar')
    +        self.assert_text_equals('foobar\n')
    +        self.text.delete('1.1', '1.3')
    +        self.assert_text_equals('fbar\n')
    +        self.assert_sidebar_n_lines(1)
    +        self.assert_state_disabled()
    +
    +        self.text.insert('insert', 'foo\n'*2)
    +        self.assert_text_equals('fbarfoo\nfoo\n\n')
    +        self.assert_sidebar_n_lines(3)
    +        self.assert_state_disabled()
    +
    +        # Note: deleting up to "2.end" doesn't delete the final newline.
    +        self.text.delete('2.0', '2.end')
    +        self.assert_text_equals('fbarfoo\n\n\n')
    +        self.assert_sidebar_n_lines(3)
    +        self.assert_state_disabled()
    +
    +        self.text.delete('1.3', 'end')
    +        self.assert_text_equals('fba\n')
    +        self.assert_sidebar_n_lines(1)
    +        self.assert_state_disabled()
    +
    +        # Note: Text widgets always keep a single '\n' character at the end.
    +        self.text.delete('1.0', 'end')
    +        self.assert_text_equals('\n')
    +        self.assert_sidebar_n_lines(1)
    +        self.assert_state_disabled()
    +
    +    def test_sidebar_text_width(self):
    +        """
    +        Test that linenumber text widget is always at the minimum
    +        width
    +        """
    +        def get_width():
    +            return self.linenumber.sidebar_text.config()['width'][-1]
    +
    +        self.assert_sidebar_n_lines(1)
    +        self.assertEqual(get_width(), 1)
    +
    +        self.text.insert('insert', 'foo')
    +        self.assert_sidebar_n_lines(1)
    +        self.assertEqual(get_width(), 1)
    +
    +        self.text.insert('insert', 'foo\n'*8)
    +        self.assert_sidebar_n_lines(9)
    +        self.assertEqual(get_width(), 1)
    +
    +        self.text.insert('insert', 'foo\n')
    +        self.assert_sidebar_n_lines(10)
    +        self.assertEqual(get_width(), 2)
    +
    +        self.text.insert('insert', 'foo\n')
    +        self.assert_sidebar_n_lines(11)
    +        self.assertEqual(get_width(), 2)
    +
    +        self.text.delete('insert -1l linestart', 'insert linestart')
    +        self.assert_sidebar_n_lines(10)
    +        self.assertEqual(get_width(), 2)
    +
    +        self.text.delete('insert -1l linestart', 'insert linestart')
    +        self.assert_sidebar_n_lines(9)
    +        self.assertEqual(get_width(), 1)
    +
    +        self.text.insert('insert', 'foo\n'*90)
    +        self.assert_sidebar_n_lines(99)
    +        self.assertEqual(get_width(), 2)
    +
    +        self.text.insert('insert', 'foo\n')
    +        self.assert_sidebar_n_lines(100)
    +        self.assertEqual(get_width(), 3)
    +
    +        self.text.insert('insert', 'foo\n')
    +        self.assert_sidebar_n_lines(101)
    +        self.assertEqual(get_width(), 3)
    +
    +        self.text.delete('insert -1l linestart', 'insert linestart')
    +        self.assert_sidebar_n_lines(100)
    +        self.assertEqual(get_width(), 3)
    +
    +        self.text.delete('insert -1l linestart', 'insert linestart')
    +        self.assert_sidebar_n_lines(99)
    +        self.assertEqual(get_width(), 2)
    +
    +        self.text.delete('50.0 -1c', 'end -1c')
    +        self.assert_sidebar_n_lines(49)
    +        self.assertEqual(get_width(), 2)
    +
    +        self.text.delete('5.0 -1c', 'end -1c')
    +        self.assert_sidebar_n_lines(4)
    +        self.assertEqual(get_width(), 1)
    +
    +        # Note: Text widgets always keep a single '\n' character at the end.
    +        self.text.delete('1.0', 'end -1c')
    +        self.assert_sidebar_n_lines(1)
    +        self.assertEqual(get_width(), 1)
    +
    +    def test_click_selection(self):
    +        self.linenumber.show_sidebar()
    +        self.text.insert('1.0', 'one\ntwo\nthree\nfour\n')
    +        self.root.update()
    +
    +        # Click on the second line.
    +        x, y = self.get_line_screen_position(2)
    +        self.linenumber.sidebar_text.event_generate('', x=x, y=y)
    +        self.linenumber.sidebar_text.update()
    +        self.root.update()
    +
    +        self.assertEqual(self.get_selection(), ('2.0', '3.0'))
    +
    +    def simulate_drag(self, start_line, end_line):
    +        start_x, start_y = self.get_line_screen_position(start_line)
    +        end_x, end_y = self.get_line_screen_position(end_line)
    +
    +        self.linenumber.sidebar_text.event_generate('',
    +                                                    x=start_x, y=start_y)
    +        self.root.update()
    +
    +        def lerp(a, b, steps):
    +            """linearly interpolate from a to b (inclusive) in equal steps"""
    +            last_step = steps - 1
    +            for i in range(steps):
    +                yield ((last_step - i) / last_step) * a + (i / last_step) * b
    +
    +        for x, y in zip(
    +                map(int, lerp(start_x, end_x, steps=11)),
    +                map(int, lerp(start_y, end_y, steps=11)),
    +        ):
    +            self.linenumber.sidebar_text.event_generate('', x=x, y=y)
    +            self.root.update()
    +
    +        self.linenumber.sidebar_text.event_generate('',
    +                                                    x=end_x, y=end_y)
    +        self.root.update()
    +
    +    def test_drag_selection_down(self):
    +        self.linenumber.show_sidebar()
    +        self.text.insert('1.0', 'one\ntwo\nthree\nfour\nfive\n')
    +        self.root.update()
    +
    +        # Drag from the second line to the fourth line.
    +        self.simulate_drag(2, 4)
    +        self.assertEqual(self.get_selection(), ('2.0', '5.0'))
    +
    +    def test_drag_selection_up(self):
    +        self.linenumber.show_sidebar()
    +        self.text.insert('1.0', 'one\ntwo\nthree\nfour\nfive\n')
    +        self.root.update()
    +
    +        # Drag from the fourth line to the second line.
    +        self.simulate_drag(4, 2)
    +        self.assertEqual(self.get_selection(), ('2.0', '5.0'))
    +
    +    def test_scroll(self):
    +        self.linenumber.show_sidebar()
    +        self.text.insert('1.0', 'line\n' * 100)
    +        self.root.update()
    +
    +        # Scroll down 10 lines.
    +        self.text.yview_scroll(10, 'unit')
    +        self.root.update()
    +        self.assertEqual(self.text.index('@0,0'), '11.0')
    +        self.assertEqual(self.linenumber.sidebar_text.index('@0,0'), '11.0')
    +
    +        # Generate a mouse-wheel event and make sure it scrolled up or down.
    +        # The meaning of the "delta" is OS-dependant, so this just checks for
    +        # any change.
    +        self.linenumber.sidebar_text.event_generate('',
    +                                                    x=0, y=0,
    +                                                    delta=10)
    +        self.root.update()
    +        self.assertNotEqual(self.text.index('@0,0'), '11.0')
    +        self.assertNotEqual(self.linenumber.sidebar_text.index('@0,0'), '11.0')
    +
    +    def test_font(self):
    +        ln = self.linenumber
    +
    +        orig_font = ln.sidebar_text['font']
    +        test_font = 'TkTextFont'
    +        self.assertNotEqual(orig_font, test_font)
    +
    +        # Ensure line numbers aren't shown.
    +        ln.hide_sidebar()
    +
    +        self.font_override = test_font
    +        # Nothing breaks when line numbers aren't shown.
    +        ln.update_font()
    +
    +        # Activate line numbers, previous font change is immediately effective.
    +        ln.show_sidebar()
    +        self.assertEqual(ln.sidebar_text['font'], test_font)
    +
    +        # Call the font update with line numbers shown, change is picked up.
    +        self.font_override = orig_font
    +        ln.update_font()
    +        self.assertEqual(ln.sidebar_text['font'], orig_font)
    +
    +    def test_highlight_colors(self):
    +        ln = self.linenumber
    +
    +        orig_colors = dict(self.highlight_cfg)
    +        test_colors = {'background': '#222222', 'foreground': '#ffff00'}
    +
    +        def assert_colors_are_equal(colors):
    +            self.assertEqual(ln.sidebar_text['background'], colors['background'])
    +            self.assertEqual(ln.sidebar_text['foreground'], colors['foreground'])
    +
    +        # Ensure line numbers aren't shown.
    +        ln.hide_sidebar()
    +
    +        self.highlight_cfg = test_colors
    +        # Nothing breaks with inactive code context.
    +        ln.update_colors()
    +
    +        # Show line numbers, previous colors change is immediately effective.
    +        ln.show_sidebar()
    +        assert_colors_are_equal(test_colors)
    +
    +        # Call colors update with no change to the configured colors.
    +        ln.update_colors()
    +        assert_colors_are_equal(test_colors)
    +
    +        # Call the colors update with line numbers shown, change is picked up.
    +        self.highlight_cfg = orig_colors
    +        ln.update_colors()
    +        assert_colors_are_equal(orig_colors)
    +
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_squeezer.py b/PythonLib/full/idlelib/idle_test/test_squeezer.py
    new file mode 100644
    index 00000000..1af2ce83
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_squeezer.py
    @@ -0,0 +1,476 @@
    +"Test squeezer, coverage 95%"
    +
    +from collections import namedtuple
    +from textwrap import dedent
    +from tkinter import Text, Tk
    +import unittest
    +from unittest.mock import Mock, NonCallableMagicMock, patch, sentinel, ANY
    +from test.support import requires
    +
    +from idlelib.config import idleConf
    +from idlelib.squeezer import count_lines_with_wrapping, ExpandingButton, \
    +    Squeezer
    +from idlelib import macosx
    +from idlelib.textview import view_text
    +from idlelib.tooltip import Hovertip
    +from idlelib.pyshell import PyShell
    +
    +
    +SENTINEL_VALUE = sentinel.SENTINEL_VALUE
    +
    +
    +def get_test_tk_root(test_instance):
    +    """Helper for tests: Create a root Tk object."""
    +    requires('gui')
    +    root = Tk()
    +    root.withdraw()
    +
    +    def cleanup_root():
    +        root.update_idletasks()
    +        root.destroy()
    +    test_instance.addCleanup(cleanup_root)
    +
    +    return root
    +
    +
    +class CountLinesTest(unittest.TestCase):
    +    """Tests for the count_lines_with_wrapping function."""
    +    def check(self, expected, text, linewidth):
    +        return self.assertEqual(
    +            expected,
    +            count_lines_with_wrapping(text, linewidth),
    +        )
    +
    +    def test_count_empty(self):
    +        """Test with an empty string."""
    +        self.assertEqual(count_lines_with_wrapping(""), 0)
    +
    +    def test_count_begins_with_empty_line(self):
    +        """Test with a string which begins with a newline."""
    +        self.assertEqual(count_lines_with_wrapping("\ntext"), 2)
    +
    +    def test_count_ends_with_empty_line(self):
    +        """Test with a string which ends with a newline."""
    +        self.assertEqual(count_lines_with_wrapping("text\n"), 1)
    +
    +    def test_count_several_lines(self):
    +        """Test with several lines of text."""
    +        self.assertEqual(count_lines_with_wrapping("1\n2\n3\n"), 3)
    +
    +    def test_empty_lines(self):
    +        self.check(expected=1, text='\n', linewidth=80)
    +        self.check(expected=2, text='\n\n', linewidth=80)
    +        self.check(expected=10, text='\n' * 10, linewidth=80)
    +
    +    def test_long_line(self):
    +        self.check(expected=3, text='a' * 200, linewidth=80)
    +        self.check(expected=3, text='a' * 200 + '\n', linewidth=80)
    +
    +    def test_several_lines_different_lengths(self):
    +        text = dedent("""\
    +            13 characters
    +            43 is the number of characters on this line
    +
    +            7 chars
    +            13 characters""")
    +        self.check(expected=5, text=text, linewidth=80)
    +        self.check(expected=5, text=text + '\n', linewidth=80)
    +        self.check(expected=6, text=text, linewidth=40)
    +        self.check(expected=7, text=text, linewidth=20)
    +        self.check(expected=11, text=text, linewidth=10)
    +
    +
    +class SqueezerTest(unittest.TestCase):
    +    """Tests for the Squeezer class."""
    +    def make_mock_editor_window(self, with_text_widget=False):
    +        """Create a mock EditorWindow instance."""
    +        editwin = NonCallableMagicMock()
    +        editwin.width = 80
    +
    +        if with_text_widget:
    +            editwin.root = get_test_tk_root(self)
    +            text_widget = self.make_text_widget(root=editwin.root)
    +            editwin.text = editwin.per.bottom = text_widget
    +
    +        return editwin
    +
    +    def make_squeezer_instance(self, editor_window=None):
    +        """Create an actual Squeezer instance with a mock EditorWindow."""
    +        if editor_window is None:
    +            editor_window = self.make_mock_editor_window()
    +        squeezer = Squeezer(editor_window)
    +        return squeezer
    +
    +    def make_text_widget(self, root=None):
    +        if root is None:
    +            root = get_test_tk_root(self)
    +        text_widget = Text(root)
    +        text_widget["font"] = ('Courier', 10)
    +        text_widget.mark_set("iomark", "1.0")
    +        return text_widget
    +
    +    def set_idleconf_option_with_cleanup(self, configType, section, option, value):
    +        prev_val = idleConf.GetOption(configType, section, option)
    +        idleConf.SetOption(configType, section, option, value)
    +        self.addCleanup(idleConf.SetOption,
    +                        configType, section, option, prev_val)
    +
    +    def test_count_lines(self):
    +        """Test Squeezer.count_lines() with various inputs."""
    +        editwin = self.make_mock_editor_window()
    +        squeezer = self.make_squeezer_instance(editwin)
    +
    +        for text_code, line_width, expected in [
    +            (r"'\n'", 80, 1),
    +            (r"'\n' * 3", 80, 3),
    +            (r"'a' * 40 + '\n'", 80, 1),
    +            (r"'a' * 80 + '\n'", 80, 1),
    +            (r"'a' * 200 + '\n'", 80, 3),
    +            (r"'aa\t' * 20", 80, 2),
    +            (r"'aa\t' * 21", 80, 3),
    +            (r"'aa\t' * 20", 40, 4),
    +        ]:
    +            with self.subTest(text_code=text_code,
    +                              line_width=line_width,
    +                              expected=expected):
    +                text = eval(text_code)
    +                with patch.object(editwin, 'width', line_width):
    +                    self.assertEqual(squeezer.count_lines(text), expected)
    +
    +    def test_init(self):
    +        """Test the creation of Squeezer instances."""
    +        editwin = self.make_mock_editor_window()
    +        squeezer = self.make_squeezer_instance(editwin)
    +        self.assertIs(squeezer.editwin, editwin)
    +        self.assertEqual(squeezer.expandingbuttons, [])
    +
    +    def test_write_no_tags(self):
    +        """Test Squeezer's overriding of the EditorWindow's write() method."""
    +        editwin = self.make_mock_editor_window()
    +        for text in ['', 'TEXT', 'LONG TEXT' * 1000, 'MANY_LINES\n' * 100]:
    +            editwin.write = orig_write = Mock(return_value=SENTINEL_VALUE)
    +            squeezer = self.make_squeezer_instance(editwin)
    +
    +            self.assertEqual(squeezer.editwin.write(text, ()), SENTINEL_VALUE)
    +            self.assertEqual(orig_write.call_count, 1)
    +            orig_write.assert_called_with(text, ())
    +            self.assertEqual(len(squeezer.expandingbuttons), 0)
    +
    +    def test_write_not_stdout(self):
    +        """Test Squeezer's overriding of the EditorWindow's write() method."""
    +        for text in ['', 'TEXT', 'LONG TEXT' * 1000, 'MANY_LINES\n' * 100]:
    +            editwin = self.make_mock_editor_window()
    +            editwin.write.return_value = SENTINEL_VALUE
    +            orig_write = editwin.write
    +            squeezer = self.make_squeezer_instance(editwin)
    +
    +            self.assertEqual(squeezer.editwin.write(text, "stderr"),
    +                              SENTINEL_VALUE)
    +            self.assertEqual(orig_write.call_count, 1)
    +            orig_write.assert_called_with(text, "stderr")
    +            self.assertEqual(len(squeezer.expandingbuttons), 0)
    +
    +    def test_write_stdout(self):
    +        """Test Squeezer's overriding of the EditorWindow's write() method."""
    +        editwin = self.make_mock_editor_window()
    +
    +        for text in ['', 'TEXT']:
    +            editwin.write = orig_write = Mock(return_value=SENTINEL_VALUE)
    +            squeezer = self.make_squeezer_instance(editwin)
    +            squeezer.auto_squeeze_min_lines = 50
    +
    +            self.assertEqual(squeezer.editwin.write(text, "stdout"),
    +                             SENTINEL_VALUE)
    +            self.assertEqual(orig_write.call_count, 1)
    +            orig_write.assert_called_with(text, "stdout")
    +            self.assertEqual(len(squeezer.expandingbuttons), 0)
    +
    +        for text in ['LONG TEXT' * 1000, 'MANY_LINES\n' * 100]:
    +            editwin.write = orig_write = Mock(return_value=SENTINEL_VALUE)
    +            squeezer = self.make_squeezer_instance(editwin)
    +            squeezer.auto_squeeze_min_lines = 50
    +
    +            self.assertEqual(squeezer.editwin.write(text, "stdout"), None)
    +            self.assertEqual(orig_write.call_count, 0)
    +            self.assertEqual(len(squeezer.expandingbuttons), 1)
    +
    +    def test_auto_squeeze(self):
    +        """Test that the auto-squeezing creates an ExpandingButton properly."""
    +        editwin = self.make_mock_editor_window(with_text_widget=True)
    +        text_widget = editwin.text
    +        squeezer = self.make_squeezer_instance(editwin)
    +        squeezer.auto_squeeze_min_lines = 5
    +        squeezer.count_lines = Mock(return_value=6)
    +
    +        editwin.write('TEXT\n'*6, "stdout")
    +        self.assertEqual(text_widget.get('1.0', 'end'), '\n')
    +        self.assertEqual(len(squeezer.expandingbuttons), 1)
    +
    +    def test_squeeze_current_text_event(self):
    +        """Test the squeeze_current_text event."""
    +        # Squeezing text should work for both stdout and stderr.
    +        for tag_name in ["stdout", "stderr"]:
    +            editwin = self.make_mock_editor_window(with_text_widget=True)
    +            text_widget = editwin.text
    +            squeezer = self.make_squeezer_instance(editwin)
    +            squeezer.count_lines = Mock(return_value=6)
    +
    +            # Prepare some text in the Text widget.
    +            text_widget.insert("1.0", "SOME\nTEXT\n", tag_name)
    +            text_widget.mark_set("insert", "1.0")
    +            self.assertEqual(text_widget.get('1.0', 'end'), 'SOME\nTEXT\n\n')
    +
    +            self.assertEqual(len(squeezer.expandingbuttons), 0)
    +
    +            # Test squeezing the current text.
    +            retval = squeezer.squeeze_current_text_event(event=Mock())
    +            self.assertEqual(retval, "break")
    +            self.assertEqual(text_widget.get('1.0', 'end'), '\n\n')
    +            self.assertEqual(len(squeezer.expandingbuttons), 1)
    +            self.assertEqual(squeezer.expandingbuttons[0].s, 'SOME\nTEXT')
    +
    +            # Test that expanding the squeezed text works and afterwards
    +            # the Text widget contains the original text.
    +            squeezer.expandingbuttons[0].expand(event=Mock())
    +            self.assertEqual(text_widget.get('1.0', 'end'), 'SOME\nTEXT\n\n')
    +            self.assertEqual(len(squeezer.expandingbuttons), 0)
    +
    +    def test_squeeze_current_text_event_no_allowed_tags(self):
    +        """Test that the event doesn't squeeze text without a relevant tag."""
    +        editwin = self.make_mock_editor_window(with_text_widget=True)
    +        text_widget = editwin.text
    +        squeezer = self.make_squeezer_instance(editwin)
    +        squeezer.count_lines = Mock(return_value=6)
    +
    +        # Prepare some text in the Text widget.
    +        text_widget.insert("1.0", "SOME\nTEXT\n", "TAG")
    +        text_widget.mark_set("insert", "1.0")
    +        self.assertEqual(text_widget.get('1.0', 'end'), 'SOME\nTEXT\n\n')
    +
    +        self.assertEqual(len(squeezer.expandingbuttons), 0)
    +
    +        # Test squeezing the current text.
    +        retval = squeezer.squeeze_current_text_event(event=Mock())
    +        self.assertEqual(retval, "break")
    +        self.assertEqual(text_widget.get('1.0', 'end'), 'SOME\nTEXT\n\n')
    +        self.assertEqual(len(squeezer.expandingbuttons), 0)
    +
    +    def test_squeeze_text_before_existing_squeezed_text(self):
    +        """Test squeezing text before existing squeezed text."""
    +        editwin = self.make_mock_editor_window(with_text_widget=True)
    +        text_widget = editwin.text
    +        squeezer = self.make_squeezer_instance(editwin)
    +        squeezer.count_lines = Mock(return_value=6)
    +
    +        # Prepare some text in the Text widget and squeeze it.
    +        text_widget.insert("1.0", "SOME\nTEXT\n", "stdout")
    +        text_widget.mark_set("insert", "1.0")
    +        squeezer.squeeze_current_text_event(event=Mock())
    +        self.assertEqual(len(squeezer.expandingbuttons), 1)
    +
    +        # Test squeezing the current text.
    +        text_widget.insert("1.0", "MORE\nSTUFF\n", "stdout")
    +        text_widget.mark_set("insert", "1.0")
    +        retval = squeezer.squeeze_current_text_event(event=Mock())
    +        self.assertEqual(retval, "break")
    +        self.assertEqual(text_widget.get('1.0', 'end'), '\n\n\n')
    +        self.assertEqual(len(squeezer.expandingbuttons), 2)
    +        self.assertTrue(text_widget.compare(
    +            squeezer.expandingbuttons[0],
    +            '<',
    +            squeezer.expandingbuttons[1],
    +        ))
    +
    +    def test_reload(self):
    +        """Test the reload() class-method."""
    +        editwin = self.make_mock_editor_window(with_text_widget=True)
    +        squeezer = self.make_squeezer_instance(editwin)
    +
    +        orig_auto_squeeze_min_lines = squeezer.auto_squeeze_min_lines
    +
    +        # Increase auto-squeeze-min-lines.
    +        new_auto_squeeze_min_lines = orig_auto_squeeze_min_lines + 10
    +        self.set_idleconf_option_with_cleanup(
    +            'main', 'PyShell', 'auto-squeeze-min-lines',
    +            str(new_auto_squeeze_min_lines))
    +
    +        Squeezer.reload()
    +        self.assertEqual(squeezer.auto_squeeze_min_lines,
    +                         new_auto_squeeze_min_lines)
    +
    +    def test_reload_no_squeezer_instances(self):
    +        """Test that Squeezer.reload() runs without any instances existing."""
    +        Squeezer.reload()
    +
    +
    +class ExpandingButtonTest(unittest.TestCase):
    +    """Tests for the ExpandingButton class."""
    +    # In these tests the squeezer instance is a mock, but actual tkinter
    +    # Text and Button instances are created.
    +    def make_mock_squeezer(self):
    +        """Helper for tests: Create a mock Squeezer object."""
    +        root = get_test_tk_root(self)
    +        squeezer = Mock()
    +        squeezer.editwin.text = Text(root)
    +
    +        # Set default values for the configuration settings.
    +        squeezer.auto_squeeze_min_lines = 50
    +        return squeezer
    +
    +    @patch('idlelib.squeezer.Hovertip', autospec=Hovertip)
    +    def test_init(self, MockHovertip):
    +        """Test the simplest creation of an ExpandingButton."""
    +        squeezer = self.make_mock_squeezer()
    +        text_widget = squeezer.editwin.text
    +
    +        expandingbutton = ExpandingButton('TEXT', 'TAGS', 50, squeezer)
    +        self.assertEqual(expandingbutton.s, 'TEXT')
    +
    +        # Check that the underlying tkinter.Button is properly configured.
    +        self.assertEqual(expandingbutton.master, text_widget)
    +        self.assertTrue('50 lines' in expandingbutton.cget('text'))
    +
    +        # Check that the text widget still contains no text.
    +        self.assertEqual(text_widget.get('1.0', 'end'), '\n')
    +
    +        # Check that the mouse events are bound.
    +        self.assertIn('', expandingbutton.bind())
    +        right_button_code = '' % ('2' if macosx.isAquaTk() else '3')
    +        self.assertIn(right_button_code, expandingbutton.bind())
    +
    +        # Check that ToolTip was called once, with appropriate values.
    +        self.assertEqual(MockHovertip.call_count, 1)
    +        MockHovertip.assert_called_with(expandingbutton, ANY, hover_delay=ANY)
    +
    +        # Check that 'right-click' appears in the tooltip text.
    +        tooltip_text = MockHovertip.call_args[0][1]
    +        self.assertIn('right-click', tooltip_text.lower())
    +
    +    def test_expand(self):
    +        """Test the expand event."""
    +        squeezer = self.make_mock_squeezer()
    +        expandingbutton = ExpandingButton('TEXT', 'TAGS', 50, squeezer)
    +
    +        # Insert the button into the text widget
    +        # (this is normally done by the Squeezer class).
    +        text_widget = expandingbutton.text
    +        text_widget.window_create("1.0", window=expandingbutton)
    +
    +        # Set base_text to the text widget, so that changes are actually
    +        # made to it (by ExpandingButton) and we can inspect these
    +        # changes afterwards.
    +        expandingbutton.base_text = expandingbutton.text
    +
    +        # trigger the expand event
    +        retval = expandingbutton.expand(event=Mock())
    +        self.assertEqual(retval, None)
    +
    +        # Check that the text was inserted into the text widget.
    +        self.assertEqual(text_widget.get('1.0', 'end'), 'TEXT\n')
    +
    +        # Check that the 'TAGS' tag was set on the inserted text.
    +        text_end_index = text_widget.index('end-1c')
    +        self.assertEqual(text_widget.get('1.0', text_end_index), 'TEXT')
    +        self.assertEqual(text_widget.tag_nextrange('TAGS', '1.0'),
    +                          ('1.0', text_end_index))
    +
    +        # Check that the button removed itself from squeezer.expandingbuttons.
    +        self.assertEqual(squeezer.expandingbuttons.remove.call_count, 1)
    +        squeezer.expandingbuttons.remove.assert_called_with(expandingbutton)
    +
    +    def test_expand_dangerous_oupput(self):
    +        """Test that expanding very long output asks user for confirmation."""
    +        squeezer = self.make_mock_squeezer()
    +        text = 'a' * 10**5
    +        expandingbutton = ExpandingButton(text, 'TAGS', 50, squeezer)
    +        expandingbutton.set_is_dangerous()
    +        self.assertTrue(expandingbutton.is_dangerous)
    +
    +        # Insert the button into the text widget
    +        # (this is normally done by the Squeezer class).
    +        text_widget = expandingbutton.text
    +        text_widget.window_create("1.0", window=expandingbutton)
    +
    +        # Set base_text to the text widget, so that changes are actually
    +        # made to it (by ExpandingButton) and we can inspect these
    +        # changes afterwards.
    +        expandingbutton.base_text = expandingbutton.text
    +
    +        # Patch the message box module to always return False.
    +        with patch('idlelib.squeezer.tkMessageBox') as mock_msgbox:
    +            mock_msgbox.askokcancel.return_value = False
    +            mock_msgbox.askyesno.return_value = False
    +            # Trigger the expand event.
    +            retval = expandingbutton.expand(event=Mock())
    +
    +        # Check that the event chain was broken and no text was inserted.
    +        self.assertEqual(retval, 'break')
    +        self.assertEqual(expandingbutton.text.get('1.0', 'end-1c'), '')
    +
    +        # Patch the message box module to always return True.
    +        with patch('idlelib.squeezer.tkMessageBox') as mock_msgbox:
    +            mock_msgbox.askokcancel.return_value = True
    +            mock_msgbox.askyesno.return_value = True
    +            # Trigger the expand event.
    +            retval = expandingbutton.expand(event=Mock())
    +
    +        # Check that the event chain wasn't broken and the text was inserted.
    +        self.assertEqual(retval, None)
    +        self.assertEqual(expandingbutton.text.get('1.0', 'end-1c'), text)
    +
    +    def test_copy(self):
    +        """Test the copy event."""
    +        # Testing with the actual clipboard proved problematic, so this
    +        # test replaces the clipboard manipulation functions with mocks
    +        # and checks that they are called appropriately.
    +        squeezer = self.make_mock_squeezer()
    +        expandingbutton = ExpandingButton('TEXT', 'TAGS', 50, squeezer)
    +        expandingbutton.clipboard_clear = Mock()
    +        expandingbutton.clipboard_append = Mock()
    +
    +        # Trigger the copy event.
    +        retval = expandingbutton.copy(event=Mock())
    +        self.assertEqual(retval, None)
    +
    +        # Vheck that the expanding button called clipboard_clear() and
    +        # clipboard_append('TEXT') once each.
    +        self.assertEqual(expandingbutton.clipboard_clear.call_count, 1)
    +        self.assertEqual(expandingbutton.clipboard_append.call_count, 1)
    +        expandingbutton.clipboard_append.assert_called_with('TEXT')
    +
    +    def test_view(self):
    +        """Test the view event."""
    +        squeezer = self.make_mock_squeezer()
    +        expandingbutton = ExpandingButton('TEXT', 'TAGS', 50, squeezer)
    +        expandingbutton.selection_own = Mock()
    +
    +        with patch('idlelib.squeezer.view_text', autospec=view_text)\
    +                as mock_view_text:
    +            # Trigger the view event.
    +            expandingbutton.view(event=Mock())
    +
    +            # Check that the expanding button called view_text.
    +            self.assertEqual(mock_view_text.call_count, 1)
    +
    +            # Check that the proper text was passed.
    +            self.assertEqual(mock_view_text.call_args[0][2], 'TEXT')
    +
    +    def test_rmenu(self):
    +        """Test the context menu."""
    +        squeezer = self.make_mock_squeezer()
    +        expandingbutton = ExpandingButton('TEXT', 'TAGS', 50, squeezer)
    +        with patch('tkinter.Menu') as mock_Menu:
    +            mock_menu = Mock()
    +            mock_Menu.return_value = mock_menu
    +            mock_event = Mock()
    +            mock_event.x = 10
    +            mock_event.y = 10
    +            expandingbutton.context_menu_event(event=mock_event)
    +            self.assertEqual(mock_menu.add_command.call_count,
    +                             len(expandingbutton.rmenu_specs))
    +            for label, *data in expandingbutton.rmenu_specs:
    +                mock_menu.add_command.assert_any_call(label=label, command=ANY)
    +
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_stackviewer.py b/PythonLib/full/idlelib/idle_test/test_stackviewer.py
    new file mode 100644
    index 00000000..98f53f95
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_stackviewer.py
    @@ -0,0 +1,47 @@
    +"Test stackviewer, coverage 63%."
    +
    +from idlelib import stackviewer
    +import unittest
    +from test.support import requires
    +from tkinter import Tk
    +
    +from idlelib.tree import TreeNode, ScrolledCanvas
    +import sys
    +
    +
    +class StackBrowserTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        svs = stackviewer.sys
    +        try:
    +            abc
    +        except NameError:
    +            svs.last_type, svs.last_value, svs.last_traceback = (
    +                sys.exc_info())
    +
    +        requires('gui')
    +        cls.root = Tk()
    +        cls.root.withdraw()
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        svs = stackviewer.sys
    +        del svs.last_traceback, svs.last_type, svs.last_value
    +
    +        cls.root.update_idletasks()
    +##        for id in cls.root.tk.call('after', 'info'):
    +##            cls.root.after_cancel(id)  # Need for EditorWindow.
    +        cls.root.destroy()
    +        del cls.root
    +
    +    def test_init(self):
    +        sb = stackviewer.StackBrowser(self.root)
    +        isi = self.assertIsInstance
    +        isi(stackviewer.sc, ScrolledCanvas)
    +        isi(stackviewer.item, stackviewer.StackTreeItem)
    +        isi(stackviewer.node, TreeNode)
    +
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_statusbar.py b/PythonLib/full/idlelib/idle_test/test_statusbar.py
    new file mode 100644
    index 00000000..203a57db
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_statusbar.py
    @@ -0,0 +1,41 @@
    +"Test statusbar, coverage 100%."
    +
    +from idlelib import statusbar
    +import unittest
    +from test.support import requires
    +from tkinter import Tk
    +
    +
    +class Test(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        requires('gui')
    +        cls.root = Tk()
    +        cls.root.withdraw()
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        cls.root.update_idletasks()
    +        cls.root.destroy()
    +        del cls.root
    +
    +    def test_init(self):
    +        bar = statusbar.MultiStatusBar(self.root)
    +        self.assertEqual(bar.labels, {})
    +
    +    def test_set_label(self):
    +        bar = statusbar.MultiStatusBar(self.root)
    +        bar.set_label('left', text='sometext', width=10)
    +        self.assertIn('left', bar.labels)
    +        left = bar.labels['left']
    +        self.assertEqual(left['text'], 'sometext')
    +        self.assertEqual(left['width'], 10)
    +        bar.set_label('left', text='revised text')
    +        self.assertEqual(left['text'], 'revised text')
    +        bar.set_label('right', text='correct text')
    +        self.assertEqual(bar.labels['right']['text'], 'correct text')
    +
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_text.py b/PythonLib/full/idlelib/idle_test/test_text.py
    index 50d3face..0f31179e 100644
    --- a/PythonLib/full/idlelib/idle_test/test_text.py
    +++ b/PythonLib/full/idlelib/idle_test/test_text.py
    @@ -1,17 +1,19 @@
    -# Test mock_tk.Text class against tkinter.Text class by running same tests with both.
    -import unittest
    -from test.test_support import requires
    +''' Test mock_tk.Text class against tkinter.Text class
     
    +Run same tests with both by creating a mixin class.
    +'''
    +import unittest
    +from test.support import requires
     from _tkinter import TclError
     
     class TextTest(object):
    +    "Define items common to both sets of tests."
     
    -    hw = 'hello\nworld'  # usual initial insert after initialization
    +    hw = 'hello\nworld'  # Several tests insert this after initialization.
         hwn = hw+'\n'  # \n present at initialization, before insert
     
    -    Text = None
    -    def setUp(self):
    -        self.text = self.Text()
    +    # setUpClass defines cls.Text and maybe cls.root.
    +    # setUp defines self.text from Text and maybe root.
     
         def test_init(self):
             self.assertEqual(self.text.get('1.0'), '\n')
    @@ -196,6 +198,10 @@ def setUpClass(cls):
             from idlelib.idle_test.mock_tk import Text
             cls.Text = Text
     
    +    def setUp(self):
    +        self.text = self.Text()
    +
    +
         def test_decode(self):
             # test endflags (-1, 0) not tested by test_index (which uses +1)
             decode = self.text._decode
    @@ -213,7 +219,7 @@ class TkTextTest(TextTest, unittest.TestCase):
         @classmethod
         def setUpClass(cls):
             requires('gui')
    -        from Tkinter import Tk, Text
    +        from tkinter import Tk, Text
             cls.Text = Text
             cls.root = Tk()
     
    @@ -222,6 +228,9 @@ def tearDownClass(cls):
             cls.root.destroy()
             del cls.root
     
    +    def setUp(self):
    +        self.text = self.Text(self.root)
    +
     
     if __name__ == '__main__':
         unittest.main(verbosity=2, exit=False)
    diff --git a/PythonLib/full/idlelib/idle_test/test_textview.py b/PythonLib/full/idlelib/idle_test/test_textview.py
    index fa437fcb..7189378a 100644
    --- a/PythonLib/full/idlelib/idle_test/test_textview.py
    +++ b/PythonLib/full/idlelib/idle_test/test_textview.py
    @@ -1,95 +1,232 @@
    -'''Test the functions and main class method of textView.py.'''
    +"""Test textview, coverage 100%.
    +
    +Since all methods and functions create (or destroy) a ViewWindow, which
    +is a widget containing a widget, etcetera, all tests must be gui tests.
    +Using mock Text would not change this.  Other mocks are used to retrieve
    +information about calls.
    +"""
    +from idlelib import textview as tv
    +from test.support import requires
    +requires('gui')
     
    -import unittest
     import os
    -from test.test_support import requires
    -from Tkinter import Tk
    -from idlelib import textView as tv
    +import unittest
    +from tkinter import Tk, TclError, CHAR, NONE, WORD
    +from tkinter.ttk import Button
     from idlelib.idle_test.mock_idle import Func
    -from idlelib.idle_test.mock_tk import Mbox
    +from idlelib.idle_test.mock_tk import Mbox_func
    +
    +def setUpModule():
    +    global root
    +    root = Tk()
    +    root.withdraw()
    +
    +def tearDownModule():
    +    global root
    +    root.update_idletasks()
    +    root.destroy()
    +    del root
    +
    +# If we call ViewWindow or wrapper functions with defaults
    +# modal=True, _utest=False, test hangs on call to wait_window.
    +# Have also gotten tk error 'can't invoke "event" command'.
     
     
    -class TV(tv.TextViewer):  # Use in TextViewTest
    +class VW(tv.ViewWindow):  # Used in ViewWindowTest.
         transient = Func()
         grab_set = Func()
         wait_window = Func()
     
    -class textviewClassTest(unittest.TestCase):
     
    -    @classmethod
    -    def setUpClass(cls):
    -        requires('gui')
    -        cls.root = Tk()
    -        cls.root.withdraw()
    -
    -    @classmethod
    -    def tearDownClass(cls):
    -        cls.root.destroy()
    -        del cls.root
    +# Call wrapper class VW with mock wait_window.
    +class ViewWindowTest(unittest.TestCase):
     
         def setUp(self):
    -        TV.transient.__init__()
    -        TV.grab_set.__init__()
    -        TV.wait_window.__init__()
    +        VW.transient.__init__()
    +        VW.grab_set.__init__()
    +        VW.wait_window.__init__()
     
         def test_init_modal(self):
    -        view = TV(self.root, 'Title', 'test text')
    -        self.assertTrue(TV.transient.called)
    -        self.assertTrue(TV.grab_set.called)
    -        self.assertTrue(TV.wait_window.called)
    -        view.Ok()
    +        view = VW(root, 'Title', 'test text')
    +        self.assertTrue(VW.transient.called)
    +        self.assertTrue(VW.grab_set.called)
    +        self.assertTrue(VW.wait_window.called)
    +        view.ok()
     
         def test_init_nonmodal(self):
    -        view = TV(self.root, 'Title', 'test text', modal=False)
    -        self.assertFalse(TV.transient.called)
    -        self.assertFalse(TV.grab_set.called)
    -        self.assertFalse(TV.wait_window.called)
    -        view.Ok()
    +        view = VW(root, 'Title', 'test text', modal=False)
    +        self.assertFalse(VW.transient.called)
    +        self.assertFalse(VW.grab_set.called)
    +        self.assertFalse(VW.wait_window.called)
    +        view.ok()
     
         def test_ok(self):
    -        view = TV(self.root, 'Title', 'test text', modal=False)
    +        view = VW(root, 'Title', 'test text', modal=False)
             view.destroy = Func()
    -        view.Ok()
    +        view.ok()
             self.assertTrue(view.destroy.called)
    -        del view.destroy  # Unmask the real function.
    +        del view.destroy  # Unmask real function.
             view.destroy()
     
     
    -class ViewFunctionTest(unittest.TestCase):
    +class AutoHideScrollbarTest(unittest.TestCase):
    +    # Method set is tested in ScrollableTextFrameTest
    +    def test_forbidden_geometry(self):
    +        scroll = tv.AutoHideScrollbar(root)
    +        self.assertRaises(TclError, scroll.pack)
    +        self.assertRaises(TclError, scroll.place)
    +
    +
    +class ScrollableTextFrameTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        cls.root = root = Tk()
    +        root.withdraw()
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        cls.root.update_idletasks()
    +        cls.root.destroy()
    +        del cls.root
    +
    +    def make_frame(self, wrap=NONE, **kwargs):
    +        frame = tv.ScrollableTextFrame(self.root, wrap=wrap, **kwargs)
    +        def cleanup_frame():
    +            frame.update_idletasks()
    +            frame.destroy()
    +        self.addCleanup(cleanup_frame)
    +        return frame
    +
    +    def test_line1(self):
    +        frame = self.make_frame()
    +        frame.text.insert('1.0', 'test text')
    +        self.assertEqual(frame.text.get('1.0', '1.end'), 'test text')
    +
    +    def test_horiz_scrollbar(self):
    +        # The horizontal scrollbar should be shown/hidden according to
    +        # the 'wrap' setting: It should only be shown when 'wrap' is
    +        # set to NONE.
    +
    +        # wrap = NONE -> with horizontal scrolling
    +        frame = self.make_frame(wrap=NONE)
    +        self.assertEqual(frame.text.cget('wrap'), NONE)
    +        self.assertIsNotNone(frame.xscroll)
    +
    +        # wrap != NONE -> no horizontal scrolling
    +        for wrap in [CHAR, WORD]:
    +            with self.subTest(wrap=wrap):
    +                frame = self.make_frame(wrap=wrap)
    +                self.assertEqual(frame.text.cget('wrap'), wrap)
    +                self.assertIsNone(frame.xscroll)
    +
    +
    +class ViewFrameTest(unittest.TestCase):
     
         @classmethod
         def setUpClass(cls):
    -        requires('gui')
    -        cls.root = Tk()
    -        cls.root.withdraw()
    -        cls.orig_mbox = tv.tkMessageBox
    -        tv.tkMessageBox = Mbox
    +        cls.root = root = Tk()
    +        root.withdraw()
    +        cls.frame = tv.ViewFrame(root, 'test text')
     
         @classmethod
         def tearDownClass(cls):
    +        del cls.frame
    +        cls.root.update_idletasks()
             cls.root.destroy()
             del cls.root
    -        tv.tkMessageBox = cls.orig_mbox
    -        del cls.orig_mbox
    +
    +    def test_line1(self):
    +        get = self.frame.text.get
    +        self.assertEqual(get('1.0', '1.end'), 'test text')
    +
    +
    +# Call ViewWindow with modal=False.
    +class ViewFunctionTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        cls.orig_error = tv.showerror
    +        tv.showerror = Mbox_func()
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        tv.showerror = cls.orig_error
    +        del cls.orig_error
     
         def test_view_text(self):
    -        # If modal True, get tkinter error 'can't invoke "event" command'.
    -        view = tv.view_text(self.root, 'Title', 'test text', modal=False)
    -        self.assertIsInstance(view, tv.TextViewer)
    -        view.Ok()
    +        view = tv.view_text(root, 'Title', 'test text', modal=False)
    +        self.assertIsInstance(view, tv.ViewWindow)
    +        self.assertIsInstance(view.viewframe, tv.ViewFrame)
    +        view.viewframe.ok()
     
         def test_view_file(self):
    -        test_dir = os.path.dirname(__file__)
    -        testfile = os.path.join(test_dir, 'test_textview.py')
    -        view = tv.view_file(self.root, 'Title', testfile, modal=False)
    -        self.assertIsInstance(view, tv.TextViewer)
    -        self.assertIn('Test', view.textView.get('1.0', '1.end'))
    -        view.Ok()
    -
    -        # Mock messagebox will be used; view_file will return None.
    -        testfile = os.path.join(test_dir, '../notthere.py')
    -        view = tv.view_file(self.root, 'Title', testfile, modal=False)
    +        view = tv.view_file(root, 'Title', __file__, 'ascii', modal=False)
    +        self.assertIsInstance(view, tv.ViewWindow)
    +        self.assertIsInstance(view.viewframe, tv.ViewFrame)
    +        get = view.viewframe.textframe.text.get
    +        self.assertIn('Test', get('1.0', '1.end'))
    +        view.ok()
    +
    +    def test_bad_file(self):
    +        # Mock showerror will be used; view_file will return None.
    +        view = tv.view_file(root, 'Title', 'abc.xyz', 'ascii', modal=False)
    +        self.assertIsNone(view)
    +        self.assertEqual(tv.showerror.title, 'File Load Error')
    +
    +    def test_bad_encoding(self):
    +        p = os.path
    +        fn = p.abspath(p.join(p.dirname(__file__), '..', 'CREDITS.txt'))
    +        view = tv.view_file(root, 'Title', fn, 'ascii', modal=False)
             self.assertIsNone(view)
    +        self.assertEqual(tv.showerror.title, 'Unicode Decode Error')
    +
    +    def test_nowrap(self):
    +        view = tv.view_text(root, 'Title', 'test', modal=False, wrap='none')
    +        text_widget = view.viewframe.textframe.text
    +        self.assertEqual(text_widget.cget('wrap'), 'none')
    +
    +
    +# Call ViewWindow with _utest=True.
    +class ButtonClickTest(unittest.TestCase):
    +
    +    def setUp(self):
    +        self.view = None
    +        self.called = False
    +
    +    def tearDown(self):
    +        if self.view:
    +            self.view.destroy()
    +
    +    def test_view_text_bind_with_button(self):
    +        def _command():
    +            self.called = True
    +            self.view = tv.view_text(root, 'TITLE_TEXT', 'COMMAND', _utest=True)
    +        button = Button(root, text='BUTTON', command=_command)
    +        button.invoke()
    +        self.addCleanup(button.destroy)
    +
    +        self.assertEqual(self.called, True)
    +        self.assertEqual(self.view.title(), 'TITLE_TEXT')
    +        self.assertEqual(self.view.viewframe.textframe.text.get('1.0', '1.end'),
    +                         'COMMAND')
    +
    +    def test_view_file_bind_with_button(self):
    +        def _command():
    +            self.called = True
    +            self.view = tv.view_file(root, 'TITLE_FILE', __file__,
    +                                     encoding='ascii', _utest=True)
    +        button = Button(root, text='BUTTON', command=_command)
    +        button.invoke()
    +        self.addCleanup(button.destroy)
    +
    +        self.assertEqual(self.called, True)
    +        self.assertEqual(self.view.title(), 'TITLE_FILE')
    +        get = self.view.viewframe.textframe.text.get
    +        with open(__file__) as f:
    +            self.assertEqual(get('1.0', '1.end'), f.readline().strip())
    +            f.readline()
    +            self.assertEqual(get('3.0', '3.end'), f.readline().strip())
     
     
     if __name__ == '__main__':
    diff --git a/PythonLib/full/idlelib/idle_test/test_tooltip.py b/PythonLib/full/idlelib/idle_test/test_tooltip.py
    new file mode 100644
    index 00000000..c616d4fd
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_tooltip.py
    @@ -0,0 +1,161 @@
    +"""Test tooltip, coverage 100%.
    +
    +Coverage is 100% after excluding 6 lines with "# pragma: no cover".
    +They involve TclErrors that either should or should not happen in a
    +particular situation, and which are 'pass'ed if they do.
    +"""
    +
    +from idlelib.tooltip import TooltipBase, Hovertip
    +from test.support import requires
    +requires('gui')
    +
    +from functools import wraps
    +import time
    +from tkinter import Button, Tk, Toplevel
    +import unittest
    +
    +
    +def setUpModule():
    +    global root
    +    root = Tk()
    +
    +def tearDownModule():
    +    global root
    +    root.update_idletasks()
    +    root.destroy()
    +    del root
    +
    +
    +def add_call_counting(func):
    +    @wraps(func)
    +    def wrapped_func(*args, **kwargs):
    +        wrapped_func.call_args_list.append((args, kwargs))
    +        return func(*args, **kwargs)
    +    wrapped_func.call_args_list = []
    +    return wrapped_func
    +
    +
    +def _make_top_and_button(testobj):
    +    global root
    +    top = Toplevel(root)
    +    testobj.addCleanup(top.destroy)
    +    top.title("Test tooltip")
    +    button = Button(top, text='ToolTip test button')
    +    button.pack()
    +    testobj.addCleanup(button.destroy)
    +    top.lift()
    +    return top, button
    +
    +
    +class ToolTipBaseTest(unittest.TestCase):
    +    def setUp(self):
    +        self.top, self.button = _make_top_and_button(self)
    +
    +    def test_base_class_is_unusable(self):
    +        global root
    +        top = Toplevel(root)
    +        self.addCleanup(top.destroy)
    +
    +        button = Button(top, text='ToolTip test button')
    +        button.pack()
    +        self.addCleanup(button.destroy)
    +
    +        with self.assertRaises(NotImplementedError):
    +            tooltip = TooltipBase(button)
    +            tooltip.showtip()
    +
    +
    +class HovertipTest(unittest.TestCase):
    +    def setUp(self):
    +        self.top, self.button = _make_top_and_button(self)
    +
    +    def is_tipwindow_shown(self, tooltip):
    +        return tooltip.tipwindow and tooltip.tipwindow.winfo_viewable()
    +
    +    def test_showtip(self):
    +        tooltip = Hovertip(self.button, 'ToolTip text')
    +        self.addCleanup(tooltip.hidetip)
    +        self.assertFalse(self.is_tipwindow_shown(tooltip))
    +        tooltip.showtip()
    +        self.assertTrue(self.is_tipwindow_shown(tooltip))
    +
    +    def test_showtip_twice(self):
    +        tooltip = Hovertip(self.button, 'ToolTip text')
    +        self.addCleanup(tooltip.hidetip)
    +        self.assertFalse(self.is_tipwindow_shown(tooltip))
    +        tooltip.showtip()
    +        self.assertTrue(self.is_tipwindow_shown(tooltip))
    +        orig_tipwindow = tooltip.tipwindow
    +        tooltip.showtip()
    +        self.assertTrue(self.is_tipwindow_shown(tooltip))
    +        self.assertIs(tooltip.tipwindow, orig_tipwindow)
    +
    +    def test_hidetip(self):
    +        tooltip = Hovertip(self.button, 'ToolTip text')
    +        self.addCleanup(tooltip.hidetip)
    +        tooltip.showtip()
    +        tooltip.hidetip()
    +        self.assertFalse(self.is_tipwindow_shown(tooltip))
    +
    +    def test_showtip_on_mouse_enter_no_delay(self):
    +        tooltip = Hovertip(self.button, 'ToolTip text', hover_delay=None)
    +        self.addCleanup(tooltip.hidetip)
    +        tooltip.showtip = add_call_counting(tooltip.showtip)
    +        root.update()
    +        self.assertFalse(self.is_tipwindow_shown(tooltip))
    +        self.button.event_generate('', x=0, y=0)
    +        root.update()
    +        self.assertTrue(self.is_tipwindow_shown(tooltip))
    +        self.assertGreater(len(tooltip.showtip.call_args_list), 0)
    +
    +    def test_hover_with_delay(self):
    +        # Run multiple tests requiring an actual delay simultaneously.
    +
    +        # Test #1: A hover tip with a non-zero delay appears after the delay.
    +        tooltip1 = Hovertip(self.button, 'ToolTip text', hover_delay=100)
    +        self.addCleanup(tooltip1.hidetip)
    +        tooltip1.showtip = add_call_counting(tooltip1.showtip)
    +        root.update()
    +        self.assertFalse(self.is_tipwindow_shown(tooltip1))
    +        self.button.event_generate('', x=0, y=0)
    +        root.update()
    +        self.assertFalse(self.is_tipwindow_shown(tooltip1))
    +
    +        # Test #2: A hover tip with a non-zero delay doesn't appear when
    +        # the mouse stops hovering over the base widget before the delay
    +        # expires.
    +        tooltip2 = Hovertip(self.button, 'ToolTip text', hover_delay=100)
    +        self.addCleanup(tooltip2.hidetip)
    +        tooltip2.showtip = add_call_counting(tooltip2.showtip)
    +        root.update()
    +        self.button.event_generate('', x=0, y=0)
    +        root.update()
    +        self.button.event_generate('', x=0, y=0)
    +        root.update()
    +
    +        time.sleep(0.15)
    +        root.update()
    +
    +        # Test #1 assertions.
    +        self.assertTrue(self.is_tipwindow_shown(tooltip1))
    +        self.assertGreater(len(tooltip1.showtip.call_args_list), 0)
    +
    +        # Test #2 assertions.
    +        self.assertFalse(self.is_tipwindow_shown(tooltip2))
    +        self.assertEqual(tooltip2.showtip.call_args_list, [])
    +
    +    def test_hidetip_on_mouse_leave(self):
    +        tooltip = Hovertip(self.button, 'ToolTip text', hover_delay=None)
    +        self.addCleanup(tooltip.hidetip)
    +        tooltip.showtip = add_call_counting(tooltip.showtip)
    +        root.update()
    +        self.button.event_generate('', x=0, y=0)
    +        root.update()
    +        self.button.event_generate('', x=0, y=0)
    +        root.update()
    +        self.assertFalse(self.is_tipwindow_shown(tooltip))
    +        self.assertGreater(len(tooltip.showtip.call_args_list), 0)
    +
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_tree.py b/PythonLib/full/idlelib/idle_test/test_tree.py
    new file mode 100644
    index 00000000..b3e4c10c
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_tree.py
    @@ -0,0 +1,60 @@
    +"Test tree. coverage 56%."
    +
    +from idlelib import tree
    +import unittest
    +from test.support import requires
    +requires('gui')
    +from tkinter import Tk, EventType, SCROLL
    +
    +
    +class TreeTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        cls.root = Tk()
    +        cls.root.withdraw()
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        cls.root.destroy()
    +        del cls.root
    +
    +    def test_init(self):
    +        # Start with code slightly adapted from htest.
    +        sc = tree.ScrolledCanvas(
    +            self.root, bg="white", highlightthickness=0, takefocus=1)
    +        sc.frame.pack(expand=1, fill="both", side='left')
    +        item = tree.FileTreeItem(tree.ICONDIR)
    +        node = tree.TreeNode(sc.canvas, None, item)
    +        node.expand()
    +
    +
    +class TestScrollEvent(unittest.TestCase):
    +
    +    def test_wheel_event(self):
    +        # Fake widget class containing `yview` only.
    +        class _Widget:
    +            def __init__(widget, *expected):
    +                widget.expected = expected
    +            def yview(widget, *args):
    +                self.assertTupleEqual(widget.expected, args)
    +        # Fake event class
    +        class _Event:
    +            pass
    +        #        (type, delta, num, amount)
    +        tests = ((EventType.MouseWheel, 120, -1, -5),
    +                 (EventType.MouseWheel, -120, -1, 5),
    +                 (EventType.ButtonPress, -1, 4, -5),
    +                 (EventType.ButtonPress, -1, 5, 5))
    +
    +        event = _Event()
    +        for ty, delta, num, amount in tests:
    +            event.type = ty
    +            event.delta = delta
    +            event.num = num
    +            res = tree.wheel_event(event, _Widget(SCROLL, amount, "units"))
    +            self.assertEqual(res, "break")
    +
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_undo.py b/PythonLib/full/idlelib/idle_test/test_undo.py
    new file mode 100644
    index 00000000..beb5b582
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_undo.py
    @@ -0,0 +1,135 @@
    +"Test undo, coverage 77%."
    +# Only test UndoDelegator so far.
    +
    +from idlelib.undo import UndoDelegator
    +import unittest
    +from test.support import requires
    +requires('gui')
    +
    +from unittest.mock import Mock
    +from tkinter import Text, Tk
    +from idlelib.percolator import Percolator
    +
    +
    +class UndoDelegatorTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        cls.root = Tk()
    +        cls.text = Text(cls.root)
    +        cls.percolator = Percolator(cls.text)
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        cls.percolator.redir.close()
    +        del cls.percolator, cls.text
    +        cls.root.destroy()
    +        del cls.root
    +
    +    def setUp(self):
    +        self.delegator = UndoDelegator()
    +        self.delegator.bell = Mock()
    +        self.percolator.insertfilter(self.delegator)
    +
    +    def tearDown(self):
    +        self.percolator.removefilter(self.delegator)
    +        self.text.delete('1.0', 'end')
    +        self.delegator.resetcache()
    +
    +    def test_undo_event(self):
    +        text = self.text
    +
    +        text.insert('insert', 'foobar')
    +        text.insert('insert', 'h')
    +        text.event_generate('<>')
    +        self.assertEqual(text.get('1.0', 'end'), '\n')
    +
    +        text.insert('insert', 'foo')
    +        text.insert('insert', 'bar')
    +        text.delete('1.2', '1.4')
    +        text.insert('insert', 'hello')
    +        text.event_generate('<>')
    +        self.assertEqual(text.get('1.0', '1.4'), 'foar')
    +        text.event_generate('<>')
    +        self.assertEqual(text.get('1.0', '1.6'), 'foobar')
    +        text.event_generate('<>')
    +        self.assertEqual(text.get('1.0', '1.3'), 'foo')
    +        text.event_generate('<>')
    +        self.delegator.undo_event('event')
    +        self.assertTrue(self.delegator.bell.called)
    +
    +    def test_redo_event(self):
    +        text = self.text
    +
    +        text.insert('insert', 'foo')
    +        text.insert('insert', 'bar')
    +        text.delete('1.0', '1.3')
    +        text.event_generate('<>')
    +        text.event_generate('<>')
    +        self.assertEqual(text.get('1.0', '1.3'), 'bar')
    +        text.event_generate('<>')
    +        self.assertTrue(self.delegator.bell.called)
    +
    +    def test_dump_event(self):
    +        """
    +        Dump_event cannot be tested directly without changing
    +        environment variables. So, test statements in dump_event
    +        indirectly
    +        """
    +        text = self.text
    +        d = self.delegator
    +
    +        text.insert('insert', 'foo')
    +        text.insert('insert', 'bar')
    +        text.delete('1.2', '1.4')
    +        self.assertTupleEqual((d.pointer, d.can_merge), (3, True))
    +        text.event_generate('<>')
    +        self.assertTupleEqual((d.pointer, d.can_merge), (2, False))
    +
    +    def test_get_set_saved(self):
    +        # test the getter method get_saved
    +        # test the setter method set_saved
    +        # indirectly test check_saved
    +        d = self.delegator
    +
    +        self.assertTrue(d.get_saved())
    +        self.text.insert('insert', 'a')
    +        self.assertFalse(d.get_saved())
    +        d.saved_change_hook = Mock()
    +
    +        d.set_saved(True)
    +        self.assertEqual(d.pointer, d.saved)
    +        self.assertTrue(d.saved_change_hook.called)
    +
    +        d.set_saved(False)
    +        self.assertEqual(d.saved, -1)
    +        self.assertTrue(d.saved_change_hook.called)
    +
    +    def test_undo_start_stop(self):
    +        # test the undo_block_start and undo_block_stop methods
    +        text = self.text
    +
    +        text.insert('insert', 'foo')
    +        self.delegator.undo_block_start()
    +        text.insert('insert', 'bar')
    +        text.insert('insert', 'bar')
    +        self.delegator.undo_block_stop()
    +        self.assertEqual(text.get('1.0', '1.3'), 'foo')
    +
    +        # test another code path
    +        self.delegator.undo_block_start()
    +        text.insert('insert', 'bar')
    +        self.delegator.undo_block_stop()
    +        self.assertEqual(text.get('1.0', '1.3'), 'foo')
    +
    +    def test_addcmd(self):
    +        text = self.text
    +        # when number of undo operations exceeds max_undo
    +        self.delegator.max_undo = max_undo = 10
    +        for i in range(max_undo + 10):
    +            text.insert('insert', 'foo')
    +            self.assertLessEqual(len(self.delegator.undolist), max_undo)
    +
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2, exit=False)
    diff --git a/PythonLib/full/idlelib/idle_test/test_warning.py b/PythonLib/full/idlelib/idle_test/test_warning.py
    index da1d8a1d..221068c5 100644
    --- a/PythonLib/full/idlelib/idle_test/test_warning.py
    +++ b/PythonLib/full/idlelib/idle_test/test_warning.py
    @@ -1,25 +1,23 @@
    -'''Test warnings replacement in PyShell.py and run.py.
    +'''Test warnings replacement in pyshell.py and run.py.
     
     This file could be expanded to include traceback overrides
     (in same two modules). If so, change name.
     Revise if output destination changes (http://bugs.python.org/issue18318).
     Make sure warnings module is left unaltered (http://bugs.python.org/issue18081).
     '''
    -
    +from idlelib import run
    +from idlelib import pyshell as shell
     import unittest
    -from test.test_support import captured_stderr
    -
    +from test.support import captured_stderr
     import warnings
    +
     # Try to capture default showwarning before Idle modules are imported.
     showwarning = warnings.showwarning
     # But if we run this file within idle, we are in the middle of the run.main loop
     # and default showwarnings has already been replaced.
     running_in_idle = 'idle' in showwarning.__name__
     
    -from idlelib import run
    -from idlelib import PyShell as shell
    -
    -# The following was generated from PyShell.idle_formatwarning
    +# The following was generated from pyshell.idle_formatwarning
     # and checked as matching expectation.
     idlemsg = '''
     Warning (from warnings module):
    @@ -29,6 +27,7 @@
     '''
     shellmsg = idlemsg + ">>> "
     
    +
     class RunWarnTest(unittest.TestCase):
     
         @unittest.skipIf(running_in_idle, "Does not work when run within Idle.")
    @@ -46,6 +45,7 @@ def test_run_show(self):
                 # The following uses .splitlines to erase line-ending differences
                 self.assertEqual(idlemsg.splitlines(), f.getvalue().splitlines())
     
    +
     class ShellWarnTest(unittest.TestCase):
     
         @unittest.skipIf(running_in_idle, "Does not work when run within Idle.")
    @@ -70,4 +70,4 @@ def test_shell_show(self):
     
     
     if __name__ == '__main__':
    -    unittest.main(verbosity=2, exit=False)
    +    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_widgetredir.py b/PythonLib/full/idlelib/idle_test/test_widgetredir.py
    deleted file mode 100644
    index e35ea417..00000000
    --- a/PythonLib/full/idlelib/idle_test/test_widgetredir.py
    +++ /dev/null
    @@ -1,124 +0,0 @@
    -"""Unittest for idlelib.WidgetRedirector
    -
    -100% coverage
    -"""
    -from test.test_support import requires
    -import unittest
    -from idlelib.idle_test.mock_idle import Func
    -from Tkinter import Tk, Text, TclError
    -from idlelib.WidgetRedirector import WidgetRedirector
    -
    -
    -class InitCloseTest(unittest.TestCase):
    -
    -    @classmethod
    -    def setUpClass(cls):
    -        requires('gui')
    -        cls.root = Tk()
    -        cls.root.withdraw()
    -        cls.text = Text(cls.root)
    -
    -    @classmethod
    -    def tearDownClass(cls):
    -        del cls.text
    -        cls.root.destroy()
    -        del cls.root
    -
    -    def test_init(self):
    -        redir = WidgetRedirector(self.text)
    -        self.assertEqual(redir.widget, self.text)
    -        self.assertEqual(redir.tk, self.text.tk)
    -        self.assertRaises(TclError, WidgetRedirector, self.text)
    -        redir.close()  # restore self.tk, self.text
    -
    -    def test_close(self):
    -        redir = WidgetRedirector(self.text)
    -        redir.register('insert', Func)
    -        redir.close()
    -        self.assertEqual(redir._operations, {})
    -        self.assertFalse(hasattr(self.text, 'widget'))
    -
    -
    -class WidgetRedirectorTest(unittest.TestCase):
    -
    -    @classmethod
    -    def setUpClass(cls):
    -        requires('gui')
    -        cls.root = Tk()
    -        cls.root.withdraw()
    -        cls.text = Text(cls.root)
    -
    -    @classmethod
    -    def tearDownClass(cls):
    -        del cls.text
    -        cls.root.destroy()
    -        del cls.root
    -
    -    def setUp(self):
    -        self.redir = WidgetRedirector(self.text)
    -        self.func = Func()
    -        self.orig_insert = self.redir.register('insert', self.func)
    -        self.text.insert('insert', 'asdf')  # leaves self.text empty
    -
    -    def tearDown(self):
    -        self.text.delete('1.0', 'end')
    -        self.redir.close()
    -
    -    def test_repr(self):  # partly for 100% coverage
    -        self.assertIn('Redirector', repr(self.redir))
    -        self.assertIn('Original', repr(self.orig_insert))
    -
    -    def test_register(self):
    -        self.assertEqual(self.text.get('1.0', 'end'), '\n')
    -        self.assertEqual(self.func.args, ('insert', 'asdf'))
    -        self.assertIn('insert', self.redir._operations)
    -        self.assertIn('insert', self.text.__dict__)
    -        self.assertEqual(self.text.insert, self.func)
    -
    -    def test_original_command(self):
    -        self.assertEqual(self.orig_insert.operation, 'insert')
    -        self.assertEqual(self.orig_insert.tk_call, self.text.tk.call)
    -        self.orig_insert('insert', 'asdf')
    -        self.assertEqual(self.text.get('1.0', 'end'), 'asdf\n')
    -
    -    def test_unregister(self):
    -        self.assertIsNone(self.redir.unregister('invalid operation name'))
    -        self.assertEqual(self.redir.unregister('insert'), self.func)
    -        self.assertNotIn('insert', self.redir._operations)
    -        self.assertNotIn('insert', self.text.__dict__)
    -
    -    def test_unregister_no_attribute(self):
    -        del self.text.insert
    -        self.assertEqual(self.redir.unregister('insert'), self.func)
    -
    -    def test_dispatch_intercept(self):
    -        self.func.__init__(True)
    -        self.assertTrue(self.redir.dispatch('insert', False))
    -        self.assertFalse(self.func.args[0])
    -
    -    def test_dispatch_bypass(self):
    -        self.orig_insert('insert', 'asdf')
    -        # tk.call returns '' where Python would return None
    -        self.assertEqual(self.redir.dispatch('delete', '1.0', 'end'), '')
    -        self.assertEqual(self.text.get('1.0', 'end'), '\n')
    -
    -    def test_dispatch_error(self):
    -        self.func.__init__(TclError())
    -        self.assertEqual(self.redir.dispatch('insert', False), '')
    -        self.assertEqual(self.redir.dispatch('invalid'), '')
    -
    -    def test_command_dispatch(self):
    -        # Test that .__init__ causes redirection of tk calls
    -        # through redir.dispatch
    -        self.root.call(self.text._w, 'insert', 'hello')
    -        self.assertEqual(self.func.args, ('hello',))
    -        self.assertEqual(self.text.get('1.0', 'end'), '\n')
    -        # Ensure that called through redir .dispatch and not through
    -        # self.text.insert by having mock raise TclError.
    -        self.func.__init__(TclError())
    -        self.assertEqual(self.root.call(self.text._w, 'insert', 'boo'), '')
    -
    -
    -
    -if __name__ == '__main__':
    -    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_window.py b/PythonLib/full/idlelib/idle_test/test_window.py
    new file mode 100644
    index 00000000..5a2645b9
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_window.py
    @@ -0,0 +1,45 @@
    +"Test window, coverage 47%."
    +
    +from idlelib import window
    +import unittest
    +from test.support import requires
    +from tkinter import Tk
    +
    +
    +class WindowListTest(unittest.TestCase):
    +
    +    def test_init(self):
    +        wl = window.WindowList()
    +        self.assertEqual(wl.dict, {})
    +        self.assertEqual(wl.callbacks, [])
    +
    +    # Further tests need mock Window.
    +
    +
    +class ListedToplevelTest(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        window.registry = set()
    +        requires('gui')
    +        cls.root = Tk()
    +        cls.root.withdraw()
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        window.registry = window.WindowList()
    +        cls.root.update_idletasks()
    +##        for id in cls.root.tk.call('after', 'info'):
    +##            cls.root.after_cancel(id)  # Need for EditorWindow.
    +        cls.root.destroy()
    +        del cls.root
    +
    +    def test_init(self):
    +
    +        win = window.ListedToplevel(self.root)
    +        self.assertIn(win, window.registry)
    +        self.assertEqual(win.focused_widget, win)
    +
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idle_test/test_zoomheight.py b/PythonLib/full/idlelib/idle_test/test_zoomheight.py
    new file mode 100644
    index 00000000..aa5bdfb4
    --- /dev/null
    +++ b/PythonLib/full/idlelib/idle_test/test_zoomheight.py
    @@ -0,0 +1,39 @@
    +"Test zoomheight, coverage 66%."
    +# Some code is system dependent.
    +
    +from idlelib import zoomheight
    +import unittest
    +from test.support import requires
    +from tkinter import Tk
    +from idlelib.editor import EditorWindow
    +
    +
    +class Test(unittest.TestCase):
    +
    +    @classmethod
    +    def setUpClass(cls):
    +        requires('gui')
    +        cls.root = Tk()
    +        cls.root.withdraw()
    +        cls.editwin = EditorWindow(root=cls.root)
    +
    +    @classmethod
    +    def tearDownClass(cls):
    +        cls.editwin._close()
    +        cls.root.update_idletasks()
    +        for id in cls.root.tk.call('after', 'info'):
    +            cls.root.after_cancel(id)  # Need for EditorWindow.
    +        cls.root.destroy()
    +        del cls.root
    +
    +    def test_init(self):
    +        zoom = zoomheight.ZoomHeight(self.editwin)
    +        self.assertIs(zoom.editwin, self.editwin)
    +
    +    def test_zoom_height_event(self):
    +        zoom = zoomheight.ZoomHeight(self.editwin)
    +        zoom.zoom_height_event()
    +
    +
    +if __name__ == '__main__':
    +    unittest.main(verbosity=2)
    diff --git a/PythonLib/full/idlelib/idlever.py b/PythonLib/full/idlelib/idlever.py
    deleted file mode 100644
    index 3e9f69a3..00000000
    --- a/PythonLib/full/idlelib/idlever.py
    +++ /dev/null
    @@ -1,12 +0,0 @@
    -"""
    -The separate Idle version was eliminated years ago;
    -idlelib.idlever is no longer used by Idle
    -and will be removed in 3.6 or later.  Use
    -    from sys import version
    -    IDLE_VERSION = version[:version.index(' ')]
    -"""
    -# Kept for now only for possible existing extension use
    -import warnings as w
    -w.warn(__doc__, DeprecationWarning, stacklevel=2)
    -from sys import version
    -IDLE_VERSION = version[:version.index(' ')]
    diff --git a/PythonLib/full/idlelib/iomenu.py b/PythonLib/full/idlelib/iomenu.py
    new file mode 100644
    index 00000000..4b2833b8
    --- /dev/null
    +++ b/PythonLib/full/idlelib/iomenu.py
    @@ -0,0 +1,573 @@
    +import codecs
    +from codecs import BOM_UTF8
    +import os
    +import re
    +import shlex
    +import sys
    +import tempfile
    +
    +import tkinter.filedialog as tkFileDialog
    +import tkinter.messagebox as tkMessageBox
    +from tkinter.simpledialog import askstring
    +
    +import idlelib
    +from idlelib.config import idleConf
    +
    +if idlelib.testing:  # Set True by test.test_idle to avoid setlocale.
    +    encoding = 'utf-8'
    +    errors = 'surrogateescape'
    +else:
    +    # Try setting the locale, so that we can find out
    +    # what encoding to use
    +    try:
    +        import locale
    +        locale.setlocale(locale.LC_CTYPE, "")
    +    except (ImportError, locale.Error):
    +        pass
    +
    +    if sys.platform == 'win32':
    +        encoding = 'utf-8'
    +        errors = 'surrogateescape'
    +    else:
    +        try:
    +            # Different things can fail here: the locale module may not be
    +            # loaded, it may not offer nl_langinfo, or CODESET, or the
    +            # resulting codeset may be unknown to Python. We ignore all
    +            # these problems, falling back to ASCII
    +            locale_encoding = locale.nl_langinfo(locale.CODESET)
    +            if locale_encoding:
    +                codecs.lookup(locale_encoding)
    +        except (NameError, AttributeError, LookupError):
    +            # Try getdefaultlocale: it parses environment variables,
    +            # which may give a clue. Unfortunately, getdefaultlocale has
    +            # bugs that can cause ValueError.
    +            try:
    +                locale_encoding = locale.getdefaultlocale()[1]
    +                if locale_encoding:
    +                    codecs.lookup(locale_encoding)
    +            except (ValueError, LookupError):
    +                pass
    +
    +        if locale_encoding:
    +            encoding = locale_encoding.lower()
    +            errors = 'strict'
    +        else:
    +            # POSIX locale or macOS
    +            encoding = 'ascii'
    +            errors = 'surrogateescape'
    +        # Encoding is used in multiple files; locale_encoding nowhere.
    +        # The only use of 'encoding' below is in _decode as initial value
    +        # of deprecated block asking user for encoding.
    +        # Perhaps use elsewhere should be reviewed.
    +
    +coding_re = re.compile(r'^[ \t\f]*#.*?coding[:=][ \t]*([-\w.]+)', re.ASCII)
    +blank_re = re.compile(r'^[ \t\f]*(?:[#\r\n]|$)', re.ASCII)
    +
    +def coding_spec(data):
    +    """Return the encoding declaration according to PEP 263.
    +
    +    When checking encoded data, only the first two lines should be passed
    +    in to avoid a UnicodeDecodeError if the rest of the data is not unicode.
    +    The first two lines would contain the encoding specification.
    +
    +    Raise a LookupError if the encoding is declared but unknown.
    +    """
    +    if isinstance(data, bytes):
    +        # This encoding might be wrong. However, the coding
    +        # spec must be ASCII-only, so any non-ASCII characters
    +        # around here will be ignored. Decoding to Latin-1 should
    +        # never fail (except for memory outage)
    +        lines = data.decode('iso-8859-1')
    +    else:
    +        lines = data
    +    # consider only the first two lines
    +    if '\n' in lines:
    +        lst = lines.split('\n', 2)[:2]
    +    elif '\r' in lines:
    +        lst = lines.split('\r', 2)[:2]
    +    else:
    +        lst = [lines]
    +    for line in lst:
    +        match = coding_re.match(line)
    +        if match is not None:
    +            break
    +        if not blank_re.match(line):
    +            return None
    +    else:
    +        return None
    +    name = match.group(1)
    +    try:
    +        codecs.lookup(name)
    +    except LookupError:
    +        # The standard encoding error does not indicate the encoding
    +        raise LookupError("Unknown encoding: "+name)
    +    return name
    +
    +
    +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("<>", self.open)
    +        self.__id_save = self.text.bind("<>", self.save)
    +        self.__id_saveas = self.text.bind("<>",
    +                                          self.save_as)
    +        self.__id_savecopy = self.text.bind("<>",
    +                                            self.save_a_copy)
    +        self.fileencoding = None
    +        self.__id_print = self.text.bind("<>", self.print_window)
    +
    +    def close(self):
    +        # Undo command bindings
    +        self.text.unbind("<>", self.__id_open)
    +        self.text.unbind("<>", self.__id_save)
    +        self.text.unbind("<>",self.__id_saveas)
    +        self.text.unbind("<>", self.__id_savecopy)
    +        self.text.unbind("<>", 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 = r"(\r\n)|\n|\r"  # \r\n (Windows), \n (UNIX), or \r (Mac)
    +    eol_re = re.compile(eol)
    +    eol_convention = os.linesep  # default
    +
    +    def loadfile(self, filename):
    +        try:
    +            # open the file in binary mode so that we can handle
    +            # end-of-line convention ourselves.
    +            with open(filename, 'rb') as f:
    +                two_lines = f.readline() + f.readline()
    +                f.seek(0)
    +                bytes = f.read()
    +        except OSError as msg:
    +            tkMessageBox.showerror("I/O Error", str(msg), parent=self.text)
    +            return False
    +        chars, converted = self._decode(two_lines, bytes)
    +        if chars is None:
    +            tkMessageBox.showerror("Decoding Error",
    +                                   "File %s\nFailed to Decode" % filename,
    +                                   parent=self.text)
    +            return False
    +        # We now convert all end-of-lines to '\n's
    +        firsteol = self.eol_re.search(chars)
    +        if firsteol:
    +            self.eol_convention = firsteol.group(0)
    +            chars = self.eol_re.sub(r"\n", chars)
    +        self.text.delete("1.0", "end")
    +        self.set_filename(None)
    +        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 _decode(self, two_lines, bytes):
    +        "Create a Unicode string."
    +        chars = None
    +        # Check presence of a UTF-8 signature first
    +        if bytes.startswith(BOM_UTF8):
    +            try:
    +                chars = bytes[3:].decode("utf-8")
    +            except UnicodeDecodeError:
    +                # has UTF-8 signature, but fails to decode...
    +                return None, False
    +            else:
    +                # Indicates that this file originally had a BOM
    +                self.fileencoding = 'BOM'
    +                return chars, False
    +        # Next look for coding specification
    +        try:
    +            enc = coding_spec(two_lines)
    +        except LookupError as name:
    +            tkMessageBox.showerror(
    +                title="Error loading the file",
    +                message="The encoding '%s' is not known to this Python "\
    +                "installation. The file may not display correctly" % name,
    +                parent = self.text)
    +            enc = None
    +        except UnicodeDecodeError:
    +            return None, False
    +        if enc:
    +            try:
    +                chars = str(bytes, enc)
    +                self.fileencoding = enc
    +                return chars, False
    +            except UnicodeDecodeError:
    +                pass
    +        # Try ascii:
    +        try:
    +            chars = str(bytes, 'ascii')
    +            self.fileencoding = None
    +            return chars, False
    +        except UnicodeDecodeError:
    +            pass
    +        # Try utf-8:
    +        try:
    +            chars = str(bytes, 'utf-8')
    +            self.fileencoding = 'utf-8'
    +            return chars, False
    +        except UnicodeDecodeError:
    +            pass
    +        # Finally, try the locale's encoding. This is deprecated;
    +        # the user should declare a non-ASCII encoding
    +        try:
    +            # 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 = encoding,
    +                parent = self.editwin.text)
    +
    +            if enc:
    +                chars = str(bytes, enc)
    +                self.fileencoding = None
    +            return chars, True
    +        except (UnicodeDecodeError, LookupError):
    +            pass
    +        return None, False  # None on failure
    +
    +    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 = tkMessageBox.askyesnocancel(
    +                  title="Save On Close",
    +                  message=message,
    +                  default=tkMessageBox.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:
    +            tkMessageBox.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 == 'BOM':
    +            return BOM_UTF8 + chars.encode("utf-8")
    +        # See whether there is anything non-ASCII in it.
    +        # If not, no need to figure out the encoding.
    +        try:
    +            return chars.encode('ascii')
    +        except UnicodeError:
    +            pass
    +        # Check if there is an encoding declared
    +        try:
    +            # a string, let coding_spec slice it to the first two lines
    +            enc = coding_spec(chars)
    +            failed = None
    +        except LookupError as msg:
    +            failed = msg
    +            enc = None
    +        else:
    +            if not enc:
    +                # PEP 3120: default source encoding is UTF-8
    +                enc = 'utf-8'
    +        if enc:
    +            try:
    +                return chars.encode(enc)
    +            except UnicodeError:
    +                failed = "Invalid encoding '%s'" % enc
    +        tkMessageBox.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 BOM_UTF8 + chars.encode("utf-8")
    +
    +    def print_window(self, event):
    +        confirm = tkMessageBox.askokcancel(
    +                  title="Print",
    +                  message="Print to Default Printer",
    +                  default=tkMessageBox.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
    +                tkMessageBox.showerror("Print status", output, parent=self.text)
    +        else:  #no printing for this platform
    +            message = "Printing is not enabled for this platform: %s" % platform
    +            tkMessageBox.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 = tkFileDialog.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 = tkFileDialog.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("", self.open)
    +            self.text.bind('', self.print)
    +            self.text.bind("", self.save)
    +            self.text.bind("", self.saveas)
    +            self.text.bind('', 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("<>")
    +        def print(self, event):
    +            self.text.event_generate("<>")
    +        def save(self, event):
    +            self.text.event_generate("<>")
    +        def saveas(self, event):
    +            self.text.event_generate("<>")
    +        def savecopy(self, event):
    +            self.text.event_generate("<>")
    +
    +    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/PythonLib/full/idlelib/keybindingDialog.py b/PythonLib/full/idlelib/keybindingDialog.py
    deleted file mode 100644
    index 9713c79a..00000000
    --- a/PythonLib/full/idlelib/keybindingDialog.py
    +++ /dev/null
    @@ -1,268 +0,0 @@
    -"""
    -Dialog for building Tkinter accelerator key bindings
    -"""
    -from Tkinter import *
    -import tkMessageBox
    -import string
    -import sys
    -
    -class GetKeysDialog(Toplevel):
    -    def __init__(self,parent,title,action,currentKeySequences,_htest=False):
    -        """
    -        action - string, the name of the virtual event these keys will be
    -                 mapped to
    -        currentKeys - list, a list of all key sequence lists currently mapped
    -                 to virtual events, for overlap checking
    -        _htest - bool, change box location when running htest
    -        """
    -        Toplevel.__init__(self, parent)
    -        self.configure(borderwidth=5)
    -        self.resizable(height=FALSE,width=FALSE)
    -        self.title(title)
    -        self.transient(parent)
    -        self.grab_set()
    -        self.protocol("WM_DELETE_WINDOW", self.Cancel)
    -        self.parent = parent
    -        self.action=action
    -        self.currentKeySequences=currentKeySequences
    -        self.result=''
    -        self.keyString=StringVar(self)
    -        self.keyString.set('')
    -        self.SetModifiersForPlatform() # set self.modifiers, self.modifier_label
    -        self.modifier_vars = []
    -        for modifier in self.modifiers:
    -            variable = StringVar(self)
    -            variable.set('')
    -            self.modifier_vars.append(variable)
    -        self.advanced = False
    -        self.CreateWidgets()
    -        self.LoadFinalKeyList()
    -        self.withdraw() #hide while setting geometry
    -        self.update_idletasks()
    -        self.geometry(
    -                "+%d+%d" % (
    -                    parent.winfo_rootx() +
    -                    (parent.winfo_width()/2 - self.winfo_reqwidth()/2),
    -                    parent.winfo_rooty() +
    -                    ((parent.winfo_height()/2 - self.winfo_reqheight()/2)
    -                    if not _htest else 150)
    -                ) )  #centre dialog over parent (or below htest box)
    -        self.deiconify() #geometry set, unhide
    -        self.wait_window()
    -
    -    def CreateWidgets(self):
    -        frameMain = Frame(self,borderwidth=2,relief=SUNKEN)
    -        frameMain.pack(side=TOP,expand=TRUE,fill=BOTH)
    -        frameButtons=Frame(self)
    -        frameButtons.pack(side=BOTTOM,fill=X)
    -        self.buttonOK = Button(frameButtons,text='OK',
    -                width=8,command=self.OK)
    -        self.buttonOK.grid(row=0,column=0,padx=5,pady=5)
    -        self.buttonCancel = Button(frameButtons,text='Cancel',
    -                width=8,command=self.Cancel)
    -        self.buttonCancel.grid(row=0,column=1,padx=5,pady=5)
    -        self.frameKeySeqBasic = Frame(frameMain)
    -        self.frameKeySeqAdvanced = Frame(frameMain)
    -        self.frameControlsBasic = Frame(frameMain)
    -        self.frameHelpAdvanced = Frame(frameMain)
    -        self.frameKeySeqAdvanced.grid(row=0,column=0,sticky=NSEW,padx=5,pady=5)
    -        self.frameKeySeqBasic.grid(row=0,column=0,sticky=NSEW,padx=5,pady=5)
    -        self.frameKeySeqBasic.lift()
    -        self.frameHelpAdvanced.grid(row=1,column=0,sticky=NSEW,padx=5)
    -        self.frameControlsBasic.grid(row=1,column=0,sticky=NSEW,padx=5)
    -        self.frameControlsBasic.lift()
    -        self.buttonLevel = Button(frameMain,command=self.ToggleLevel,
    -                text='Advanced Key Binding Entry >>')
    -        self.buttonLevel.grid(row=2,column=0,stick=EW,padx=5,pady=5)
    -        labelTitleBasic = Label(self.frameKeySeqBasic,
    -                text="New keys for  '"+self.action+"' :")
    -        labelTitleBasic.pack(anchor=W)
    -        labelKeysBasic = Label(self.frameKeySeqBasic,justify=LEFT,
    -                textvariable=self.keyString,relief=GROOVE,borderwidth=2)
    -        labelKeysBasic.pack(ipadx=5,ipady=5,fill=X)
    -        self.modifier_checkbuttons = {}
    -        column = 0
    -        for modifier, variable in zip(self.modifiers, self.modifier_vars):
    -            label = self.modifier_label.get(modifier, modifier)
    -            check=Checkbutton(self.frameControlsBasic,
    -                command=self.BuildKeyString,
    -                text=label,variable=variable,onvalue=modifier,offvalue='')
    -            check.grid(row=0,column=column,padx=2,sticky=W)
    -            self.modifier_checkbuttons[modifier] = check
    -            column += 1
    -        labelFnAdvice=Label(self.frameControlsBasic,justify=LEFT,
    -                            text=\
    -                            "Select the desired modifier keys\n"+
    -                            "above, and the final key from the\n"+
    -                            "list on the right.\n\n" +
    -                            "Use upper case Symbols when using\n" +
    -                            "the Shift modifier.  (Letters will be\n" +
    -                            "converted automatically.)")
    -        labelFnAdvice.grid(row=1,column=0,columnspan=4,padx=2,sticky=W)
    -        self.listKeysFinal=Listbox(self.frameControlsBasic,width=15,height=10,
    -                selectmode=SINGLE)
    -        self.listKeysFinal.bind('',self.FinalKeySelected)
    -        self.listKeysFinal.grid(row=0,column=4,rowspan=4,sticky=NS)
    -        scrollKeysFinal=Scrollbar(self.frameControlsBasic,orient=VERTICAL,
    -                command=self.listKeysFinal.yview)
    -        self.listKeysFinal.config(yscrollcommand=scrollKeysFinal.set)
    -        scrollKeysFinal.grid(row=0,column=5,rowspan=4,sticky=NS)
    -        self.buttonClear=Button(self.frameControlsBasic,
    -                text='Clear Keys',command=self.ClearKeySeq)
    -        self.buttonClear.grid(row=2,column=0,columnspan=4)
    -        labelTitleAdvanced = Label(self.frameKeySeqAdvanced,justify=LEFT,
    -                text="Enter new binding(s) for  '"+self.action+"' :\n"+
    -                "(These bindings will not be checked for validity!)")
    -        labelTitleAdvanced.pack(anchor=W)
    -        self.entryKeysAdvanced=Entry(self.frameKeySeqAdvanced,
    -                textvariable=self.keyString)
    -        self.entryKeysAdvanced.pack(fill=X)
    -        labelHelpAdvanced=Label(self.frameHelpAdvanced,justify=LEFT,
    -            text="Key bindings are specified using Tkinter keysyms as\n"+
    -                 "in these samples: , , ,\n"
    -                 ", , .\n"
    -                 "Upper case is used when the Shift modifier is present!\n\n" +
    -                 "'Emacs style' multi-keystroke bindings are specified as\n" +
    -                 "follows: , 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.,  ." )
    -        labelHelpAdvanced.grid(row=0,column=0,sticky=NSEW)
    -
    -    def SetModifiersForPlatform(self):
    -        """Determine list of names of key modifiers for this platform.
    -
    -        The names are used to build Tk bindings -- it doesn't matter if the
    -        keyboard has these keys, it matters if Tk understands them. The
    -        order is also important: key binding equality depends on it, so
    -        config-keys.def must use the same ordering.
    -        """
    -        if sys.platform == "darwin":
    -            self.modifiers = ['Shift', 'Control', 'Option', 'Command']
    -        else:
    -            self.modifiers = ['Control', 'Alt', 'Shift']
    -        self.modifier_label = {'Control': 'Ctrl'} # short name
    -
    -    def ToggleLevel(self):
    -        if  self.buttonLevel.cget('text')[:8]=='Advanced':
    -            self.ClearKeySeq()
    -            self.buttonLevel.config(text='<< Basic Key Binding Entry')
    -            self.frameKeySeqAdvanced.lift()
    -            self.frameHelpAdvanced.lift()
    -            self.entryKeysAdvanced.focus_set()
    -            self.advanced = True
    -        else:
    -            self.ClearKeySeq()
    -            self.buttonLevel.config(text='Advanced Key Binding Entry >>')
    -            self.frameKeySeqBasic.lift()
    -            self.frameControlsBasic.lift()
    -            self.advanced = False
    -
    -    def FinalKeySelected(self,event):
    -        self.BuildKeyString()
    -
    -    def BuildKeyString(self):
    -        keyList = modifiers = self.GetModifiers()
    -        finalKey = self.listKeysFinal.get(ANCHOR)
    -        if finalKey:
    -            finalKey = self.TranslateKey(finalKey, modifiers)
    -            keyList.append(finalKey)
    -        self.keyString.set('<' + string.join(keyList,'-') + '>')
    -
    -    def GetModifiers(self):
    -        modList = [variable.get() for variable in self.modifier_vars]
    -        return [mod for mod in modList if mod]
    -
    -    def ClearKeySeq(self):
    -        self.listKeysFinal.select_clear(0,END)
    -        self.listKeysFinal.yview(MOVETO, '0.0')
    -        for variable in self.modifier_vars:
    -            variable.set('')
    -        self.keyString.set('')
    -
    -    def LoadFinalKeyList(self):
    -        #these tuples are also available for use in validity checks
    -        self.functionKeys=('F1','F2','F3','F4','F5','F6','F7','F8','F9',
    -                'F10','F11','F12')
    -        self.alphanumKeys=tuple(string.ascii_lowercase+string.digits)
    -        self.punctuationKeys=tuple('~!@#%^&*()_-+={}[]|;:,.<>/?')
    -        self.whitespaceKeys=('Tab','Space','Return')
    -        self.editKeys=('BackSpace','Delete','Insert')
    -        self.moveKeys=('Home','End','Page Up','Page Down','Left Arrow',
    -                'Right Arrow','Up Arrow','Down Arrow')
    -        #make a tuple of most of the useful common 'final' keys
    -        keys=(self.alphanumKeys+self.punctuationKeys+self.functionKeys+
    -                self.whitespaceKeys+self.editKeys+self.moveKeys)
    -        self.listKeysFinal.insert(END, *keys)
    -
    -    def TranslateKey(self, key, modifiers):
    -        "Translate from keycap symbol to the Tkinter keysym"
    -        translateDict = {'Space':'space',
    -                '~':'asciitilde','!':'exclam','@':'at','#':'numbersign',
    -                '%':'percent','^':'asciicircum','&':'ampersand','*':'asterisk',
    -                '(':'parenleft',')':'parenright','_':'underscore','-':'minus',
    -                '+':'plus','=':'equal','{':'braceleft','}':'braceright',
    -                '[':'bracketleft',']':'bracketright','|':'bar',';':'semicolon',
    -                ':':'colon',',':'comma','.':'period','<':'less','>':'greater',
    -                '/':'slash','?':'question','Page Up':'Prior','Page Down':'Next',
    -                'Left Arrow':'Left','Right Arrow':'Right','Up Arrow':'Up',
    -                'Down Arrow': 'Down', 'Tab':'Tab'}
    -        if key in translateDict.keys():
    -            key = translateDict[key]
    -        if 'Shift' in modifiers and key in string.ascii_lowercase:
    -            key = key.upper()
    -        key = 'Key-' + key
    -        return key
    -
    -    def OK(self, event=None):
    -        if self.advanced or self.KeysOK():  # doesn't check advanced string yet
    -            self.result=self.keyString.get()
    -            self.grab_release()
    -            self.destroy()
    -
    -    def Cancel(self, event=None):
    -        self.result=''
    -        self.grab_release()
    -        self.destroy()
    -
    -    def KeysOK(self):
    -        '''Validity check on user's 'basic' keybinding selection.
    -
    -        Doesn't check the string produced by the advanced dialog because
    -        'modifiers' isn't set.
    -
    -        '''
    -        keys = self.keyString.get()
    -        keys.strip()
    -        finalKey = self.listKeysFinal.get(ANCHOR)
    -        modifiers = self.GetModifiers()
    -        # create a key sequence list for overlap check:
    -        keySequence = keys.split()
    -        keysOK = False
    -        title = 'Key Sequence Error'
    -        if not keys:
    -            tkMessageBox.showerror(title=title, parent=self,
    -                                   message='No keys specified.')
    -        elif not keys.endswith('>'):
    -            tkMessageBox.showerror(title=title, parent=self,
    -                                   message='Missing the final Key')
    -        elif (not modifiers
    -              and finalKey not in self.functionKeys + self.moveKeys):
    -            tkMessageBox.showerror(title=title, parent=self,
    -                                   message='No modifier key(s) specified.')
    -        elif (modifiers == ['Shift']) \
    -                 and (finalKey not in
    -                      self.functionKeys + self.moveKeys + ('Tab', 'Space')):
    -            msg = 'The shift modifier by itself may not be used with'\
    -                  ' this key symbol.'
    -            tkMessageBox.showerror(title=title, parent=self, message=msg)
    -        elif keySequence in self.currentKeySequences:
    -            msg = 'This key combination is already in use.'
    -            tkMessageBox.showerror(title=title, parent=self, message=msg)
    -        else:
    -            keysOK = True
    -        return keysOK
    -
    -if __name__ == '__main__':
    -    from idlelib.idle_test.htest import run
    -    run(GetKeysDialog)
    diff --git a/PythonLib/full/idlelib/macosx.py b/PythonLib/full/idlelib/macosx.py
    new file mode 100644
    index 00000000..eeaab59a
    --- /dev/null
    +++ b/PythonLib/full/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_dialog)
    +    root.bind('<>', config_dialog)
    +    root.createcommand('::tk::mac::ShowPreferences', config_dialog)
    +    if flist:
    +        root.bind('<>', 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', '<>'),
    +                    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', '')
    +    root.unbind_class('Text', '')
    +    root.unbind_class('Text', '<>')
    +
    +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/PythonLib/full/idlelib/macosxSupport.py b/PythonLib/full/idlelib/macosxSupport.py
    deleted file mode 100644
    index 041d7008..00000000
    --- a/PythonLib/full/idlelib/macosxSupport.py
    +++ /dev/null
    @@ -1,237 +0,0 @@
    -"""
    -A number of functions that enhance IDLE on Mac OSX.
    -"""
    -import sys
    -import Tkinter
    -from os import path
    -
    -
    -import warnings
    -
    -def runningAsOSXApp():
    -    warnings.warn("runningAsOSXApp() is deprecated, use isAquaTk()",
    -                        DeprecationWarning, stacklevel=2)
    -    return isAquaTk()
    -
    -def isCarbonAquaTk(root):
    -    warnings.warn("isCarbonAquaTk(root) is deprecated, use isCarbonTk()",
    -                        DeprecationWarning, stacklevel=2)
    -    return isCarbonTk()
    -
    -_tk_type = None
    -
    -def _initializeTkVariantTests(root):
    -    """
    -    Initializes OS X Tk variant values for
    -    isAquaTk(), isCarbonTk(), isCocoaTk(), and isXQuartz().
    -    """
    -    global _tk_type
    -    if sys.platform == 'darwin':
    -        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"
    -    else:
    -        _tk_type = "other"
    -
    -def isAquaTk():
    -    """
    -    Returns True if IDLE is using a native OS X Tk (Cocoa or Carbon).
    -    """
    -    assert _tk_type is not None
    -    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).
    -    """
    -    assert _tk_type is not None
    -    return _tk_type == "carbon"
    -
    -def isCocoaTk():
    -    """
    -    Returns True if IDLE is using a Cocoa Aqua Tk.
    -    """
    -    assert _tk_type is not None
    -    return _tk_type == "cocoa"
    -
    -def isXQuartz():
    -    """
    -    Returns True if IDLE is using an OS X X11 Tk.
    -    """
    -    assert _tk_type is not None
    -    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 (r"WARNING: The version of Tcl/Tk ({0}) in use may"
    -                r" be unstable.\n"
    -                r"Visit http://www.python.org/download/mac/tcltk/"
    -                r" for current information.".format(patchlevel))
    -    else:
    -        return False
    -
    -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 Bindings
    -    from idlelib import WindowList
    -
    -    closeItem = Bindings.menudefs[0][1][-2]
    -
    -    # Remove the last 3 items of the file menu: a separator, close window and
    -    # quit. Close window will be reinserted just above the save item, where
    -    # it should be according to the HIG. Quit is in the application menu.
    -    del Bindings.menudefs[0][1][-3:]
    -    Bindings.menudefs[0][1].insert(6, closeItem)
    -
    -    # Remove the 'About' entry from the help menu, it is in the application
    -    # menu
    -    del Bindings.menudefs[-1][1][0:2]
    -    # Remove the 'Configure Idle' entry from the options menu, it is in the
    -    # application menu as 'Preferences'
    -    del Bindings.menudefs[-2][1][0]
    -    menubar = Menu(root)
    -    root.configure(menu=menubar)
    -    menudict = {}
    -
    -    menudict['windows'] = menu = Menu(menubar, name='windows', 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)
    -        WindowList.add_windows_to_menu(menu)
    -    WindowList.register_callback(postwindowsmenu)
    -
    -    def about_dialog(event=None):
    -        "Handle Help 'About IDLE' event."
    -        # Synchronize with EditorWindow.EditorWindow.about_dialog.
    -        from idlelib import aboutDialog
    -        aboutDialog.AboutDialog(root, 'About IDLE')
    -
    -    def config_dialog(event=None):
    -        "Handle Options 'Configure IDLE' event."
    -        # Synchronize with EditorWindow.EditorWindow.config_dialog.
    -        from idlelib import configDialog
    -        root.instance_dict = flist.inversedict
    -        configDialog.ConfigDialog(root, 'Settings')
    -
    -    def help_dialog(event=None):
    -        "Handle Help 'IDLE Help' event."
    -        # Synchronize with EditorWindow.EditorWindow.help_dialog.
    -        from idlelib import help
    -        help.show_idlehelp(root)
    -
    -    root.bind('<>', about_dialog)
    -    root.bind('<>', config_dialog)
    -    root.createcommand('::tk::mac::ShowPreferences', config_dialog)
    -    if flist:
    -        root.bind('<>', flist.close_all_callback)
    -
    -        # The binding above doesn't reliably work on all versions of Tk
    -        # on MacOSX. 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)
    -        Bindings.menudefs.insert(0,
    -            ('application', [
    -                ('About IDLE', '<>'),
    -                    None,
    -                ]))
    -        tkversion = root.tk.eval('info patchlevel')
    -        if tuple(map(int, tkversion.split('.'))) < (8, 4, 14):
    -            # for earlier AquaTk versions, supply a Preferences menu item
    -            Bindings.menudefs[0][1].append(
    -                    ('_Preferences....', '<>'),
    -                )
    -    if isCocoaTk():
    -        # replace default About dialog with About IDLE one
    -        root.createcommand('tkAboutDialog', about_dialog)
    -        # replace default "Help" item in Help menu
    -        root.createcommand('::tk::mac::ShowHelp', help_dialog)
    -        # remove redundant "IDLE Help" from menu
    -        del Bindings.menudefs[-1][1][0]
    -
    -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.
    -    """
    -    _initializeTkVariantTests(root)
    -    if isAquaTk():
    -        hideTkConsole(root)
    -        overrideRootMenu(root, flist)
    -        addOpenEventSupport(root, flist)
    diff --git a/PythonLib/full/idlelib/mainmenu.py b/PythonLib/full/idlelib/mainmenu.py
    new file mode 100644
    index 00000000..74edce23
    --- /dev/null
    +++ b/PythonLib/full/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...', '<>'),
    +   ('Open _Module...', '<>'),
    +   ('Module _Browser', '<>'),
    +   ('_Path Browser', '<>'),
    +   None,
    +   ('_Save', '<>'),
    +   ('Save _As...', '<>'),
    +   ('Save Cop_y As...', '<>'),
    +   None,
    +   ('Prin_t Window', '<>'),
    +   None,
    +   ('_Close', '<>'),
    +   ('E_xit', '<>'),
    +   ]),
    +
    + ('edit', [
    +   ('_Undo', '<>'),
    +   ('_Redo', '<>'),
    +   None,
    +   ('Cu_t', '<>'),
    +   ('_Copy', '<>'),
    +   ('_Paste', '<>'),
    +   ('Select _All', '<>'),
    +   None,
    +   ('_Find...', '<>'),
    +   ('Find A_gain', '<>'),
    +   ('Find _Selection', '<>'),
    +   ('Find in Files...', '<>'),
    +   ('R_eplace...', '<>'),
    +   ('Go to _Line', '<>'),
    +   ('S_how Completions', '<>'),
    +   ('E_xpand Word', '<>'),
    +   ('Show C_all Tip', '<>'),
    +   ('Show Surrounding P_arens', '<>'),
    +   ]),
    +
    + ('format', [
    +   ('F_ormat Paragraph', '<>'),
    +   ('_Indent Region', '<>'),
    +   ('_Dedent Region', '<>'),
    +   ('Comment _Out Region', '<>'),
    +   ('U_ncomment Region', '<>'),
    +   ('Tabify Region', '<>'),
    +   ('Untabify Region', '<>'),
    +   ('Toggle Tabs', '<>'),
    +   ('New Indent Width', '<>'),
    +   ('S_trip Trailing Whitespace', '<>'),
    +   ]),
    +
    + ('run', [
    +   ('R_un Module', '<>'),
    +   ('Run... _Customized', '<>'),
    +   ('C_heck Module', '<>'),
    +   ('Python Shell', '<>'),
    +   ]),
    +
    + ('shell', [
    +   ('_View Last Restart', '<>'),
    +   ('_Restart Shell', '<>'),
    +   None,
    +   ('_Previous History', '<>'),
    +   ('_Next History', '<>'),
    +   None,
    +   ('_Interrupt Execution', '<>'),
    +   ]),
    +
    + ('debug', [
    +   ('_Go to File/Line', '<>'),
    +   ('!_Debugger', '<>'),
    +   ('_Stack Viewer', '<>'),
    +   ('!_Auto-open Stack Viewer', '<>'),
    +   ]),
    +
    + ('options', [
    +   ('Configure _IDLE', '<>'),
    +   None,
    +   ('Show _Code Context', '<>'),
    +   ('Show _Line Numbers', '<>'),
    +   ('_Zoom Height', '<>'),
    +   ]),
    +
    + ('window', [
    +   ]),
    +
    + ('help', [
    +   ('_About IDLE', '<>'),
    +   None,
    +   ('_IDLE Help', '<>'),
    +   ('Python _Docs', '<>'),
    +   ]),
    +]
    +
    +if find_spec('turtledemo'):
    +    menudefs[-1][1].append(('Turtle Demo', '<>'))
    +
    +default_keydefs = idleConf.GetCurrentKeySet()
    +
    +if __name__ == '__main__':
    +    from unittest import main
    +    main('idlelib.idle_test.test_mainmenu', verbosity=2)
    diff --git a/PythonLib/full/idlelib/multicall.py b/PythonLib/full/idlelib/multicall.py
    new file mode 100644
    index 00000000..dc020012
    --- /dev/null
    +++ b/PythonLib/full/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("<>"%n[0], handler)
    +        text.event_add("<>"%n[0], seq)
    +        n[0] += 1
    +    bindseq("")
    +    bindseq("")
    +    bindseq("")
    +    bindseq("")
    +    bindseq("")
    +    bindseq("")
    +    bindseq("")
    +    bindseq("")
    +    bindseq("")
    +    bindseq("")
    +    bindseq("")
    +    bindseq("")
    +
    +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/PythonLib/full/idlelib/outwin.py b/PythonLib/full/idlelib/outwin.py
    new file mode 100644
    index 00000000..90272b6f
    --- /dev/null
    +++ b/PythonLib/full/idlelib/outwin.py
    @@ -0,0 +1,189 @@
    +"""Editor window that can serve as an output file.
    +"""
    +
    +import re
    +
    +from tkinter import messagebox
    +
    +from idlelib.editor import EditorWindow
    +from idlelib import iomenu
    +
    +
    +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", "<>", "rmenu_check_cut"),
    +        ("Copy", "<>", "rmenu_check_copy"),
    +        ("Paste", "<>", "rmenu_check_paste"),
    +        (None, None, None),
    +        ("Go to file/line", "<>", None),
    +    ]
    +
    +    allow_code_context = False
    +
    +    def __init__(self, *args):
    +        EditorWindow.__init__(self, *args)
    +        self.text.bind("<>", 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.
    +        """
    +        if isinstance(s, bytes):
    +            s = s.decode(iomenu.encoding, "replace")
    +        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/PythonLib/full/idlelib/parenmatch.py b/PythonLib/full/idlelib/parenmatch.py
    new file mode 100644
    index 00000000..3fd7aadb
    --- /dev/null
    +++ b/PythonLib/full/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 = "<>"
    +    # We want the restore event be called before the usual return and
    +    # backspace events.
    +    RESTORE_SEQUENCES = ("", "",
    +                         "", "")
    +
    +    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 <>, 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/PythonLib/full/idlelib/pathbrowser.py b/PythonLib/full/idlelib/pathbrowser.py
    new file mode 100644
    index 00000000..6de242d0
    --- /dev/null
    +++ b/PythonLib/full/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/PythonLib/full/idlelib/percolator.py b/PythonLib/full/idlelib/percolator.py
    new file mode 100644
    index 00000000..db70304f
    --- /dev/null
    +++ b/PythonLib/full/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/PythonLib/full/idlelib/pyparse.py b/PythonLib/full/idlelib/pyparse.py
    new file mode 100644
    index 00000000..d34872b4
    --- /dev/null
    +++ b/PythonLib/full/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 , 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/PythonLib/full/idlelib/pyshell.py b/PythonLib/full/idlelib/pyshell.py
    new file mode 100644
    index 00000000..d5b310ff
    --- /dev/null
    +++ b/PythonLib/full/idlelib/pyshell.py
    @@ -0,0 +1,1569 @@
    +#! /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
    +        ctypes.OleDLL('shcore').SetProcessDpiAwareness(PROCESS_SYSTEM_DPI_AWARE)
    +    except (ImportError, AttributeError, OSError):
    +        pass
    +
    +import tkinter.messagebox as tkMessageBox
    +if TkVersion < 8.5:
    +    root = Tk()  # otherwise create root in main
    +    root.withdraw()
    +    from idlelib.run import fix_scaling
    +    fix_scaling(root)
    +    tkMessageBox.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  entries
    +
    +    Rather than repeating the linecache code, patch it to save the
    +     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("<>", self.set_breakpoint_here)
    +        self.text.bind("<>", self.clear_breakpoint_here)
    +        self.text.bind("<>", 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", "<>", "rmenu_check_cut"),
    +        ("Copy", "<>", "rmenu_check_copy"),
    +        ("Paste", "<>", "rmenu_check_paste"),
    +        (None, None, None),
    +        ("Set Breakpoint", "<>", None),
    +        ("Clear Breakpoint", "<>", 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
    +                tkMessageBox.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("<>"):
    +                    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)
    +        self.more = 0
    +        # 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 = "" % 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 ""
    +        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("<>"):
    +            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.interp.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 tkMessageBox.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):
    +        tkMessageBox.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):
    +        tkMessageBox.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):
    +        tkMessageBox.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 = "Python " + python_version() + " Shell"
    +
    +    # 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", "<>"),
    +    ]
    +
    +    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("<>", self.enter_callback)
    +        text.bind("<>", self.linefeed_callback)
    +        text.bind("<>", self.cancel_callback)
    +        text.bind("<>", self.eof_callback)
    +        text.bind("<>", self.open_stack_viewer)
    +        text.bind("<>", self.toggle_debugger)
    +        text.bind("<>", self.toggle_jit_stack_viewer)
    +        if use_subprocess:
    +            text.bind("<>", self.view_restart_mark)
    +            text.bind("<>", self.restart_shell)
    +        squeezer = self.Squeezer(self)
    +        text.bind("<>",
    +                  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:
    +            tkMessageBox.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("<>", 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 beginexecuting(self):
    +        "Helper for ModifiedInterpreter"
    +        self.resetoutput()
    +        self.executing = 1
    +
    +    def endexecuting(self):
    +        "Helper for ModifiedInterpreter"
    +        self.executing = 0
    +        self.canceled = 0
    +        self.showprompt()
    +
    +    def close(self):
    +        "Extend EditorWindow.close()"
    +        if self.executing:
    +            response = tkMessageBox.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()
    +        import tkinter
    +        tkinter._default_root = None # 03Jan04 KBK What's this?
    +        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 = 1
    +            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 = 0
    +            if not use_subprocess:
    +                raise KeyboardInterrupt
    +        if self.endoffile:
    +            self.endoffile = 0
    +            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 = 0
    +        self.canceled = 1
    +        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 = 0
    +            self.endoffile = 1
    +            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:
    +            tkMessageBox.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 = 0
    +            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,
    +                '<>',
    +                'catch {%W delete sel.first sel.last}\n' +
    +                        root.bind_class(cls, '<>'))
    +
    +
    +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():
    +        ext = '.png' if TkVersion >= 8.6 else '.gif'
    +        iconfiles = [os.path.join(icondir, 'idle_%d%s' % (size, ext))
    +                     for size in (16, 32, 48)]
    +        icons = [PhotoImage(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/PythonLib/full/idlelib/query.py b/PythonLib/full/idlelib/query.py
    new file mode 100644
    index 00000000..097e6e61
    --- /dev/null
    +++ b/PythonLib/full/idlelib/query.py
    @@ -0,0 +1,376 @@
    +"""
    +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  is entered or [Ok] is clicked.
    +Entries are ignored when [Cancel] or [X] are clicked.
    +The 'return value' is .result set to either a valid answer or None.
    +
    +Subclass SectionName gets a name for a new config file section.
    +Configdialog uses it for new highlight theme and keybinding set names.
    +Subclass ModuleName gets a name for File => Open Module.
    +Subclass HelpSource gets menu item and path for additions to Help menu.
    +"""
    +# Query and Section name result from splitting GetCfgSectionNameDialog
    +# of configSectionNameDialog.py (temporarily config_sec.py) into
    +# generic and specific parts.  3.6 only, July 2016.
    +# ModuleName.entry_ok came from editor.EditorWindow.load_module.
    +# HelpSource was extracted from configHelpSourceEdit.py (temporarily
    +# config_help.py), with darwin code moved from ok to path_ok.
    +
    +import importlib
    +import os
    +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
    +
    +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)
    +        self.grab_set()
    +
    +        windowingsystem = self.tk.call('tk', 'windowingsystem')
    +        if windowingsystem == 'aqua':
    +            try:
    +                self.tk.call('::tk::unsupported::MacWindowStyle', 'style',
    +                             self._w, 'moveableModal', '')
    +            except:
    +                pass
    +            self.bind("", self.cancel)
    +        self.bind('', self.cancel)
    +        self.protocol("WM_DELETE_WINDOW", self.cancel)
    +        self.bind('', self.ok)
    +        self.bind("", 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)
    +        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."
    +        self.entry_error['text'] = ''
    +        entry = self.entry.get().strip()
    +        if not entry:
    +            self.showerror('blank line.')
    +            return None
    +        return entry
    +
    +    def ok(self, event=None):  # Do not replace.
    +        '''If entry is valid, bind it to 'result' and destroy tk widget.
    +
    +        Otherwise leave dialog open for user to correct entry or cancel.
    +        '''
    +        entry = self.entry_ok()
    +        if entry is not None:
    +            self.result = entry
    +            self.destroy()
    +        else:
    +            # [Ok] moves focus.  ( 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."
    +        self.entry_error['text'] = ''
    +        name = self.entry.get().strip()
    +        if not name:
    +            self.showerror('no name specified.')
    +            return None
    +        elif len(name)>30:
    +            self.showerror('name is longer than 30 characters.')
    +            return None
    +        elif name in self.used_names:
    +            self.showerror('name is already in use.')
    +            return None
    +        return name
    +
    +
    +class ModuleName(Query):
    +    "Get a module name for Open Module menu entry."
    +    # Used in open_module (editor.EditorWindow until move to iobinding).
    +
    +    def __init__(self, parent, title, message, text0,
    +                 *, _htest=False, _utest=False):
    +        super().__init__(parent, title, message, text0=text0,
    +                       _htest=_htest, _utest=_utest)
    +
    +    def entry_ok(self):
    +        "Return entered module name as file path or None."
    +        self.entry_error['text'] = ''
    +        name = self.entry.get().strip()
    +        if not name:
    +            self.showerror('no name specified.')
    +            return None
    +        # XXX Ought to insert current file's directory in front of path.
    +        try:
    +            spec = importlib.util.find_spec(name)
    +        except (ValueError, ImportError) as msg:
    +            self.showerror(str(msg))
    +            return None
    +        if spec is None:
    +            self.showerror("module not found")
    +            return None
    +        if not isinstance(spec.loader, importlib.abc.SourceLoader):
    +            self.showerror("not a source-based module")
    +            return None
    +        try:
    +            file_path = spec.loader.get_filename(name)
    +        except AttributeError:
    +            self.showerror("loader does not support get_filename",
    +                      parent=self)
    +            return None
    +        return file_path
    +
    +
    +class HelpSource(Query):
    +    "Get menu name and help source for Help menu."
    +    # Used in ConfigDialog.HelpListItemAdd/Edit, (941/9)
    +
    +    def __init__(self, parent, title, *, menuitem='', filepath='',
    +                 used_names={}, _htest=False, _utest=False):
    +        """Get menu entry and url/local file for Additional Help.
    +
    +        User enters a name for the Help resource and a web url or file
    +        name. The user can browse for the file.
    +        """
    +        self.filepath = filepath
    +        message = 'Name for item on Help menu:'
    +        super().__init__(
    +                parent, title, message, text0=menuitem,
    +                used_names=used_names, _htest=_htest, _utest=_utest)
    +
    +    def create_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.entry_error['text'] = ''
    +        self.path_error['text'] = ''
    +        name = self.item_ok()
    +        path = self.path_ok()
    +        return None if name is None or path is None else (name, path)
    +
    +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"
    +        self.entry_error['text'] = ''
    +        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/PythonLib/full/idlelib/redirector.py b/PythonLib/full/idlelib/redirector.py
    new file mode 100644
    index 00000000..9ab34c5a
    --- /dev/null
    +++ b/PythonLib/full/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  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/PythonLib/full/idlelib/replace.py b/PythonLib/full/idlelib/replace.py
    new file mode 100644
    index 00000000..6be034af
    --- /dev/null
    +++ b/PythonLib/full/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/PythonLib/full/idlelib/rpc.py b/PythonLib/full/idlelib/rpc.py
    index 43328e72..aa8cbd36 100644
    --- a/PythonLib/full/idlelib/rpc.py
    +++ b/PythonLib/full/idlelib/rpc.py
    @@ -5,7 +5,7 @@
     only one client per server, this was not a limitation.
     
        +---------------------------------+ +-------------+
    -   | SocketServer.BaseRequestHandler | | SocketIO    |
    +   | socketserver.BaseRequestHandler | | SocketIO    |
        +---------------------------------+ +-------------+
                        ^                   | register()  |
                        |                   | unregister()|
    @@ -26,52 +26,56 @@
     accomplished in Idle.
     
     """
    -
    -import sys
    +import builtins
    +import copyreg
    +import io
    +import marshal
     import os
    -import socket
    +import pickle
    +import queue
     import select
    -import SocketServer
    +import socket
    +import socketserver
     import struct
    -import cPickle as pickle
    +import sys
     import threading
    -import Queue
     import traceback
    -import copy_reg
     import types
    -import marshal
    -
     
     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,)
     
    -# XXX KBK 24Aug02 function pickling capability not used in Idle
    -#  def unpickle_function(ms):
    -#      return 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()
    +
     
    -#  def pickle_function(fn):
    -#      assert isinstance(fn, type.FunctionType)
    -#      return repr(fn)
    +class CodePickler(pickle.Pickler):
    +    dispatch_table = {types.CodeType: pickle_code, **copyreg.dispatch_table}
     
    -copy_reg.pickle(types.CodeType, pickle_code, unpickle_code)
    -# copy_reg.pickle(types.FunctionType, pickle_function, unpickle_function)
     
     BUFSIZE = 8*1024
     LOCALHOST = '127.0.0.1'
     
    -class RPCServer(SocketServer.TCPServer):
    +class RPCServer(socketserver.TCPServer):
     
         def __init__(self, addr, handlerclass=None):
             if handlerclass is None:
                 handlerclass = RPCHandler
    -        SocketServer.TCPServer.__init__(self, addr, handlerclass)
    +        socketserver.TCPServer.__init__(self, addr, handlerclass)
     
         def server_bind(self):
             "Override TCPServer method, no bind() phase for connecting entity"
    @@ -104,21 +108,21 @@ def handle_error(self, request, client_address):
                 raise
             except:
                 erf = sys.__stderr__
    -            print>>erf, '\n' + '-'*40
    -            print>>erf, 'Unhandled server exception!'
    -            print>>erf, 'Thread: %s' % threading.currentThread().getName()
    -            print>>erf, 'Client Address: ', client_address
    -            print>>erf, 'Request: ', repr(request)
    +            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>>erf, '\n*** Unrecoverable, server exiting!'
    -            print>>erf, '-'*40
    +            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)
    +request_queue = queue.Queue(0)
    +response_queue = queue.Queue(0)
     
     
     class SocketIO(object):
    @@ -126,7 +130,7 @@ class SocketIO(object):
         nextseq = 0
     
         def __init__(self, sock, objtable=None, debugging=None):
    -        self.sockthread = threading.currentThread()
    +        self.sockthread = threading.current_thread()
             if debugging is not None:
                 self.debugging = debugging
             self.sock = sock
    @@ -149,10 +153,10 @@ def exithook(self):
         def debug(self, *args):
             if not self.debugging:
                 return
    -        s = self.location + " " + str(threading.currentThread().getName())
    +        s = self.location + " " + str(threading.current_thread().name)
             for a in args:
                 s = s + " " + str(a)
    -        print>>sys.__stderr__, s
    +        print(s, file=sys.__stderr__)
     
         def register(self, oid, object):
             self.objtable[oid] = object
    @@ -196,12 +200,16 @@ def localcall(self, seq, request):
                     return ("ERROR", "Unsupported message type: %s" % how)
             except SystemExit:
                 raise
    -        except socket.error:
    +        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>>sys.__stderr__, msg % (oid, method, args)
    +            print(msg % (oid, method, args), file=sys.__stderr__)
                 traceback.print_exc(file=sys.__stderr__)
                 return ("EXCEPTION", None)
     
    @@ -218,7 +226,7 @@ def remotequeue(self, oid, methodname, args, kwargs):
         def asynccall(self, oid, methodname, args, kwargs):
             request = ("CALL", (oid, methodname, args, kwargs))
             seq = self.newseq()
    -        if threading.currentThread() != self.sockthread:
    +        if threading.current_thread() != self.sockthread:
                 cvar = threading.Condition()
                 self.cvars[seq] = cvar
             self.debug(("asynccall:%d:" % seq), oid, methodname, args, kwargs)
    @@ -228,7 +236,7 @@ def asynccall(self, oid, methodname, args, kwargs):
         def asyncqueue(self, oid, methodname, args, kwargs):
             request = ("QUEUE", (oid, methodname, args, kwargs))
             seq = self.newseq()
    -        if threading.currentThread() != self.sockthread:
    +        if threading.current_thread() != self.sockthread:
                 cvar = threading.Condition()
                 self.cvars[seq] = cvar
             self.debug(("asyncqueue:%d:" % seq), oid, methodname, args, kwargs)
    @@ -256,8 +264,11 @@ def decoderesponse(self, response):
                 return None
             if how == "ERROR":
                 self.debug("decoderesponse: Internal ERROR:", what)
    -            raise RuntimeError, what
    -        raise SystemError, (how, what)
    +            raise RuntimeError(what)
    +        if how == "CALLEXC":
    +            self.debug("decoderesponse: Call Exception:", what)
    +            raise what
    +        raise SystemError(how, what)
     
         def decode_interrupthook(self):
             ""
    @@ -287,14 +298,14 @@ def getresponse(self, myseq, wait):
         def _proxify(self, obj):
             if isinstance(obj, RemoteProxy):
                 return RPCProxy(self, obj.oid)
    -        if isinstance(obj, types.ListType):
    -            return map(self._proxify, obj)
    +        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.currentThread() is self.sockthread:
    +        if threading.current_thread() is self.sockthread:
                 # this thread does all reading of requests or responses
                 while 1:
                     response = self.pollresponse(myseq, wait)
    @@ -321,9 +332,9 @@ def newseq(self):
         def putmessage(self, message):
             self.debug("putmessage:%d:" % message[0])
             try:
    -            s = pickle.dumps(message)
    +            s = dumps(message)
             except pickle.PicklingError:
    -            print >>sys.__stderr__, "Cannot pickle:", repr(message)
    +            print("Cannot pickle:", repr(message), file=sys.__stderr__)
                 raise
             s = struct.pack(" 0:
    @@ -331,40 +342,40 @@ def putmessage(self, message):
                     r, w, x = select.select([], [self.sock], [])
                     n = self.sock.send(s[:BUFSIZE])
                 except (AttributeError, TypeError):
    -                raise IOError, "socket no longer exists"
    +                raise OSError("socket no longer exists")
                 s = s[n:]
     
    -    buffer = ""
    +    buff = b''
         bufneed = 4
         bufstate = 0 # meaning: 0 => reading count; 1 => reading data
     
         def pollpacket(self, wait):
             self._stage0()
    -        if len(self.buffer) < self.bufneed:
    +        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 socket.error:
    +            except OSError:
                     raise EOFError
                 if len(s) == 0:
                     raise EOFError
    -            self.buffer += s
    +            self.buff += s
                 self._stage0()
             return self._stage1()
     
         def _stage0(self):
    -        if self.bufstate == 0 and len(self.buffer) >= 4:
    -            s = self.buffer[:4]
    -            self.buffer = self.buffer[4:]
    +        if self.bufstate == 0 and len(self.buff) >= 4:
    +            s = self.buff[:4]
    +            self.buff = self.buff[4:]
                 self.bufneed = struct.unpack("= self.bufneed:
    -            packet = self.buffer[:self.bufneed]
    -            self.buffer = self.buffer[self.bufneed:]
    +        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
    @@ -376,10 +387,10 @@ def pollmessage(self, wait):
             try:
                 message = pickle.loads(packet)
             except pickle.UnpicklingError:
    -            print >>sys.__stderr__, "-----------------------"
    -            print >>sys.__stderr__, "cannot unpickle packet:", repr(packet)
    +            print("-----------------------", file=sys.__stderr__)
    +            print("cannot unpickle packet:", repr(packet), file=sys.__stderr__)
                 traceback.print_stack(file=sys.__stderr__)
    -            print >>sys.__stderr__, "-----------------------"
    +            print("-----------------------", file=sys.__stderr__)
                 raise
             return message
     
    @@ -410,7 +421,7 @@ def pollresponse(self, myseq, wait):
                 # send queued response if there is one available
                 try:
                     qmsg = response_queue.get(0)
    -            except Queue.Empty:
    +            except queue.Empty:
                     pass
                 else:
                     seq, response = qmsg
    @@ -479,17 +490,20 @@ class RemoteObject(object):
         # Token mix-in class
         pass
     
    +
     def remoteref(obj):
         oid = id(obj)
         objecttable[oid] = obj
         return RemoteProxy(oid)
     
    +
     class RemoteProxy(object):
     
         def __init__(self, oid):
             self.oid = oid
     
    -class RPCHandler(SocketServer.BaseRequestHandler, SocketIO):
    +
    +class RPCHandler(socketserver.BaseRequestHandler, SocketIO):
     
         debugging = False
         location = "#S"  # Server
    @@ -497,15 +511,16 @@ class RPCHandler(SocketServer.BaseRequestHandler, SocketIO):
         def __init__(self, sock, addr, svr):
             svr.current_handler = self ## cgt xxx
             SocketIO.__init__(self, sock)
    -        SocketServer.BaseRequestHandler.__init__(self, sock, addr, svr)
    +        socketserver.BaseRequestHandler.__init__(self, sock, addr, svr)
     
         def handle(self):
    -        "handle() method required by SocketServer"
    +        "handle() method required by socketserver"
             self.mainloop()
     
         def get_remote_proxy(self, oid):
             return RPCProxy(self, oid)
     
    +
     class RPCClient(SocketIO):
     
         debugging = False
    @@ -521,16 +536,17 @@ def __init__(self, address, family=socket.AF_INET, type=socket.SOCK_STREAM):
         def accept(self):
             working_sock, address = self.listening_sock.accept()
             if self.debugging:
    -            print>>sys.__stderr__, "****** Connection request from ", address
    +            print("****** Connection request from ", address, file=sys.__stderr__)
             if address[0] == LOCALHOST:
                 SocketIO.__init__(self, working_sock)
             else:
    -            print>>sys.__stderr__, "** Invalid host: ", address
    -            raise socket.error
    +            print("** Invalid host: ", address, file=sys.__stderr__)
    +            raise OSError
     
         def get_remote_proxy(self, oid):
             return RPCProxy(self, oid)
     
    +
     class RPCProxy(object):
     
         __methods = None
    @@ -552,7 +568,7 @@ def __getattr__(self, name):
                                                (name,), {})
                 return value
             else:
    -            raise AttributeError, name
    +            raise AttributeError(name)
     
         def __getattributes(self):
             self.__attributes = self.sockio.remotecall(self.oid,
    @@ -567,20 +583,19 @@ def _getmethods(obj, methods):
         # Adds names to dictionary argument 'methods'
         for name in dir(obj):
             attr = getattr(obj, name)
    -        if hasattr(attr, '__call__'):
    +        if callable(attr):
                 methods[name] = 1
    -    if type(obj) == types.InstanceType:
    -        _getmethods(obj.__class__, methods)
    -    if type(obj) == types.ClassType:
    +    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 hasattr(attr, '__call__'):
    +        if not callable(attr):
                 attributes[name] = 1
     
    +
     class MethodProxy(object):
     
         def __init__(self, sockio, oid, name):
    @@ -588,10 +603,33 @@ def __init__(self, sockio, oid, name):
             self.oid = oid
             self.name = name
     
    -    def __call__(self, *args, **kwargs):
    +    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/PythonLib/full/idlelib/run.py b/PythonLib/full/idlelib/run.py
    index 518afabd..5bd84aad 100644
    --- a/PythonLib/full/idlelib/run.py
    +++ b/PythonLib/full/idlelib/run.py
    @@ -1,27 +1,56 @@
    -import sys
    +""" 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 socket
     import traceback
    -import thread
    +import _thread as thread
     import threading
    -import Queue
    -
    -from idlelib import CallTips
    -from idlelib import AutoComplete
    -
    -from idlelib import RemoteDebugger
    -from idlelib import RemoteObjectBrowser
    -from idlelib import StackViewer
    -from idlelib import rpc
    -from idlelib import PyShell
    -from idlelib import IOBinding
    +import warnings
     
    +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'
     
    -import warnings
    +
    +def idle_formatwarning(message, category, filename, lineno, line=None):
    +    """Format warnings the IDLE way."""
    +
    +    s = "\nWarning (from warnings module):\n"
    +    s += '  File \"%s\", line %s\n' % (filename, lineno)
    +    if line is None:
    +        line = linecache.getline(filename, lineno)
    +    line = line.strip()
    +    if line:
    +        s += "    %s\n" % line
    +    s += "%s: %s\n" % (category.__name__, message)
    +    return s
     
     def idle_showwarning_subproc(
             message, category, filename, lineno, file=None, line=None):
    @@ -32,9 +61,9 @@ def idle_showwarning_subproc(
         if file is None:
             file = sys.stderr
         try:
    -        file.write(PyShell.idle_formatwarning(
    +        file.write(idle_formatwarning(
                     message, category, filename, lineno, line))
    -    except IOError:
    +    except OSError:
             pass # the file (probably stderr) is invalid - this warning gets lost.
     
     _warnings_showwarning = None
    @@ -53,6 +82,13 @@ def capture_warnings(capture):
                 _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
    @@ -75,7 +111,7 @@ def main(del_exitfunc=False):
         MyHandler object.  That reference is saved as attribute rpchandler of the
         Executive instance.  The Executive methods have access to the reference and
         can pass it on to entities that they command
    -    (e.g. RemoteDebugger.Debugger.start_debugger()).  The latter, in turn, can
    +    (e.g. debugger_r.Debugger.start_debugger()).  The latter, in turn, can
         call MyHandler(SocketIO) register/unregister methods via the reference to
         register and unregister themselves.
     
    @@ -89,7 +125,8 @@ def main(del_exitfunc=False):
             assert(len(sys.argv) > 1)
             port = int(sys.argv[-1])
         except:
    -        print>>sys.stderr, "IDLE Subprocess: no IP port passed in sys.argv."
    +        print("IDLE Subprocess: no IP port passed in sys.argv.",
    +              file=sys.__stderr__)
             return
     
         capture_warnings(True)
    @@ -97,7 +134,7 @@ def main(del_exitfunc=False):
         sockthread = threading.Thread(target=manage_socket,
                                       name='SockThread',
                                       args=((LOCALHOST, port),))
    -    sockthread.setDaemon(True)
    +    sockthread.daemon = True
         sockthread.start()
         while 1:
             try:
    @@ -108,12 +145,17 @@ def main(del_exitfunc=False):
                         # exiting but got an extra KBI? Try again!
                         continue
                 try:
    -                seq, request = rpc.request_queue.get(block=True, timeout=0.05)
    -            except Queue.Empty:
    -                continue
    -            method, args, kwargs = request
    -            ret = method(*args, **kwargs)
    -            rpc.response_queue.put((seq, ret))
    +                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
    @@ -139,33 +181,33 @@ def manage_socket(address):
             try:
                 server = MyRPCServer(address, MyHandler)
                 break
    -        except socket.error as err:
    -            print>>sys.__stderr__,"IDLE Subprocess: socket error: "\
    -                                        + err.args[1] + ", retrying...."
    +        except OSError as err:
    +            print("IDLE Subprocess: OSError: " + err.args[1] +
    +                  ", retrying....", file=sys.__stderr__)
    +            socket_error = err
         else:
    -        print>>sys.__stderr__, "IDLE Subprocess: Connection to "\
    -                               "IDLE GUI failed, exiting."
    -        show_socket_error(err, address)
    +        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):
    -    import Tkinter
    -    import tkMessageBox
    -    root = Tkinter.Tk()
    +    "Display socket error from manage_socket."
    +    import tkinter
    +    from tkinter.messagebox import showerror
    +    root = tkinter.Tk()
         fix_scaling(root)
         root.withdraw()
    -    if err.args[0] == 61: # connection refused
    -        msg = "IDLE's subprocess can't connect to %s:%d.  This may be due "\
    -              "to your personal firewall configuration.  It is safe to "\
    -              "allow this internal connection because no data is visible on "\
    -              "external ports." % address
    -        tkMessageBox.showerror("IDLE Subprocess Error", msg, parent=root)
    -    else:
    -        tkMessageBox.showerror("IDLE Subprocess Error",
    -                               "Socket Error: %s" % err.args[1], parent=root)
    +    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():
    @@ -175,15 +217,34 @@ def print_exception():
         efile = sys.stderr
         typ, val, tb = excinfo = sys.exc_info()
         sys.last_type, sys.last_value, sys.last_traceback = excinfo
    -    tbe = traceback.extract_tb(tb)
    -    print>>efile, '\nTraceback (most recent call last):'
    -    exclude = ("run.py", "rpc.py", "threading.py", "Queue.py",
    -               "RemoteDebugger.py", "bdb.py")
    -    cleanup_traceback(tbe, exclude)
    -    traceback.print_list(tbe, file=efile)
    -    lines = traceback.format_exception_only(typ, val)
    -    for line in lines:
    -        print>>efile, line,
    +    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"
    @@ -205,53 +266,106 @@ def cleanup_traceback(tb, exclude):
         if len(tb) == 0:
             # exception was in IDLE internals, don't prune!
             tb[:] = orig_tb[:]
    -        print>>sys.stderr, "** IDLE Internal Exception: "
    +        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 fn.startswith(" 1.4:
    -        for name in tkFont.names(root):
    -            font = tkFont.Font(root=root, name=name, exists=True)
    +        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'] = int(round(-0.75*size))
    +                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):
    @@ -273,17 +387,105 @@ def handle_error(self, request, client_address):
                 thread.interrupt_main()
             except:
                 erf = sys.__stderr__
    -            print>>erf, '\n' + '-'*40
    -            print>>erf, 'Unhandled server exception!'
    -            print>>erf, 'Thread: %s' % threading.currentThread().getName()
    -            print>>erf, 'Client Address: ', client_address
    -            print>>erf, 'Request: ', repr(request)
    +            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>>erf, '\n*** Unrecoverable, server exiting!'
    -            print>>erf, '-'*40
    +            print('\n*** Unrecoverable, server exiting!', file=erf)
    +            print('-'*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):
    @@ -291,17 +493,24 @@ def handle(self):
             executive = Executive(self)
             self.register("exec", executive)
             self.console = self.get_remote_proxy("console")
    -        sys.stdin = PyShell.PseudoInputFile(self.console, "stdin",
    -                IOBinding.encoding)
    -        sys.stdout = PyShell.PseudoOutputFile(self.console, "stdout",
    -                IOBinding.encoding)
    -        sys.stderr = PyShell.PseudoOutputFile(self.console, "stderr",
    -                IOBinding.encoding)
    +        sys.stdin = 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)
     
    @@ -327,8 +536,8 @@ class Executive(object):
         def __init__(self, rpchandler):
             self.rpchandler = rpchandler
             self.locals = __main__.__dict__
    -        self.calltip = CallTips.CallTips()
    -        self.autocomplete = AutoComplete.AutoComplete()
    +        self.calltip = calltip.Calltip()
    +        self.autocomplete = autocomplete.AutoComplete()
     
         def runcode(self, code):
             global interruptable
    @@ -336,13 +545,15 @@ def runcode(self, code):
                 self.usr_exc_info = None
                 interruptable = True
                 try:
    -                exec code in self.locals
    +                exec(code, self.locals)
                 finally:
                     interruptable = False
    -        except SystemExit:
    -            # Scripts that raise SystemExit should just
    -            # return to the interactive prompt
    -            pass
    +        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.usr_exc_info = sys.exc_info()
                 if quitting:
    @@ -359,7 +570,7 @@ def interrupt_the_server(self):
                 thread.interrupt_main()
     
         def start_the_debugger(self, gui_adap_oid):
    -        return RemoteDebugger.start_debugger(self.rpchandler, gui_adap_oid)
    +        return debugger_r.start_debugger(self.rpchandler, gui_adap_oid)
     
         def stop_the_debugger(self, idb_adap_oid):
             "Unregister the Idb Adapter.  Link objects and Idb then subject to GC"
    @@ -383,7 +594,12 @@ def stackviewer(self, flist_oid=None):
                 tb = tb.tb_next
             sys.last_type = typ
             sys.last_value = val
    -        item = StackViewer.StackTreeItem(flist, tb)
    -        return RemoteObjectBrowser.remote_object_tree_item(item)
    +        item = stackviewer.StackTreeItem(flist, tb)
    +        return debugobj_r.remote_object_tree_item(item)
    +
    +
    +if __name__ == '__main__':
    +    from unittest import main
    +    main('idlelib.idle_test.test_run', verbosity=2)
     
    -capture_warnings(False)  # Make sure turned off; see issue 18081
    +capture_warnings(False)  # Make sure turned off; see bpo-18081.
    diff --git a/PythonLib/full/idlelib/runscript.py b/PythonLib/full/idlelib/runscript.py
    new file mode 100644
    index 00000000..a5410879
    --- /dev/null
    +++ b/PythonLib/full/idlelib/runscript.py
    @@ -0,0 +1,225 @@
    +"""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 tokenize
    +
    +import tkinter.messagebox as tkMessageBox
    +
    +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 = []
    +
    +        if macosx.isCocoaTk():
    +            self.editwin.text_frame.bind('<>', self._run_module_event)
    +
    +    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 ""
    +            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_module_event(self, event):
    +        if macosx.isCocoaTk():
    +            # Tk-Cocoa in MacOSX is broken until at least
    +            # Tk 8.5.9, and without this rather
    +            # crude workaround IDLE would hang when a user
    +            # tries to run a module using the keyboard shortcut
    +            # (the menu item works fine).
    +            self.editwin.text_frame.after(200,
    +                lambda: self.editwin.text_frame.event_generate(
    +                        '<>'))
    +            return 'break'
    +        else:
    +            return self._run_module_event(event)
    +
    +    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 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 = tkMessageBox.askokcancel(title="Save Before Run or Check",
    +                                           message=msg,
    +                                           default=tkMessageBox.OK,
    +                                           parent=self.editwin.text)
    +        return confirm
    +
    +    def errorbox(self, title, message):
    +        # XXX This should really be a function of EditorWindow...
    +        tkMessageBox.showerror(title, message, parent=self.editwin.text)
    +        self.editwin.text.focus_set()
    +
    +
    +if __name__ == "__main__":
    +    from unittest import main
    +    main('idlelib.idle_test.test_runscript', verbosity=2,)
    diff --git a/PythonLib/full/idlelib/scrolledlist.py b/PythonLib/full/idlelib/scrolledlist.py
    new file mode 100644
    index 00000000..71fd18ab
    --- /dev/null
    +++ b/PythonLib/full/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("", self.click_event)
    +        listbox.bind("", self.double_click_event)
    +        if macosx.isAquaTk():
    +            listbox.bind("", self.popup_event)
    +            listbox.bind("", self.popup_event)
    +        else:
    +            listbox.bind("", self.popup_event)
    +        listbox.bind("", self.up_event)
    +        listbox.bind("", 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/PythonLib/full/idlelib/search.py b/PythonLib/full/idlelib/search.py
    new file mode 100644
    index 00000000..b35f3b59
    --- /dev/null
    +++ b/PythonLib/full/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/PythonLib/full/idlelib/searchbase.py b/PythonLib/full/idlelib/searchbase.py
    new file mode 100644
    index 00000000..6fba0b8e
    --- /dev/null
    +++ b/PythonLib/full/idlelib/searchbase.py
    @@ -0,0 +1,203 @@
    +'''Define SearchDialogBase used by Search, Replace, and Grep dialogs.'''
    +
    +from tkinter import Toplevel
    +from tkinter.ttk import Frame, Entry, Label, Button, Checkbutton, Radiobutton
    +
    +
    +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().
    +        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("", self.default_command)
    +        top.bind("", self.close)
    +        top.protocol("WM_DELETE_WINDOW", self.close)
    +        top.wm_title(self.title)
    +        top.wm_iconname(self.icon)
    +        self.top = top
    +
    +        self.row = 0
    +        self.top.grid_columnconfigure(0, pad=2, weight=0)
    +        self.top.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.top, text=label_text)
    +        label.grid(row=self.row, column=0, sticky="nw")
    +        entry = Entry(self.top, 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.top, text=labeltext)
    +            label.grid(row=self.row, column=0, sticky="nw")
    +        else:
    +            label = ''
    +        frame = Frame(self.top)
    +        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.top)
    +        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/PythonLib/full/idlelib/searchengine.py b/PythonLib/full/idlelib/searchengine.py
    new file mode 100644
    index 00000000..911e7d46
    --- /dev/null
    +++ b/PythonLib/full/idlelib/searchengine.py
    @@ -0,0 +1,237 @@
    +'''Define SearchEngine for search dialogs.'''
    +import re
    +
    +from tkinter import StringVar, BooleanVar, TclError
    +import tkinter.messagebox as tkMessageBox
    +
    +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 what:
    +            args = what.args
    +            msg = args[0]
    +            col = args[1] if len(args) >= 2 else -1
    +            self.report_error(pat, msg, col)
    +            return None
    +        return prog
    +
    +    def report_error(self, pat, msg, col=-1):
    +        # Derived class could override this with something fancier
    +        msg = "Error: " + str(msg)
    +        if pat:
    +            msg = msg + "\nPattern: " + str(pat)
    +        if col >= 0:
    +            msg = msg + "\nOffset: " + str(col)
    +        tkMessageBox.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/PythonLib/full/idlelib/sidebar.py b/PythonLib/full/idlelib/sidebar.py
    new file mode 100644
    index 00000000..41c09684
    --- /dev/null
    +++ b/PythonLib/full/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('',
    +                                 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('', 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('', 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'',
    +                               f'',
    +                               f'',
    +                               ):
    +                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'',
    +                               f'',
    +                               ):
    +                bind_mouse_event(event_name,
    +                                 target_event_name=f'')
    +
    +        # 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('', 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('', 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_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_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/PythonLib/full/idlelib/squeezer.py b/PythonLib/full/idlelib/squeezer.py
    new file mode 100644
    index 00000000..be1538a2
    --- /dev/null
    +++ b/PythonLib/full/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
    +import tkinter.messagebox as tkMessageBox
    +
    +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("", self.expand)
    +        if macosx.isAquaTk():
    +            # AquaTk defines <2> as the right button, not <3>.
    +            self.bind("", self.context_menu_event)
    +        else:
    +            self.bind("", 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 = tkMessageBox.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=tkMessageBox.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/PythonLib/full/idlelib/stackviewer.py b/PythonLib/full/idlelib/stackviewer.py
    new file mode 100644
    index 00000000..94ffb4ef
    --- /dev/null
    +++ b/PythonLib/full/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("", frame.f_locals, self.flist)
    +            sublist.append(item)
    +        item = VariablesTreeItem("", 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/PythonLib/full/idlelib/statusbar.py b/PythonLib/full/idlelib/statusbar.py
    new file mode 100644
    index 00000000..c071f898
    --- /dev/null
    +++ b/PythonLib/full/idlelib/statusbar.py
    @@ -0,0 +1,49 @@
    +from tkinter import Frame, Label
    +
    +
    +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, Frame, Text, Button
    +    top = Toplevel(parent)
    +    x, y = map(int, parent.geometry().split('+')[1:])
    +    top.geometry("+%d+%d" %(x, y + 175))
    +    top.title("Test multistatus bar")
    +    frame = Frame(top)
    +    text = Text(frame, height=5, width=40)
    +    text.pack()
    +    msb = MultiStatusBar(frame)
    +    msb.set_label("one", "hello")
    +    msb.set_label("two", "world")
    +    msb.pack(side='bottom', fill='x')
    +
    +    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/PythonLib/full/idlelib/tabbedpages.py b/PythonLib/full/idlelib/tabbedpages.py
    deleted file mode 100644
    index 0723d945..00000000
    --- a/PythonLib/full/idlelib/tabbedpages.py
    +++ /dev/null
    @@ -1,498 +0,0 @@
    -"""An implementation of tabbed pages using only standard Tkinter.
    -
    -Originally developed for use in IDLE. Based on tabpage.py.
    -
    -Classes exported:
    -TabbedPageSet -- A Tkinter implementation of a tabbed-page widget.
    -TabSet -- A widget containing tabs (buttons) in one or more rows.
    -
    -"""
    -from Tkinter import *
    -
    -class InvalidNameError(Exception): pass
    -class AlreadyExistsError(Exception): pass
    -
    -
    -class TabSet(Frame):
    -    """A widget containing tabs (buttons) in one or more rows.
    -
    -    Only one tab may be selected at a time.
    -
    -    """
    -    def __init__(self, page_set, select_command,
    -                 tabs=None, n_rows=1, max_tabs_per_row=5,
    -                 expand_tabs=False, **kw):
    -        """Constructor arguments:
    -
    -        select_command -- A callable which will be called when a tab is
    -        selected. It is called with the name of the selected tab as an
    -        argument.
    -
    -        tabs -- A list of strings, the names of the tabs. Should be specified in
    -        the desired tab order. The first tab will be the default and first
    -        active tab. If tabs is None or empty, the TabSet will be initialized
    -        empty.
    -
    -        n_rows -- Number of rows of tabs to be shown. If n_rows <= 0 or is
    -        None, then the number of rows will be decided by TabSet. See
    -        _arrange_tabs() for details.
    -
    -        max_tabs_per_row -- Used for deciding how many rows of tabs are needed,
    -        when the number of rows is not constant. See _arrange_tabs() for
    -        details.
    -
    -        """
    -        Frame.__init__(self, page_set, **kw)
    -        self.select_command = select_command
    -        self.n_rows = n_rows
    -        self.max_tabs_per_row = max_tabs_per_row
    -        self.expand_tabs = expand_tabs
    -        self.page_set = page_set
    -
    -        self._tabs = {}
    -        self._tab2row = {}
    -        if tabs:
    -            self._tab_names = list(tabs)
    -        else:
    -            self._tab_names = []
    -        self._selected_tab = None
    -        self._tab_rows = []
    -
    -        self.padding_frame = Frame(self, height=2,
    -                                   borderwidth=0, relief=FLAT,
    -                                   background=self.cget('background'))
    -        self.padding_frame.pack(side=TOP, fill=X, expand=False)
    -
    -        self._arrange_tabs()
    -
    -    def add_tab(self, tab_name):
    -        """Add a new tab with the name given in tab_name."""
    -        if not tab_name:
    -            raise InvalidNameError("Invalid Tab name: '%s'" % tab_name)
    -        if tab_name in self._tab_names:
    -            raise AlreadyExistsError("Tab named '%s' already exists" %tab_name)
    -
    -        self._tab_names.append(tab_name)
    -        self._arrange_tabs()
    -
    -    def remove_tab(self, tab_name):
    -        """Remove the tab named """
    -        if not tab_name in self._tab_names:
    -            raise KeyError("No such Tab: '%s" % page_name)
    -
    -        self._tab_names.remove(tab_name)
    -        self._arrange_tabs()
    -
    -    def set_selected_tab(self, tab_name):
    -        """Show the tab named  as the selected one"""
    -        if tab_name == self._selected_tab:
    -            return
    -        if tab_name is not None and tab_name not in self._tabs:
    -            raise KeyError("No such Tab: '%s" % page_name)
    -
    -        # deselect the current selected tab
    -        if self._selected_tab is not None:
    -            self._tabs[self._selected_tab].set_normal()
    -        self._selected_tab = None
    -
    -        if tab_name is not None:
    -            # activate the tab named tab_name
    -            self._selected_tab = tab_name
    -            tab = self._tabs[tab_name]
    -            tab.set_selected()
    -            # move the tab row with the selected tab to the bottom
    -            tab_row = self._tab2row[tab]
    -            tab_row.pack_forget()
    -            tab_row.pack(side=TOP, fill=X, expand=0)
    -
    -    def _add_tab_row(self, tab_names, expand_tabs):
    -        if not tab_names:
    -            return
    -
    -        tab_row = Frame(self)
    -        tab_row.pack(side=TOP, fill=X, expand=0)
    -        self._tab_rows.append(tab_row)
    -
    -        for tab_name in tab_names:
    -            tab = TabSet.TabButton(tab_name, self.select_command,
    -                                   tab_row, self)
    -            if expand_tabs:
    -                tab.pack(side=LEFT, fill=X, expand=True)
    -            else:
    -                tab.pack(side=LEFT)
    -            self._tabs[tab_name] = tab
    -            self._tab2row[tab] = tab_row
    -
    -        # tab is the last one created in the above loop
    -        tab.is_last_in_row = True
    -
    -    def _reset_tab_rows(self):
    -        while self._tab_rows:
    -            tab_row = self._tab_rows.pop()
    -            tab_row.destroy()
    -        self._tab2row = {}
    -
    -    def _arrange_tabs(self):
    -        """
    -        Arrange the tabs in rows, in the order in which they were added.
    -
    -        If n_rows >= 1, this will be the number of rows used. Otherwise the
    -        number of rows will be calculated according to the number of tabs and
    -        max_tabs_per_row. In this case, the number of rows may change when
    -        adding/removing tabs.
    -
    -        """
    -        # remove all tabs and rows
    -        for tab_name in self._tabs.keys():
    -            self._tabs.pop(tab_name).destroy()
    -        self._reset_tab_rows()
    -
    -        if not self._tab_names:
    -            return
    -
    -        if self.n_rows is not None and self.n_rows > 0:
    -            n_rows = self.n_rows
    -        else:
    -            # calculate the required number of rows
    -            n_rows = (len(self._tab_names) - 1) // self.max_tabs_per_row + 1
    -
    -        # not expanding the tabs with more than one row is very ugly
    -        expand_tabs = self.expand_tabs or n_rows > 1
    -        i = 0 # index in self._tab_names
    -        for row_index in xrange(n_rows):
    -            # calculate required number of tabs in this row
    -            n_tabs = (len(self._tab_names) - i - 1) // (n_rows - row_index) + 1
    -            tab_names = self._tab_names[i:i + n_tabs]
    -            i += n_tabs
    -            self._add_tab_row(tab_names, expand_tabs)
    -
    -        # re-select selected tab so it is properly displayed
    -        selected = self._selected_tab
    -        self.set_selected_tab(None)
    -        if selected in self._tab_names:
    -            self.set_selected_tab(selected)
    -
    -    class TabButton(Frame):
    -        """A simple tab-like widget."""
    -
    -        bw = 2 # borderwidth
    -
    -        def __init__(self, name, select_command, tab_row, tab_set):
    -            """Constructor arguments:
    -
    -            name -- The tab's name, which will appear in its button.
    -
    -            select_command -- The command to be called upon selection of the
    -            tab. It is called with the tab's name as an argument.
    -
    -            """
    -            Frame.__init__(self, tab_row, borderwidth=self.bw, relief=RAISED)
    -
    -            self.name = name
    -            self.select_command = select_command
    -            self.tab_set = tab_set
    -            self.is_last_in_row = False
    -
    -            self.button = Radiobutton(
    -                self, text=name, command=self._select_event,
    -                padx=5, pady=1, takefocus=FALSE, indicatoron=FALSE,
    -                highlightthickness=0, selectcolor='', borderwidth=0)
    -            self.button.pack(side=LEFT, fill=X, expand=True)
    -
    -            self._init_masks()
    -            self.set_normal()
    -
    -        def _select_event(self, *args):
    -            """Event handler for tab selection.
    -
    -            With TabbedPageSet, this calls TabbedPageSet.change_page, so that
    -            selecting a tab changes the page.
    -
    -            Note that this does -not- call set_selected -- it will be called by
    -            TabSet.set_selected_tab, which should be called when whatever the
    -            tabs are related to changes.
    -
    -            """
    -            self.select_command(self.name)
    -            return
    -
    -        def set_selected(self):
    -            """Assume selected look"""
    -            self._place_masks(selected=True)
    -
    -        def set_normal(self):
    -            """Assume normal look"""
    -            self._place_masks(selected=False)
    -
    -        def _init_masks(self):
    -            page_set = self.tab_set.page_set
    -            background = page_set.pages_frame.cget('background')
    -            # mask replaces the middle of the border with the background color
    -            self.mask = Frame(page_set, borderwidth=0, relief=FLAT,
    -                              background=background)
    -            # mskl replaces the bottom-left corner of the border with a normal
    -            # left border
    -            self.mskl = Frame(page_set, borderwidth=0, relief=FLAT,
    -                              background=background)
    -            self.mskl.ml = Frame(self.mskl, borderwidth=self.bw,
    -                                 relief=RAISED)
    -            self.mskl.ml.place(x=0, y=-self.bw,
    -                               width=2*self.bw, height=self.bw*4)
    -            # mskr replaces the bottom-right corner of the border with a normal
    -            # right border
    -            self.mskr = Frame(page_set, borderwidth=0, relief=FLAT,
    -                              background=background)
    -            self.mskr.mr = Frame(self.mskr, borderwidth=self.bw,
    -                                 relief=RAISED)
    -
    -        def _place_masks(self, selected=False):
    -            height = self.bw
    -            if selected:
    -                height += self.bw
    -
    -            self.mask.place(in_=self,
    -                            relx=0.0, x=0,
    -                            rely=1.0, y=0,
    -                            relwidth=1.0, width=0,
    -                            relheight=0.0, height=height)
    -
    -            self.mskl.place(in_=self,
    -                            relx=0.0, x=-self.bw,
    -                            rely=1.0, y=0,
    -                            relwidth=0.0, width=self.bw,
    -                            relheight=0.0, height=height)
    -
    -            page_set = self.tab_set.page_set
    -            if selected and ((not self.is_last_in_row) or
    -                             (self.winfo_rootx() + self.winfo_width() <
    -                              page_set.winfo_rootx() + page_set.winfo_width())
    -                             ):
    -                # for a selected tab, if its rightmost edge isn't on the
    -                # rightmost edge of the page set, the right mask should be one
    -                # borderwidth shorter (vertically)
    -                height -= self.bw
    -
    -            self.mskr.place(in_=self,
    -                            relx=1.0, x=0,
    -                            rely=1.0, y=0,
    -                            relwidth=0.0, width=self.bw,
    -                            relheight=0.0, height=height)
    -
    -            self.mskr.mr.place(x=-self.bw, y=-self.bw,
    -                               width=2*self.bw, height=height + self.bw*2)
    -
    -            # finally, lower the tab set so that all of the frames we just
    -            # placed hide it
    -            self.tab_set.lower()
    -
    -class TabbedPageSet(Frame):
    -    """A Tkinter tabbed-pane widget.
    -
    -    Constains set of 'pages' (or 'panes') with tabs above for selecting which
    -    page is displayed. Only one page will be displayed at a time.
    -
    -    Pages may be accessed through the 'pages' attribute, which is a dictionary
    -    of pages, using the name given as the key. A page is an instance of a
    -    subclass of Tk's Frame widget.
    -
    -    The page widgets will be created (and destroyed when required) by the
    -    TabbedPageSet. Do not call the page's pack/place/grid/destroy methods.
    -
    -    Pages may be added or removed at any time using the add_page() and
    -    remove_page() methods.
    -
    -    """
    -    class Page(object):
    -        """Abstract base class for TabbedPageSet's pages.
    -
    -        Subclasses must override the _show() and _hide() methods.
    -
    -        """
    -        uses_grid = False
    -
    -        def __init__(self, page_set):
    -            self.frame = Frame(page_set, borderwidth=2, relief=RAISED)
    -
    -        def _show(self):
    -            raise NotImplementedError
    -
    -        def _hide(self):
    -            raise NotImplementedError
    -
    -    class PageRemove(Page):
    -        """Page class using the grid placement manager's "remove" mechanism."""
    -        uses_grid = True
    -
    -        def _show(self):
    -            self.frame.grid(row=0, column=0, sticky=NSEW)
    -
    -        def _hide(self):
    -            self.frame.grid_remove()
    -
    -    class PageLift(Page):
    -        """Page class using the grid placement manager's "lift" mechanism."""
    -        uses_grid = True
    -
    -        def __init__(self, page_set):
    -            super(TabbedPageSet.PageLift, self).__init__(page_set)
    -            self.frame.grid(row=0, column=0, sticky=NSEW)
    -            self.frame.lower()
    -
    -        def _show(self):
    -            self.frame.lift()
    -
    -        def _hide(self):
    -            self.frame.lower()
    -
    -    class PagePackForget(Page):
    -        """Page class using the pack placement manager's "forget" mechanism."""
    -        def _show(self):
    -            self.frame.pack(fill=BOTH, expand=True)
    -
    -        def _hide(self):
    -            self.frame.pack_forget()
    -
    -    def __init__(self, parent, page_names=None, page_class=PageLift,
    -                 n_rows=1, max_tabs_per_row=5, expand_tabs=False,
    -                 **kw):
    -        """Constructor arguments:
    -
    -        page_names -- A list of strings, each will be the dictionary key to a
    -        page's widget, and the name displayed on the page's tab. Should be
    -        specified in the desired page order. The first page will be the default
    -        and first active page. If page_names is None or empty, the
    -        TabbedPageSet will be initialized empty.
    -
    -        n_rows, max_tabs_per_row -- Parameters for the TabSet which will
    -        manage the tabs. See TabSet's docs for details.
    -
    -        page_class -- Pages can be shown/hidden using three mechanisms:
    -
    -        * PageLift - All pages will be rendered one on top of the other. When
    -          a page is selected, it will be brought to the top, thus hiding all
    -          other pages. Using this method, the TabbedPageSet will not be resized
    -          when pages are switched. (It may still be resized when pages are
    -          added/removed.)
    -
    -        * PageRemove - When a page is selected, the currently showing page is
    -          hidden, and the new page shown in its place. Using this method, the
    -          TabbedPageSet may resize when pages are changed.
    -
    -        * PagePackForget - This mechanism uses the pack placement manager.
    -          When a page is shown it is packed, and when it is hidden it is
    -          unpacked (i.e. pack_forget). This mechanism may also cause the
    -          TabbedPageSet to resize when the page is changed.
    -
    -        """
    -        Frame.__init__(self, parent, **kw)
    -
    -        self.page_class = page_class
    -        self.pages = {}
    -        self._pages_order = []
    -        self._current_page = None
    -        self._default_page = None
    -
    -        self.columnconfigure(0, weight=1)
    -        self.rowconfigure(1, weight=1)
    -
    -        self.pages_frame = Frame(self)
    -        self.pages_frame.grid(row=1, column=0, sticky=NSEW)
    -        if self.page_class.uses_grid:
    -            self.pages_frame.columnconfigure(0, weight=1)
    -            self.pages_frame.rowconfigure(0, weight=1)
    -
    -        # the order of the following commands is important
    -        self._tab_set = TabSet(self, self.change_page, n_rows=n_rows,
    -                               max_tabs_per_row=max_tabs_per_row,
    -                               expand_tabs=expand_tabs)
    -        if page_names:
    -            for name in page_names:
    -                self.add_page(name)
    -        self._tab_set.grid(row=0, column=0, sticky=NSEW)
    -
    -        self.change_page(self._default_page)
    -
    -    def add_page(self, page_name):
    -        """Add a new page with the name given in page_name."""
    -        if not page_name:
    -            raise InvalidNameError("Invalid TabPage name: '%s'" % page_name)
    -        if page_name in self.pages:
    -            raise AlreadyExistsError(
    -                "TabPage named '%s' already exists" % page_name)
    -
    -        self.pages[page_name] = self.page_class(self.pages_frame)
    -        self._pages_order.append(page_name)
    -        self._tab_set.add_tab(page_name)
    -
    -        if len(self.pages) == 1: # adding first page
    -            self._default_page = page_name
    -            self.change_page(page_name)
    -
    -    def remove_page(self, page_name):
    -        """Destroy the page whose name is given in page_name."""
    -        if not page_name in self.pages:
    -            raise KeyError("No such TabPage: '%s" % page_name)
    -
    -        self._pages_order.remove(page_name)
    -
    -        # handle removing last remaining, default, or currently shown page
    -        if len(self._pages_order) > 0:
    -            if page_name == self._default_page:
    -                # set a new default page
    -                self._default_page = self._pages_order[0]
    -        else:
    -            self._default_page = None
    -
    -        if page_name == self._current_page:
    -            self.change_page(self._default_page)
    -
    -        self._tab_set.remove_tab(page_name)
    -        page = self.pages.pop(page_name)
    -        page.frame.destroy()
    -
    -    def change_page(self, page_name):
    -        """Show the page whose name is given in page_name."""
    -        if self._current_page == page_name:
    -            return
    -        if page_name is not None and page_name not in self.pages:
    -            raise KeyError("No such TabPage: '%s'" % page_name)
    -
    -        if self._current_page is not None:
    -            self.pages[self._current_page]._hide()
    -        self._current_page = None
    -
    -        if page_name is not None:
    -            self._current_page = page_name
    -            self.pages[page_name]._show()
    -
    -        self._tab_set.set_selected_tab(page_name)
    -
    -def _tabbed_pages(parent):
    -    # test dialog
    -    root=Tk()
    -    width, height, x, y = list(map(int, re.split('[x+]', parent.geometry())))
    -    root.geometry("+%d+%d"%(x, y + 175))
    -    root.title("Test tabbed pages")
    -    tabPage=TabbedPageSet(root, page_names=['Foobar','Baz'], n_rows=0,
    -                          expand_tabs=False,
    -                          )
    -    tabPage.pack(side=TOP, expand=TRUE, fill=BOTH)
    -    Label(tabPage.pages['Foobar'].frame, text='Foo', pady=20).pack()
    -    Label(tabPage.pages['Foobar'].frame, text='Bar', pady=20).pack()
    -    Label(tabPage.pages['Baz'].frame, text='Baz').pack()
    -    entryPgName=Entry(root)
    -    buttonAdd=Button(root, text='Add Page',
    -            command=lambda:tabPage.add_page(entryPgName.get()))
    -    buttonRemove=Button(root, text='Remove Page',
    -            command=lambda:tabPage.remove_page(entryPgName.get()))
    -    labelPgName=Label(root, text='name of page to add/remove:')
    -    buttonAdd.pack(padx=5, pady=5)
    -    buttonRemove.pack(padx=5, pady=5)
    -    labelPgName.pack(padx=5)
    -    entryPgName.pack(padx=5)
    -    root.mainloop()
    -
    -
    -if __name__ == '__main__':
    -    from idlelib.idle_test.htest import run
    -    run(_tabbed_pages)
    diff --git a/PythonLib/full/idlelib/textView.py b/PythonLib/full/idlelib/textView.py
    deleted file mode 100644
    index ec837f81..00000000
    --- a/PythonLib/full/idlelib/textView.py
    +++ /dev/null
    @@ -1,97 +0,0 @@
    -"""Simple text browser for IDLE
    -
    -"""
    -
    -from Tkinter import *
    -import tkMessageBox
    -
    -class TextViewer(Toplevel):
    -    """A simple text viewer dialog for IDLE
    -
    -    """
    -    def __init__(self, parent, title, text, modal=True, _htest=False):
    -        """Show the given text in a scrollable window with a 'close' button
    -
    -        If modal option set to False, user can interact with other windows,
    -        otherwise they will be unable to interact with other windows until
    -        the textview window is closed.
    -
    -        _htest - bool; change box location when running htest.
    -        """
    -        Toplevel.__init__(self, parent)
    -        self.configure(borderwidth=5)
    -        # place dialog below parent if running htest
    -        self.geometry("=%dx%d+%d+%d" % (750, 500,
    -                           parent.winfo_rootx() + 10,
    -                           parent.winfo_rooty() + (10 if not _htest else 100)))
    -        #elguavas - config placeholders til config stuff completed
    -        self.bg = '#ffffff'
    -        self.fg = '#000000'
    -
    -        self.CreateWidgets()
    -        self.title(title)
    -        self.protocol("WM_DELETE_WINDOW", self.Ok)
    -        self.parent = parent
    -        self.textView.focus_set()
    -        #key bindings for this dialog
    -        self.bind('',self.Ok) #dismiss dialog
    -        self.bind('',self.Ok) #dismiss dialog
    -        self.textView.insert(0.0, text)
    -        self.textView.config(state=DISABLED)
    -
    -        self.is_modal = modal
    -        if self.is_modal:
    -            self.transient(parent)
    -            self.grab_set()
    -            self.wait_window()
    -
    -    def CreateWidgets(self):
    -        frameText = Frame(self, relief=SUNKEN, height=700)
    -        frameButtons = Frame(self)
    -        self.buttonOk = Button(frameButtons, text='Close',
    -                               command=self.Ok, takefocus=FALSE)
    -        self.scrollbarView = Scrollbar(frameText, orient=VERTICAL,
    -                                       takefocus=FALSE, highlightthickness=0)
    -        self.textView = Text(frameText, wrap=WORD, highlightthickness=0,
    -                             fg=self.fg, bg=self.bg)
    -        self.scrollbarView.config(command=self.textView.yview)
    -        self.textView.config(yscrollcommand=self.scrollbarView.set)
    -        self.buttonOk.pack()
    -        self.scrollbarView.pack(side=RIGHT,fill=Y)
    -        self.textView.pack(side=LEFT,expand=TRUE,fill=BOTH)
    -        frameButtons.pack(side=BOTTOM,fill=X)
    -        frameText.pack(side=TOP,expand=TRUE,fill=BOTH)
    -
    -    def Ok(self, event=None):
    -        if self.is_modal:
    -            self.grab_release()
    -        self.destroy()
    -
    -
    -def view_text(parent, title, text, modal=True):
    -    return TextViewer(parent, title, text, modal)
    -
    -def view_file(parent, title, filename, encoding=None, modal=True):
    -    try:
    -        if encoding:
    -            import codecs
    -            textFile = codecs.open(filename, 'r')
    -        else:
    -            textFile = open(filename, 'r')
    -    except IOError:
    -        tkMessageBox.showerror(title='File Load Error',
    -                               message='Unable to load file %r .' % filename,
    -                               parent=parent)
    -    except UnicodeDecodeError as err:
    -        showerror(title='Unicode Decode Error',
    -                  message=str(err),
    -                  parent=parent)
    -    else:
    -        return view_text(parent, title, textFile.read(), modal)
    -
    -
    -if __name__ == '__main__':
    -    import unittest
    -    unittest.main('idlelib.idle_test.test_textview', verbosity=2, exit=False)
    -    from idlelib.idle_test.htest import run
    -    run(TextViewer)
    diff --git a/PythonLib/full/idlelib/textview.py b/PythonLib/full/idlelib/textview.py
    new file mode 100644
    index 00000000..808a2aef
    --- /dev/null
    +++ b/PythonLib/full/idlelib/textview.py
    @@ -0,0 +1,194 @@
    +"""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 functools import update_wrapper
    +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('', self.ok)
    +        self.bind('', 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/PythonLib/full/idlelib/tooltip.py b/PythonLib/full/idlelib/tooltip.py
    new file mode 100644
    index 00000000..69658264
    --- /dev/null
    +++ b/PythonLib/full/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(object):
    +    """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("", self._show_event)
    +        self._id2 = self.anchor_widget.bind("", self._hide_event)
    +        self._id3 = self.anchor_widget.bind("