Commit 604e7b99 authored by Tal Einat's avatar Tal Einat Committed by GitHub

bpo-1529353: IDLE: squeeze large output in the shell (GH-7626)

parent 5b3cbcd4
......@@ -66,6 +66,9 @@ font-size= 10
font-bold= 0
encoding= none
[PyShell]
auto-squeeze-min-lines= 50
[Indent]
use-spaces= 1
num-spaces= 4
......
......@@ -30,10 +30,12 @@ from idlelib.autocomplete import AutoComplete
from idlelib.codecontext import CodeContext
from idlelib.parenmatch import ParenMatch
from idlelib.paragraph import FormatParagraph
from idlelib.squeezer import Squeezer
changes = ConfigChanges()
# Reload changed options in the following classes.
reloadables = (AutoComplete, CodeContext, ParenMatch, FormatParagraph)
reloadables = (AutoComplete, CodeContext, ParenMatch, FormatParagraph,
Squeezer)
class ConfigDialog(Toplevel):
......@@ -1748,9 +1750,9 @@ class KeysPage(Frame):
self.customlist.SetMenu(item_list, item_list[0])
# Revert to default key set.
self.keyset_source.set(idleConf.defaultCfg['main']
.Get('Keys', 'default'))
.Get('Keys', 'default'))
self.builtin_name.set(idleConf.defaultCfg['main'].Get('Keys', 'name')
or idleConf.default_keys())
or idleConf.default_keys())
# User can't back out of these changes, they must be applied now.
changes.save_all()
self.cd.save_all_changed_extensions()
......@@ -1817,6 +1819,10 @@ class GenPage(Frame):
frame_context: Frame
context_title: Label
(*)context_int: Entry - context_lines
frame_shell: LabelFrame
frame_auto_squeeze_min_lines: Frame
auto_squeeze_min_lines_title: Label
(*)auto_squeeze_min_lines_int: Entry - auto_squeeze_min_lines
frame_help: LabelFrame
frame_helplist: Frame
frame_helplist_buttons: Frame
......@@ -1842,6 +1848,9 @@ class GenPage(Frame):
self.paren_bell = tracers.add(
BooleanVar(self), ('extensions', 'ParenMatch', 'bell'))
self.auto_squeeze_min_lines = tracers.add(
StringVar(self), ('main', 'PyShell', 'auto-squeeze-min-lines'))
self.autosave = tracers.add(
IntVar(self), ('main', 'General', 'autosave'))
self.format_width = tracers.add(
......@@ -1855,8 +1864,10 @@ class GenPage(Frame):
text=' Window Preferences')
frame_editor = LabelFrame(self, borderwidth=2, relief=GROOVE,
text=' Editor Preferences')
frame_shell = LabelFrame(self, borderwidth=2, relief=GROOVE,
text=' Shell Preferences')
frame_help = LabelFrame(self, borderwidth=2, relief=GROOVE,
text=' Additional Help Sources ')
text=' Additional Help Sources ')
# Frame_window.
frame_run = Frame(frame_window, borderwidth=0)
startup_title = Label(frame_run, text='At Startup')
......@@ -1918,6 +1929,13 @@ class GenPage(Frame):
self.context_int = Entry(
frame_context, textvariable=self.context_lines, width=3)
# Frame_shell.
frame_auto_squeeze_min_lines = Frame(frame_shell, borderwidth=0)
auto_squeeze_min_lines_title = Label(frame_auto_squeeze_min_lines,
text='Auto-Squeeze Min. Lines:')
self.auto_squeeze_min_lines_int = Entry(
frame_auto_squeeze_min_lines, width=4,
textvariable=self.auto_squeeze_min_lines)
# frame_help.
frame_helplist = Frame(frame_help)
......@@ -1943,6 +1961,7 @@ class GenPage(Frame):
# Body.
frame_window.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
frame_editor.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
frame_shell.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
frame_help.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
# frame_run.
frame_run.pack(side=TOP, padx=5, pady=0, fill=X)
......@@ -1983,6 +2002,11 @@ class GenPage(Frame):
context_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
self.context_int.pack(side=TOP, padx=5, pady=5)
# frame_auto_squeeze_min_lines
frame_auto_squeeze_min_lines.pack(side=TOP, padx=5, pady=0, fill=X)
auto_squeeze_min_lines_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
self.auto_squeeze_min_lines_int.pack(side=TOP, padx=5, pady=5)
# frame_help.
frame_helplist_buttons.pack(side=RIGHT, padx=5, pady=5, fill=Y)
frame_helplist.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
......@@ -2018,6 +2042,10 @@ class GenPage(Frame):
self.context_lines.set(idleConf.GetOption(
'extensions', 'CodeContext', 'maxlines', type='int'))
# Set variables for shell windows.
self.auto_squeeze_min_lines.set(idleConf.GetOption(
'main', 'PyShell', 'auto-squeeze-min-lines', type='int'))
# Set additional help sources.
self.user_helplist = idleConf.GetAllExtraHelpSourcesList()
self.helplist.delete(0, 'end')
......@@ -2211,6 +2239,9 @@ long to highlight if cursor is not moved (0 means forever).
CodeContext: Maxlines is the maximum number of code context lines to
display when Code Context is turned on for an editor window.
Shell Preferences: Auto-Squeeze Min. Lines is the minimum number of lines
of output to automatically "squeeze".
'''
}
......
......@@ -2,9 +2,7 @@ import importlib.abc
import importlib.util
import os
import platform
import re
import string
import sys
import tokenize
import traceback
import webbrowser
......@@ -50,7 +48,6 @@ class EditorWindow(object):
from idlelib.undo import UndoDelegator
from idlelib.iomenu import IOBinding, encoding
from idlelib import mainmenu
from tkinter import Toplevel, EventType
from idlelib.statusbar import MultiStatusBar
from idlelib.autocomplete import AutoComplete
from idlelib.autoexpand import AutoExpand
......@@ -59,6 +56,7 @@ class EditorWindow(object):
from idlelib.paragraph import FormatParagraph
from idlelib.parenmatch import ParenMatch
from idlelib.rstrip import Rstrip
from idlelib.squeezer import Squeezer
from idlelib.zoomheight import ZoomHeight
filesystemencoding = sys.getfilesystemencoding() # for file names
......@@ -319,6 +317,9 @@ class EditorWindow(object):
text.bind("<<zoom-height>>", self.ZoomHeight(self).zoom_height_event)
text.bind("<<toggle-code-context>>",
self.CodeContext(self).toggle_code_context_event)
squeezer = self.Squeezer(self)
text.bind("<<squeeze-current-text>>",
squeezer.squeeze_current_text_event)
def _filename_to_unicode(self, filename):
"""Return filename as BMP unicode so diplayable in Tk."""
......
......@@ -163,7 +163,7 @@ _grep_dialog_spec = {
'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."
}
......
......@@ -356,11 +356,11 @@ class IdleConfTest(unittest.TestCase):
self.assertCountEqual(
conf.GetSectionList('default', 'main'),
['General', 'EditorWindow', 'Indent', 'Theme',
['General', 'EditorWindow', 'PyShell', 'Indent', 'Theme',
'Keys', 'History', 'HelpFiles'])
self.assertCountEqual(
conf.GetSectionList('user', 'main'),
['General', 'EditorWindow', 'Indent', 'Theme',
['General', 'EditorWindow', 'PyShell', 'Indent', 'Theme',
'Keys', 'History', 'HelpFiles'])
with self.assertRaises(config.InvalidConfigSet):
......@@ -452,7 +452,7 @@ class IdleConfTest(unittest.TestCase):
self.assertCountEqual(
conf.RemoveKeyBindNames(conf.GetSectionList('default', 'extensions')),
['AutoComplete', 'CodeContext', 'FormatParagraph', 'ParenMatch','ZzDummy'])
['AutoComplete', 'CodeContext', 'FormatParagraph', 'ParenMatch', 'ZzDummy'])
def test_get_extn_name_for_event(self):
userextn.read_string('''
......
This diff is collapsed.
......@@ -73,7 +73,6 @@ class TextFrameTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
"By itself, this tests that file parsed without exception."
cls.root = root = Tk()
root.withdraw()
cls.frame = tv.TextFrame(root, 'test text')
......@@ -126,11 +125,15 @@ class ViewFunctionTest(unittest.TestCase):
def test_bad_encoding(self):
p = os.path
fn = p.abspath(p.join(p.dirname(__file__), '..', 'CREDITS.txt'))
tv.showerror.title = None
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):
......
......@@ -856,6 +856,10 @@ class PyShell(OutputWindow):
("help", "_Help"),
]
# Extend right-click context menu
rmenu_specs = OutputWindow.rmenu_specs + [
("Squeeze", "<<squeeze-current-text>>"),
]
# New classes
from idlelib.history import History
......
This diff is collapsed.
"""Simple text browser for IDLE
"""
from tkinter import Toplevel, Text
from tkinter import Toplevel, Text, TclError,\
HORIZONTAL, VERTICAL, N, S, E, W
from tkinter.ttk import Frame, Scrollbar, Button
from tkinter.messagebox import showerror
from idlelib.colorizer import color_config
class AutoHiddenScrollbar(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 TextFrame(Frame):
"Display text with scrollbar."
def __init__(self, parent, rawtext):
def __init__(self, parent, rawtext, wrap='word'):
"""Create a frame for Textview.
parent - parent widget for this frame
......@@ -21,27 +41,39 @@ class TextFrame(Frame):
self['relief'] = 'sunken'
self['height'] = 700
self.text = text = Text(self, wrap='word', highlightthickness=0)
self.text = text = Text(self, wrap=wrap, highlightthickness=0)
color_config(text)
self.scroll = scroll = Scrollbar(self, orient='vertical',
takefocus=False, command=text.yview)
text['yscrollcommand'] = scroll.set
text.grid(row=0, column=0, sticky=N+S+E+W)
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
text.insert(0.0, rawtext)
text['state'] = 'disabled'
text.focus_set()
scroll.pack(side='right', fill='y')
text.pack(side='left', expand=True, fill='both')
# vertical scrollbar
self.yscroll = yscroll = AutoHiddenScrollbar(self, orient=VERTICAL,
takefocus=False,
command=text.yview)
text['yscrollcommand'] = yscroll.set
yscroll.grid(row=0, column=1, sticky=N+S)
if wrap == 'none':
# horizontal scrollbar
self.xscroll = xscroll = AutoHiddenScrollbar(self, orient=HORIZONTAL,
takefocus=False,
command=text.xview)
text['xscrollcommand'] = xscroll.set
xscroll.grid(row=1, column=0, sticky=E+W)
class ViewFrame(Frame):
"Display TextFrame and Close button."
def __init__(self, parent, text):
def __init__(self, parent, text, wrap='word'):
super().__init__(parent)
self.parent = parent
self.bind('<Return>', self.ok)
self.bind('<Escape>', self.ok)
self.textframe = TextFrame(self, text)
self.textframe = TextFrame(self, text, wrap=wrap)
self.button_ok = button_ok = Button(
self, text='Close', command=self.ok, takefocus=False)
self.textframe.pack(side='top', expand=True, fill='both')
......@@ -55,7 +87,7 @@ class ViewFrame(Frame):
class ViewWindow(Toplevel):
"A simple text viewer dialog for IDLE."
def __init__(self, parent, title, text, modal=True,
def __init__(self, parent, title, text, modal=True, wrap='word',
*, _htest=False, _utest=False):
"""Show the given text in a scrollable window with a 'close' button.
......@@ -65,6 +97,7 @@ class ViewWindow(Toplevel):
parent - parent of this dialog
title - string which is title of popup dialog
text - 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.
"""
......@@ -76,7 +109,7 @@ class ViewWindow(Toplevel):
self.geometry(f'=750x500+{x}+{y}')
self.title(title)
self.viewframe = ViewFrame(self, text)
self.viewframe = ViewFrame(self, text, wrap=wrap)
self.protocol("WM_DELETE_WINDOW", self.ok)
self.button_ok = button_ok = Button(self, text='Close',
command=self.ok, takefocus=False)
......@@ -96,20 +129,22 @@ class ViewWindow(Toplevel):
self.destroy()
def view_text(parent, title, text, modal=True, _utest=False):
def view_text(parent, title, text, 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
text - 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, text, modal, _utest=_utest)
return ViewWindow(parent, title, text, modal, wrap=wrap, _utest=_utest)
def view_file(parent, title, filename, encoding, modal=True, _utest=False):
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
......@@ -127,7 +162,8 @@ def view_file(parent, title, filename, encoding, modal=True, _utest=False):
message=str(err),
parent=parent)
else:
return view_text(parent, title, contents, modal, _utest=_utest)
return view_text(parent, title, contents, modal, wrap=wrap,
_utest=_utest)
return None
......
Enable "squeezing" of long outputs in the shell, to avoid performance
degradation and to clean up the history without losing it. Squeezed outputs
may be copied, viewed in a separate window, and "unsqueezed".
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment