Unverified Kaydet (Commit) 604e7b99 authored tarafından Tal Einat's avatar Tal Einat Kaydeden (comit) GitHub

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

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