From 25259921eab001ec8620a7ba46aaf47917ac9965 Mon Sep 17 00:00:00 2001 From: Tal Einat Date: Sat, 10 Nov 2018 23:40:15 +0200 Subject: [PATCH 01/25] bpo-35196: optimize Squeezer's write() interception --- Lib/idlelib/idle_test/test_squeezer.py | 104 +++++++++---------------- Lib/idlelib/squeezer.py | 78 +++++++++++++++---- 2 files changed, 100 insertions(+), 82 deletions(-) diff --git a/Lib/idlelib/idle_test/test_squeezer.py b/Lib/idlelib/idle_test/test_squeezer.py index da2c2dd50c65ad8..d48addb3c47f27e 100644 --- a/Lib/idlelib/idle_test/test_squeezer.py +++ b/Lib/idlelib/idle_test/test_squeezer.py @@ -115,61 +115,44 @@ 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() - return Squeezer(editor_window) + squeezer = Squeezer(editor_window) + squeezer.get_line_width = Mock(return_value=80) + squeezer.editwin.get_tk_tabwidth = Mock(return_value=8) + return squeezer + + def make_text_widget(self): + root = get_test_tk_root(self) + text_widget = Text(root) + text_widget["font"] = idleConf.GetFont(root, "main", "EditorWindow") + text_widget.mark_set("iomark", "1.0") + return text_widget def test_count_lines(self): - """Test Squeezer.count_lines() with various inputs. - - This checks that Squeezer.count_lines() calls the - count_lines_with_wrapping() function with the appropriate parameters. - """ - for tabwidth, linewidth in [(4, 80), (1, 79), (8, 80), (3, 120)]: - self._test_count_lines_helper(linewidth=linewidth, - tabwidth=tabwidth) - - def _prepare_mock_editwin_for_count_lines(self, editwin, - linewidth, tabwidth): - """Prepare a mock EditorWindow object for Squeezer.count_lines.""" - CHAR_WIDTH = 10 - BORDER_WIDTH = 2 - PADDING_WIDTH = 1 - - # Prepare all the required functionality on the mock EditorWindow object - # so that the calculations in Squeezer.count_lines() can run. - editwin.get_tk_tabwidth.return_value = tabwidth - editwin.text.winfo_width.return_value = \ - linewidth * CHAR_WIDTH + 2 * (BORDER_WIDTH + PADDING_WIDTH) - text_opts = { - 'border': BORDER_WIDTH, - 'padx': PADDING_WIDTH, - 'font': None, - } - editwin.text.cget = lambda opt: text_opts[opt] - - # monkey-path tkinter.font.Font with a mock object, so that - # Font.measure('0') returns CHAR_WIDTH - mock_font = Mock() - def measure(char): - if char == '0': - return CHAR_WIDTH - raise ValueError("measure should only be called on '0'!") - mock_font.return_value.measure = measure - patcher = patch('idlelib.squeezer.Font', mock_font) - patcher.start() - self.addCleanup(patcher.stop) - - def _test_count_lines_helper(self, linewidth, tabwidth): - """Helper for test_count_lines.""" + """Test Squeezer.count_lines() with various inputs.""" editwin = self.make_mock_editor_window() - self._prepare_mock_editwin_for_count_lines(editwin, linewidth, tabwidth) squeezer = self.make_squeezer_instance(editwin) - mock_count_lines = Mock(return_value=SENTINEL_VALUE) - text = 'TEXT' - with patch('idlelib.squeezer.count_lines_with_wrapping', - mock_count_lines): - self.assertIs(squeezer.count_lines(text), SENTINEL_VALUE) - mock_count_lines.assert_called_with(text, linewidth, tabwidth) + for text_code, tab_width, line_width, expected in [ + (r"'\n'", 8, 80, 1), + (r"'\n' * 3", 8, 80, 3), + (r"'a' * 40 + '\n'", 8, 80, 1), + (r"'a' * 80 + '\n'", 8, 80, 1), + # TODO: uncomment the next test case after bpo-35208 is fixed + # (r"'a' * 200 + '\n'", 8, 80, 3), + (r"'aa\t' * 20", 8, 80, 2), + (r"'aa\t' * 21", 8, 80, 3), + (r"'aa\t' * 20", 8, 40, 4), + (r"'aa\t' * 20", 4, 80, 1), + (r"'aa\t' * 21", 4, 80, 2), + ]: + with self.subTest(text_code=text_code, + tab_width=tab_width, + line_width=line_width, + expected=expected): + text = eval(text_code) + editwin.get_tk_tabwidth.return_value = tab_width + squeezer.get_line_width.return_value = line_width + self.assertEquals(squeezer.count_lines(text), expected) def test_init(self): """Test the creation of Squeezer instances.""" @@ -207,8 +190,6 @@ def test_write_not_stdout(self): def test_write_stdout(self): """Test Squeezer's overriding of the EditorWindow's write() method.""" editwin = self.make_mock_editor_window() - self._prepare_mock_editwin_for_count_lines(editwin, - linewidth=80, tabwidth=8) for text in ['', 'TEXT']: editwin.write = orig_write = Mock(return_value=SENTINEL_VALUE) @@ -232,9 +213,7 @@ def test_write_stdout(self): def test_auto_squeeze(self): """Test that the auto-squeezing creates an ExpandingButton properly.""" - root = get_test_tk_root(self) - text_widget = Text(root) - text_widget.mark_set("iomark", "1.0") + text_widget = self.make_text_widget() editwin = self.make_mock_editor_window() editwin.text = text_widget @@ -248,12 +227,9 @@ def test_auto_squeeze(self): def test_squeeze_current_text_event(self): """Test the squeeze_current_text event.""" - root = get_test_tk_root(self) - # squeezing text should work for both stdout and stderr for tag_name in ["stdout", "stderr"]: - text_widget = Text(root) - text_widget.mark_set("iomark", "1.0") + text_widget = self.make_text_widget() editwin = self.make_mock_editor_window() editwin.text = editwin.per.bottom = text_widget @@ -282,10 +258,7 @@ def test_squeeze_current_text_event(self): def test_squeeze_current_text_event_no_allowed_tags(self): """Test that the event doesn't squeeze text without a relevant tag.""" - root = get_test_tk_root(self) - - text_widget = Text(root) - text_widget.mark_set("iomark", "1.0") + text_widget = self.make_text_widget() editwin = self.make_mock_editor_window() editwin.text = editwin.per.bottom = text_widget @@ -307,10 +280,7 @@ def test_squeeze_current_text_event_no_allowed_tags(self): def test_squeeze_text_before_existing_squeezed_text(self): """Test squeezing text before existing squeezed text.""" - root = get_test_tk_root(self) - - text_widget = Text(root) - text_widget.mark_set("iomark", "1.0") + text_widget = self.make_text_widget() editwin = self.make_mock_editor_window() editwin.text = editwin.per.bottom = text_widget diff --git a/Lib/idlelib/squeezer.py b/Lib/idlelib/squeezer.py index 8960356799a4805..56cea71fa208cf8 100644 --- a/Lib/idlelib/squeezer.py +++ b/Lib/idlelib/squeezer.py @@ -15,6 +15,7 @@ messages and their tracebacks. """ import re +import weakref import tkinter as tk from tkinter.font import Font @@ -202,6 +203,8 @@ class Squeezer: This avoids IDLE's shell slowing down considerably, and even becoming completely unresponsive, when very long outputs are written. """ + _instance_weakrefs = [] + @classmethod def reload(cls): """Load class variables from config.""" @@ -210,6 +213,17 @@ def reload(cls): type="int", default=50, ) + # Loading the font info requires a Tk root, and we don't want to + # rely on Tkinter's "default root", so each instance will load + # font info separately using its editor windows's Tk root. + new_instance_weakrefs = [] + for inst_ref in cls._instance_weakrefs: + inst = inst_ref() + if inst is not None: + inst.load_font() + new_instance_weakrefs.append(inst_ref) + cls._instance_weakrefs = new_instance_weakrefs + def __init__(self, editwin): """Initialize settings for Squeezer. @@ -229,25 +243,56 @@ def __init__(self, editwin): # the actual Text widget. Squeezer, however, needs to make such changes. self.base_text = editwin.per.bottom + self._instance_weakrefs.append(weakref.ref(self)) + self.load_font() + + # 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 = [] from idlelib.pyshell import PyShell # done here to avoid import cycle if isinstance(editwin, PyShell): # If we get a PyShell instance, replace its 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 + # 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 + # Only auto-squeeze text with at least the minimum + # configured number of lines. + + # First, a very quick check to skip very short texts. + s_len = len(s) + auto_squeeze_min_lines = self.auto_squeeze_min_lines + if s_len < auto_squeeze_min_lines: + return write(s, tags) + + # Try another quick check to avoid calculating the actual + # number of lines, but only if the given text is not too + # long for it to possibly succeed. + line_width = self.get_line_width() + if s_len < auto_squeeze_min_lines * line_width: + # Check lower bound: Assume that all non-newline + # characters are in one long line, and that the + # tab width is 8. + n_newlines = s.count('\n') + n_tabs = s.count('\t') + n_lines_lower_bound = n_newlines + \ + (s_len - n_newlines + n_tabs * 7) // line_width + if n_lines_lower_bound < auto_squeeze_min_lines: + return write(s, tags) + numoflines = self.count_lines(s) if numoflines < self.auto_squeeze_min_lines: return write(s, tags) # create an ExpandingButton instance - expandingbutton = ExpandingButton(s, tags, numoflines, - self) + expandingbutton = ExpandingButton(s, tags, numoflines, self) # insert the ExpandingButton into the Text widget text.mark_gravity("iomark", tk.RIGHT) @@ -275,23 +320,26 @@ def count_lines(self, s): """ # Tab width is configurable tabwidth = self.editwin.get_tk_tabwidth() + linewidth = self.get_line_width() + return count_lines_with_wrapping(s, linewidth, tabwidth) - # Get the Text widget's size - linewidth = self.editwin.text.winfo_width() - # Deduct the border and padding - linewidth -= 2*sum([int(self.editwin.text.cget(opt)) - for opt in ('border', 'padx')]) + def get_line_width(self): + # The maximum line length in pixels: The width of the text widget, + # minus twice the border width and internal padding. + linewidth_pixels = \ + self.base_text.winfo_width() - self.window_width_delta - # Get the Text widget's font - font = Font(self.editwin.text, name=self.editwin.text.cget('font')) - # Divide the size of the Text widget by the font's width. + # Divide the width of the Text widget by the font's width. # According to Tk8.5 docs, the Text widget's width is set # according to the width of its font's '0' (zero) character, # so we will use this as an approximation. # see: http://www.tcl.tk/man/tcl8.5/TkCmd/text.htm#M-width - linewidth //= font.measure('0') + return linewidth_pixels // self.zero_char_width - return count_lines_with_wrapping(s, linewidth, tabwidth) + def load_font(self): + text = self.base_text + self.zero_char_width = \ + Font(text, name=text.cget('font')).measure('0') def squeeze_current_text_event(self, event): """squeeze-current-text event handler From 6b60df6e2625a69d5daeb02aee65b15d6633e7fb Mon Sep 17 00:00:00 2001 From: Tal Einat Date: Sat, 10 Nov 2018 23:40:52 +0200 Subject: [PATCH 02/25] remove dead, old test code from test_squeezer.py --- Lib/idlelib/idle_test/test_squeezer.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/Lib/idlelib/idle_test/test_squeezer.py b/Lib/idlelib/idle_test/test_squeezer.py index d48addb3c47f27e..361b6688eff1089 100644 --- a/Lib/idlelib/idle_test/test_squeezer.py +++ b/Lib/idlelib/idle_test/test_squeezer.py @@ -306,21 +306,6 @@ def test_squeeze_text_before_existing_squeezed_text(self): squeezer.expandingbuttons[1], )) - GetOptionSignature = namedtuple('GetOptionSignature', - 'configType section option default type warn_on_default raw') - @classmethod - def _make_sig(cls, configType, section, option, default=sentinel.NOT_GIVEN, - type=sentinel.NOT_GIVEN, - warn_on_default=sentinel.NOT_GIVEN, - raw=sentinel.NOT_GIVEN): - return cls.GetOptionSignature(configType, section, option, default, - type, warn_on_default, raw) - - @classmethod - def get_GetOption_signature(cls, mock_call_obj): - args, kwargs = mock_call_obj[-2:] - return cls._make_sig(*args, **kwargs) - def test_reload(self): """Test the reload() class-method.""" self.assertIsInstance(Squeezer.auto_squeeze_min_lines, int) From 0e30a72e1a59df0ff193ad3b12a526cd41b55523 Mon Sep 17 00:00:00 2001 From: Tal Einat Date: Sun, 11 Nov 2018 14:51:04 +0200 Subject: [PATCH 03/25] bpo-35196: fix test_reload() failing --- Lib/idlelib/idle_test/test_squeezer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/idlelib/idle_test/test_squeezer.py b/Lib/idlelib/idle_test/test_squeezer.py index 361b6688eff1089..5e3f4da2b6a5206 100644 --- a/Lib/idlelib/idle_test/test_squeezer.py +++ b/Lib/idlelib/idle_test/test_squeezer.py @@ -310,7 +310,8 @@ def test_reload(self): """Test the reload() class-method.""" self.assertIsInstance(Squeezer.auto_squeeze_min_lines, int) idleConf.SetOption('main', 'PyShell', 'auto-squeeze-min-lines', '42') - Squeezer.reload() + with patch('idlelib.squeezer.Squeezer.load_font') as mock_load_font: + Squeezer.reload() self.assertEqual(Squeezer.auto_squeeze_min_lines, 42) From 76e8ea271adf34e5a05672b9d407851fa033d856 Mon Sep 17 00:00:00 2001 From: Tal Einat Date: Mon, 24 Dec 2018 10:02:49 +0200 Subject: [PATCH 04/25] remove unnecessary additional check which doesn't improve performance --- Lib/idlelib/squeezer.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/Lib/idlelib/squeezer.py b/Lib/idlelib/squeezer.py index 56cea71fa208cf8..ee7a845c97ef61a 100644 --- a/Lib/idlelib/squeezer.py +++ b/Lib/idlelib/squeezer.py @@ -272,21 +272,6 @@ def mywrite(s, tags=(), write=editwin.write): if s_len < auto_squeeze_min_lines: return write(s, tags) - # Try another quick check to avoid calculating the actual - # number of lines, but only if the given text is not too - # long for it to possibly succeed. - line_width = self.get_line_width() - if s_len < auto_squeeze_min_lines * line_width: - # Check lower bound: Assume that all non-newline - # characters are in one long line, and that the - # tab width is 8. - n_newlines = s.count('\n') - n_tabs = s.count('\t') - n_lines_lower_bound = n_newlines + \ - (s_len - n_newlines + n_tabs * 7) // line_width - if n_lines_lower_bound < auto_squeeze_min_lines: - return write(s, tags) - numoflines = self.count_lines(s) if numoflines < self.auto_squeeze_min_lines: return write(s, tags) From 65ee0565f4b0b05c5cf3bdc9e6eaa5e35cd00798 Mon Sep 17 00:00:00 2001 From: Tal Einat Date: Mon, 24 Dec 2018 14:04:44 +0200 Subject: [PATCH 05/25] instantiate Squeezer only on PyShell windows This is cleaner and allows simplifying Squeezer.reload(). --- Lib/idlelib/editor.py | 8 ++-- Lib/idlelib/squeezer.py | 94 ++++++++++++++++++++--------------------- 2 files changed, 50 insertions(+), 52 deletions(-) diff --git a/Lib/idlelib/editor.py b/Lib/idlelib/editor.py index d92b32b2400443b..27649adba7ec62f 100644 --- a/Lib/idlelib/editor.py +++ b/Lib/idlelib/editor.py @@ -65,6 +65,7 @@ class EditorWindow(object): def __init__(self, flist=None, filename=None, key=None, root=None): # Delay import: runscript imports pyshell imports EditorWindow. from idlelib.runscript import ScriptBinding + from idlelib.pyshell import PyShell if EditorWindow.help_url is None: dochome = os.path.join(sys.base_prefix, 'Doc', 'index.html') @@ -317,9 +318,10 @@ def __init__(self, flist=None, filename=None, key=None, root=None): text.bind("<>", self.ZoomHeight(self).zoom_height_event) text.bind("<>", self.CodeContext(self).toggle_code_context_event) - squeezer = self.Squeezer(self) - text.bind("<>", - squeezer.squeeze_current_text_event) + if isinstance(self, PyShell): + squeezer = self.Squeezer(self) + text.bind("<>", + squeezer.squeeze_current_text_event) def _filename_to_unicode(self, filename): """Return filename as BMP unicode so diplayable in Tk.""" diff --git a/Lib/idlelib/squeezer.py b/Lib/idlelib/squeezer.py index ee7a845c97ef61a..62b0a1b7d8ce4a2 100644 --- a/Lib/idlelib/squeezer.py +++ b/Lib/idlelib/squeezer.py @@ -203,7 +203,7 @@ class Squeezer: This avoids IDLE's shell slowing down considerably, and even becoming completely unresponsive, when very long outputs are written. """ - _instance_weakrefs = [] + _instance_weakref = None @classmethod def reload(cls): @@ -213,16 +213,13 @@ def reload(cls): type="int", default=50, ) - # Loading the font info requires a Tk root, and we don't want to - # rely on Tkinter's "default root", so each instance will load - # font info separately using its editor windows's Tk root. - new_instance_weakrefs = [] - for inst_ref in cls._instance_weakrefs: - inst = inst_ref() - if inst is not None: - inst.load_font() - new_instance_weakrefs.append(inst_ref) - cls._instance_weakrefs = new_instance_weakrefs + # Loading the font info requires a Tk root. IDLE doesn't rely + # on Tkinter's "default root", so the instance will reload + # font info using its editor windows's Tk root. + if cls._instance_weakref is not None: + instance = cls._instance_weakref() + if instance is not None: + instance.load_font() def __init__(self, editwin): """Initialize settings for Squeezer. @@ -243,7 +240,7 @@ def __init__(self, editwin): # the actual Text widget. Squeezer, however, needs to make such changes. self.base_text = editwin.per.bottom - self._instance_weakrefs.append(weakref.ref(self)) + self._instance_weakref = weakref.ref(self) self.load_font() # Twice the text widget's border width and internal padding; @@ -254,43 +251,42 @@ def __init__(self, editwin): ) self.expandingbuttons = [] - from idlelib.pyshell import PyShell # done here to avoid import cycle - if isinstance(editwin, PyShell): - # If we get a PyShell instance, replace its 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. - - # First, a very quick check to skip very short texts. - s_len = len(s) - auto_squeeze_min_lines = self.auto_squeeze_min_lines - if s_len < auto_squeeze_min_lines: - return write(s, tags) - - numoflines = self.count_lines(s) - if numoflines < self.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 + + # 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. + + # First, a very quick check to skip very short texts. + s_len = len(s) + auto_squeeze_min_lines = self.auto_squeeze_min_lines + if s_len < auto_squeeze_min_lines: + return write(s, tags) + + numoflines = self.count_lines(s) + if numoflines < self.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. From 485d954a3bb2d2e23fed6dcb9beaf13b464ce131 Mon Sep 17 00:00:00 2001 From: Tal Einat Date: Mon, 24 Dec 2018 14:18:37 +0200 Subject: [PATCH 06/25] hard-code tab width to 8 (always true for IDLE's shell) --- Lib/idlelib/idle_test/test_squeezer.py | 71 ++++++++------------------ Lib/idlelib/squeezer.py | 7 ++- 2 files changed, 24 insertions(+), 54 deletions(-) diff --git a/Lib/idlelib/idle_test/test_squeezer.py b/Lib/idlelib/idle_test/test_squeezer.py index 5e3f4da2b6a5206..bbf989924b962c3 100644 --- a/Lib/idlelib/idle_test/test_squeezer.py +++ b/Lib/idlelib/idle_test/test_squeezer.py @@ -33,10 +33,10 @@ def cleanup_root(): class CountLinesTest(unittest.TestCase): """Tests for the count_lines_with_wrapping function.""" - def check(self, expected, text, linewidth, tabwidth): + def check(self, expected, text, linewidth): return self.assertEqual( expected, - count_lines_with_wrapping(text, linewidth, tabwidth), + count_lines_with_wrapping(text, linewidth), ) def test_count_empty(self): @@ -55,37 +55,14 @@ 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_tab_width(self): - """Test with various tab widths and line widths.""" - self.check(expected=1, text='\t' * 1, linewidth=8, tabwidth=4) - self.check(expected=1, text='\t' * 2, linewidth=8, tabwidth=4) - self.check(expected=2, text='\t' * 3, linewidth=8, tabwidth=4) - self.check(expected=2, text='\t' * 4, linewidth=8, tabwidth=4) - self.check(expected=3, text='\t' * 5, linewidth=8, tabwidth=4) - - # test longer lines and various tab widths - self.check(expected=4, text='\t' * 10, linewidth=12, tabwidth=4) - self.check(expected=10, text='\t' * 10, linewidth=12, tabwidth=8) - self.check(expected=2, text='\t' * 4, linewidth=10, tabwidth=3) - - # test tabwidth=1 - self.check(expected=2, text='\t' * 9, linewidth=5, tabwidth=1) - self.check(expected=2, text='\t' * 10, linewidth=5, tabwidth=1) - self.check(expected=3, text='\t' * 11, linewidth=5, tabwidth=1) - - # test for off-by-one errors - self.check(expected=2, text='\t' * 6, linewidth=12, tabwidth=4) - self.check(expected=3, text='\t' * 6, linewidth=11, tabwidth=4) - self.check(expected=2, text='\t' * 6, linewidth=13, tabwidth=4) - def test_empty_lines(self): - self.check(expected=1, text='\n', linewidth=80, tabwidth=8) - self.check(expected=2, text='\n\n', linewidth=80, tabwidth=8) - self.check(expected=10, text='\n' * 10, linewidth=80, tabwidth=8) + 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, tabwidth=8) - self.check(expected=3, text='a' * 200 + '\n', linewidth=80, tabwidth=8) + 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("""\ @@ -94,12 +71,11 @@ def test_several_lines_different_lengths(self): 7 chars 13 characters""") - self.check(expected=5, text=text, linewidth=80, tabwidth=8) - self.check(expected=5, text=text + '\n', linewidth=80, tabwidth=8) - self.check(expected=6, text=text, linewidth=40, tabwidth=8) - self.check(expected=7, text=text, linewidth=20, tabwidth=8) - self.check(expected=11, text=text, linewidth=10, tabwidth=8) - + 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.""" @@ -117,7 +93,6 @@ def make_squeezer_instance(self, editor_window=None): editor_window = self.make_mock_editor_window() squeezer = Squeezer(editor_window) squeezer.get_line_width = Mock(return_value=80) - squeezer.editwin.get_tk_tabwidth = Mock(return_value=8) return squeezer def make_text_widget(self): @@ -132,25 +107,21 @@ def test_count_lines(self): editwin = self.make_mock_editor_window() squeezer = self.make_squeezer_instance(editwin) - for text_code, tab_width, line_width, expected in [ - (r"'\n'", 8, 80, 1), - (r"'\n' * 3", 8, 80, 3), - (r"'a' * 40 + '\n'", 8, 80, 1), - (r"'a' * 80 + '\n'", 8, 80, 1), + 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), # TODO: uncomment the next test case after bpo-35208 is fixed - # (r"'a' * 200 + '\n'", 8, 80, 3), - (r"'aa\t' * 20", 8, 80, 2), - (r"'aa\t' * 21", 8, 80, 3), - (r"'aa\t' * 20", 8, 40, 4), - (r"'aa\t' * 20", 4, 80, 1), - (r"'aa\t' * 21", 4, 80, 2), + # (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, - tab_width=tab_width, line_width=line_width, expected=expected): text = eval(text_code) - editwin.get_tk_tabwidth.return_value = tab_width squeezer.get_line_width.return_value = line_width self.assertEquals(squeezer.count_lines(text), expected) diff --git a/Lib/idlelib/squeezer.py b/Lib/idlelib/squeezer.py index 62b0a1b7d8ce4a2..e9a5384cbebfbfb 100644 --- a/Lib/idlelib/squeezer.py +++ b/Lib/idlelib/squeezer.py @@ -27,7 +27,7 @@ from idlelib import macosx -def count_lines_with_wrapping(s, linewidth=80, tabwidth=8): +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 @@ -35,6 +35,7 @@ def count_lines_with_wrapping(s, linewidth=80, tabwidth=8): Tabs are considered tabwidth characters long. """ + tabwidth = 8 # this is currently always true in IDLE's shell pos = 0 linecount = 1 current_column = 0 @@ -299,10 +300,8 @@ def count_lines(self, s): Tabs are considered tabwidth characters long. """ - # Tab width is configurable - tabwidth = self.editwin.get_tk_tabwidth() linewidth = self.get_line_width() - return count_lines_with_wrapping(s, linewidth, tabwidth) + return count_lines_with_wrapping(s, linewidth) def get_line_width(self): # The maximum line length in pixels: The width of the text widget, From 5f0e69fda6dc73a70e0408ce5398eff2d3dec3a2 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Thu, 27 Dec 2018 17:48:23 -0500 Subject: [PATCH 07/25] Add blurb and idlelib news. --- Lib/idlelib/NEWS.txt | 2 ++ Misc/NEWS.d/next/IDLE/2018-12-27-17-46-42.bpo-35196.9E-xUh.rst | 1 + 2 files changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/IDLE/2018-12-27-17-46-42.bpo-35196.9E-xUh.rst diff --git a/Lib/idlelib/NEWS.txt b/Lib/idlelib/NEWS.txt index 3d93e91d3147598..d1748a21bce400e 100644 --- a/Lib/idlelib/NEWS.txt +++ b/Lib/idlelib/NEWS.txt @@ -3,6 +3,8 @@ Released on 2019-10-20? ====================================== +bpo-35196: Speed up squeezer line counting. + bpo-35208: Squeezer now counts wrapped lines before newlines. bpo-35555: Gray out Code Context menu entry when it's not applicable. diff --git a/Misc/NEWS.d/next/IDLE/2018-12-27-17-46-42.bpo-35196.9E-xUh.rst b/Misc/NEWS.d/next/IDLE/2018-12-27-17-46-42.bpo-35196.9E-xUh.rst new file mode 100644 index 000000000000000..ee90d76010d9866 --- /dev/null +++ b/Misc/NEWS.d/next/IDLE/2018-12-27-17-46-42.bpo-35196.9E-xUh.rst @@ -0,0 +1 @@ +Speed up squeezer line counting. From 23af0403709fe67e1205c65225556b3a1dd8f7a0 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Thu, 27 Dec 2018 17:54:57 -0500 Subject: [PATCH 08/25] Condense character width comment and update to 8.6. --- Lib/idlelib/squeezer.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Lib/idlelib/squeezer.py b/Lib/idlelib/squeezer.py index e9a5384cbebfbfb..b1c6aeb3b8e0765 100644 --- a/Lib/idlelib/squeezer.py +++ b/Lib/idlelib/squeezer.py @@ -309,11 +309,9 @@ def get_line_width(self): linewidth_pixels = \ self.base_text.winfo_width() - self.window_width_delta - # Divide the width of the Text widget by the font's width. - # According to Tk8.5 docs, the Text widget's width is set - # according to the width of its font's '0' (zero) character, - # so we will use this as an approximation. - # see: http://www.tcl.tk/man/tcl8.5/TkCmd/text.htm#M-width + # Divide the width of the Text widget by the font width, + # which is taked to be the width of '0' (zero). + # http://www.tcl.tk/man/tcl8.6/TkCmd/text.htm#M21 return linewidth_pixels // self.zero_char_width def load_font(self): From 8a4ca02160b4ef8f77b5c48aed555ccdc8323ae5 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Thu, 27 Dec 2018 17:56:47 -0500 Subject: [PATCH 09/25] In test, change deprecated assertEquals to assertEqual. --- Lib/idlelib/idle_test/test_squeezer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/idlelib/idle_test/test_squeezer.py b/Lib/idlelib/idle_test/test_squeezer.py index bbf989924b962c3..31078d0ab995741 100644 --- a/Lib/idlelib/idle_test/test_squeezer.py +++ b/Lib/idlelib/idle_test/test_squeezer.py @@ -123,7 +123,7 @@ def test_count_lines(self): expected=expected): text = eval(text_code) squeezer.get_line_width.return_value = line_width - self.assertEquals(squeezer.count_lines(text), expected) + self.assertEqual(squeezer.count_lines(text), expected) def test_init(self): """Test the creation of Squeezer instances.""" From 1f56ee8000414ec2d76b11deba9ed04c0a087388 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Thu, 27 Dec 2018 19:22:37 -0500 Subject: [PATCH 10/25] Add coverage information. Details in comment. --- Lib/idlelib/idle_test/test_squeezer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/idlelib/idle_test/test_squeezer.py b/Lib/idlelib/idle_test/test_squeezer.py index 31078d0ab995741..43267f8902316d3 100644 --- a/Lib/idlelib/idle_test/test_squeezer.py +++ b/Lib/idlelib/idle_test/test_squeezer.py @@ -1,3 +1,5 @@ +"Test squeezer, coverage 93%" + from collections import namedtuple from textwrap import dedent from tkinter import Text, Tk From 390d0899a13442eaa2e54f8a02000770df339a85 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Thu, 27 Dec 2018 19:23:09 -0500 Subject: [PATCH 11/25] Add X windows comment. --- Lib/idlelib/squeezer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/idlelib/squeezer.py b/Lib/idlelib/squeezer.py index b1c6aeb3b8e0765..c7ea8f39ee5d865 100644 --- a/Lib/idlelib/squeezer.py +++ b/Lib/idlelib/squeezer.py @@ -121,7 +121,7 @@ def __init__(self, s, tags, numoflines, squeezer): self.bind("", self.context_menu_event) else: self.bind("", self.context_menu_event) - self.selection_handle( + self.selection_handle( # X windows only. lambda offset, length: s[int(offset):int(offset) + int(length)]) self.is_dangerous = None From 8e12a8169d9b16336a07b2b723e2d99769860269 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Thu, 27 Dec 2018 19:34:00 -0500 Subject: [PATCH 12/25] Move PyShell only code to pyshell.PyShell. --- Lib/idlelib/editor.py | 5 ----- Lib/idlelib/pyshell.py | 3 +++ 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Lib/idlelib/editor.py b/Lib/idlelib/editor.py index 27649adba7ec62f..aa20387615a608d 100644 --- a/Lib/idlelib/editor.py +++ b/Lib/idlelib/editor.py @@ -65,7 +65,6 @@ class EditorWindow(object): def __init__(self, flist=None, filename=None, key=None, root=None): # Delay import: runscript imports pyshell imports EditorWindow. from idlelib.runscript import ScriptBinding - from idlelib.pyshell import PyShell if EditorWindow.help_url is None: dochome = os.path.join(sys.base_prefix, 'Doc', 'index.html') @@ -318,10 +317,6 @@ def __init__(self, flist=None, filename=None, key=None, root=None): text.bind("<>", self.ZoomHeight(self).zoom_height_event) text.bind("<>", self.CodeContext(self).toggle_code_context_event) - if isinstance(self, PyShell): - squeezer = self.Squeezer(self) - text.bind("<>", - squeezer.squeeze_current_text_event) def _filename_to_unicode(self, filename): """Return filename as BMP unicode so diplayable in Tk.""" diff --git a/Lib/idlelib/pyshell.py b/Lib/idlelib/pyshell.py index 81a97ef6d6bcc51..b73b2b8c0e9cfca 100755 --- a/Lib/idlelib/pyshell.py +++ b/Lib/idlelib/pyshell.py @@ -899,6 +899,9 @@ def __init__(self, flist=None): 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 From ee25bac1684e766a32901c0c8e6ae63686d2be4e Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Thu, 27 Dec 2018 19:37:53 -0500 Subject: [PATCH 13/25] Use existing local reference. --- Lib/idlelib/squeezer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/idlelib/squeezer.py b/Lib/idlelib/squeezer.py index c7ea8f39ee5d865..61fd7f2965df3cc 100644 --- a/Lib/idlelib/squeezer.py +++ b/Lib/idlelib/squeezer.py @@ -270,7 +270,7 @@ def mywrite(s, tags=(), write=editwin.write): return write(s, tags) numoflines = self.count_lines(s) - if numoflines < self.auto_squeeze_min_lines: + if numoflines < auto_squeeze_min_lines: return write(s, tags) # create an ExpandingButton instance From 9ac5156ea72636a94ee173e66b4584e083bf282f Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Thu, 27 Dec 2018 20:17:34 -0500 Subject: [PATCH 14/25] Fix comments (# Cap ... length ... end.) in squeezer.py. --- Lib/idlelib/squeezer.py | 74 ++++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/Lib/idlelib/squeezer.py b/Lib/idlelib/squeezer.py index 61fd7f2965df3cc..6b2af0b76e572e5 100644 --- a/Lib/idlelib/squeezer.py +++ b/Lib/idlelib/squeezer.py @@ -35,26 +35,27 @@ def count_lines_with_wrapping(s, linewidth=80): Tabs are considered tabwidth characters long. """ - tabwidth = 8 # this is currently always true in IDLE's shell + 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 + # Process the normal chars up to tab or newline. numchars = m.start() - pos pos += numchars current_column += numchars - # deal with tab or newline + # 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. + # 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. + # 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 @@ -62,21 +63,21 @@ def count_lines_with_wrapping(s, linewidth=80): 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 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 + pos += 1 # After the tab or newline. - # process remaining chars (no more tabs or newlines) + # Process remaining chars (no more tabs or newlines). current_column += len(s) - pos - # avoid divmod(-1, linewidth) + # Avoid divmod(-1, linewidth). if current_column > 0: linecount += (current_column - 1) // linewidth else: - # the text ended with a newline; don't count an extra line after it + # Text ended with newline; don't count an extra line after it. linecount -= 1 return linecount @@ -100,9 +101,7 @@ def __init__(self, s, tags, numoflines, squeezer): self.squeezer = squeezer self.editwin = editwin = squeezer.editwin self.text = text = editwin.text - - # the base Text widget of the PyShell object, used to change text - # before the iomark + # 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" @@ -184,7 +183,7 @@ def view(self, event=None): modal=False, wrap='none') rmenu_specs = ( - # item structure: (label, method_name) + # Item structure: (label, method_name). ('copy', 'copy'), ('view', 'view'), ) @@ -235,10 +234,11 @@ def __init__(self, editwin): 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. + # 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 self._instance_weakref = weakref.ref(self) @@ -273,10 +273,10 @@ def mywrite(s, tags=(), write=editwin.write): if numoflines < auto_squeeze_min_lines: return write(s, tags) - # create an ExpandingButton instance + # Create an ExpandingButton instance. expandingbutton = ExpandingButton(s, tags, numoflines, self) - # insert the ExpandingButton into the Text widget + # Insert the ExpandingButton into the Text widget. text.mark_gravity("iomark", tk.RIGHT) text.window_create("iomark", window=expandingbutton, padx=3, pady=5) @@ -284,7 +284,7 @@ def mywrite(s, tags=(), write=editwin.write): text.update() text.mark_gravity("iomark", tk.LEFT) - # add the ExpandingButton to the Squeezer's list + # Add the ExpandingButton to the Squeezer's list. self.expandingbuttons.append(expandingbutton) editwin.write = mywrite @@ -304,8 +304,8 @@ def count_lines(self, s): return count_lines_with_wrapping(s, linewidth) def get_line_width(self): - # The maximum line length in pixels: The width of the text widget, - # minus twice the border width and internal padding. + # The maximum line length in pixels: The width of the text + # widget, minus twice the border width and internal padding. linewidth_pixels = \ self.base_text.winfo_width() - self.window_width_delta @@ -327,29 +327,29 @@ def squeeze_current_text_event(self, event): 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 + # 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 + # The insert cursor doesn't have a "stdout" or "stderr" tag. self.text.bell() return "break" - # find the range to squeeze + # 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 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 + # Delete the text. self.base_text.delete(start, end) - # prepare an ExpandingButton + # Prepare an ExpandingButton. numoflines = self.count_lines(s) expandingbutton = ExpandingButton(s, tag_name, numoflines, self) @@ -357,9 +357,9 @@ def squeeze_current_text_event(self, event): 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 + # 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): From 6e1556dc7bc3d96633eb3bc94b7b7ad2fb43b79e Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Thu, 27 Dec 2018 20:26:54 -0500 Subject: [PATCH 15/25] Fix comments in test_squeezer to PEP standards. Uncomment test dependent on previous PR. (It now passes.) --- Lib/idlelib/idle_test/test_squeezer.py | 91 +++++++++++++------------- 1 file changed, 45 insertions(+), 46 deletions(-) diff --git a/Lib/idlelib/idle_test/test_squeezer.py b/Lib/idlelib/idle_test/test_squeezer.py index 43267f8902316d3..ec4da64f711bc35 100644 --- a/Lib/idlelib/idle_test/test_squeezer.py +++ b/Lib/idlelib/idle_test/test_squeezer.py @@ -85,7 +85,7 @@ def make_mock_editor_window(self): """Create a mock EditorWindow instance.""" editwin = NonCallableMagicMock() # isinstance(editwin, PyShell) must be true for Squeezer to enable - # auto-squeezing; in practice this will always be true + # auto-squeezing; in practice this will always be true. editwin.__class__ = PyShell return editwin @@ -114,8 +114,7 @@ def test_count_lines(self): (r"'\n' * 3", 80, 3), (r"'a' * 40 + '\n'", 80, 1), (r"'a' * 80 + '\n'", 80, 1), - # TODO: uncomment the next test case after bpo-35208 is fixed - # (r"'a' * 200 + '\n'", 80, 3), + (r"'a' * 200 + '\n'", 80, 3), (r"'aa\t' * 20", 80, 2), (r"'aa\t' * 21", 80, 3), (r"'aa\t' * 20", 40, 4), @@ -200,7 +199,7 @@ def test_auto_squeeze(self): def test_squeeze_current_text_event(self): """Test the squeeze_current_text event.""" - # squeezing text should work for both stdout and stderr + # Squeezing text should work for both stdout and stderr. for tag_name in ["stdout", "stderr"]: text_widget = self.make_text_widget() @@ -209,22 +208,22 @@ def test_squeeze_current_text_event(self): squeezer = self.make_squeezer_instance(editwin) squeezer.count_lines = Mock(return_value=6) - # prepare some text in the Text widget + # 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 + # 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 + # 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) @@ -238,14 +237,14 @@ def test_squeeze_current_text_event_no_allowed_tags(self): squeezer = self.make_squeezer_instance(editwin) squeezer.count_lines = Mock(return_value=6) - # prepare some text in the Text widget + # 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 + # 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') @@ -260,13 +259,13 @@ def test_squeeze_text_before_existing_squeezed_text(self): squeezer = self.make_squeezer_instance(editwin) squeezer.count_lines = Mock(return_value=6) - # prepare some text in the Text widget and squeeze it + # 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 + # 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()) @@ -298,7 +297,7 @@ def make_mock_squeezer(self): squeezer = Mock() squeezer.editwin.text = Text(root) - # Set default values for the configuration settings + # Set default values for the configuration settings. squeezer.auto_squeeze_min_lines = 50 return squeezer @@ -311,23 +310,23 @@ def test_init(self, MockHovertip): expandingbutton = ExpandingButton('TEXT', 'TAGS', 50, squeezer) self.assertEqual(expandingbutton.s, 'TEXT') - # check that the underlying tkinter.Button is properly configured + # 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 + # 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 + # 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 + # 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 + # Check that 'right-click' appears in the tooltip text. tooltip_text = MockHovertip.call_args[0][1] self.assertIn('right-click', tooltip_text.lower()) @@ -336,29 +335,30 @@ def test_expand(self): 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) + # 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 + # 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 + # 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 + # 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 + # Check that the button removed itself from squeezer.expandingbuttons. self.assertEqual(squeezer.expandingbuttons.remove.call_count, 1) squeezer.expandingbuttons.remove.assert_called_with(expandingbutton) @@ -370,55 +370,54 @@ def test_expand_dangerous_oupput(self): expandingbutton.set_is_dangerous() self.assertTrue(expandingbutton.is_dangerous) - # insert the button into the text widget - # (this is normally done by the Squeezer class) + # 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 + # 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 + # 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 + # Trigger the expand event. retval = expandingbutton.expand(event=Mock()) - # check that the event chain was broken and no text was inserted + # 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 + # 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 + # Trigger the expand event. retval = expandingbutton.expand(event=Mock()) - # check that the event chain wasn't broken and the text was inserted + # 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 + # 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 + # Trigger the copy event. retval = expandingbutton.copy(event=Mock()) self.assertEqual(retval, None) - # check that the expanding button called clipboard_clear() and - # clipboard_append('TEXT') once each + # 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') @@ -431,13 +430,13 @@ def test_view(self): with patch('idlelib.squeezer.view_text', autospec=view_text)\ as mock_view_text: - # trigger the view event + # Trigger the view event. expandingbutton.view(event=Mock()) - # check that the expanding button called view_text + # Check that the expanding button called view_text. self.assertEqual(mock_view_text.call_count, 1) - # check that the proper text was passed + # Check that the proper text was passed. self.assertEqual(mock_view_text.call_args[0][2], 'TEXT') def test_rmenu(self): From 2f0e0a5b53b33676c45f1d13c1e60f51cff08717 Mon Sep 17 00:00:00 2001 From: Tal Einat Date: Thu, 3 Jan 2019 12:54:53 +0200 Subject: [PATCH 16/25] fix bug in setting Squeezer._instance_weakref and improve test coverage --- Lib/idlelib/idle_test/test_squeezer.py | 77 ++++++++++++++++++-------- Lib/idlelib/squeezer.py | 2 +- 2 files changed, 54 insertions(+), 25 deletions(-) diff --git a/Lib/idlelib/idle_test/test_squeezer.py b/Lib/idlelib/idle_test/test_squeezer.py index ec4da64f711bc35..b5b3f9872a81b7f 100644 --- a/Lib/idlelib/idle_test/test_squeezer.py +++ b/Lib/idlelib/idle_test/test_squeezer.py @@ -81,12 +81,18 @@ def test_several_lines_different_lengths(self): class SqueezerTest(unittest.TestCase): """Tests for the Squeezer class.""" - def make_mock_editor_window(self): + def make_mock_editor_window(self, with_text_widget=False): """Create a mock EditorWindow instance.""" editwin = NonCallableMagicMock() # isinstance(editwin, PyShell) must be true for Squeezer to enable # auto-squeezing; in practice this will always be true. editwin.__class__ = PyShell + + 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): @@ -97,13 +103,20 @@ def make_squeezer_instance(self, editor_window=None): squeezer.get_line_width = Mock(return_value=80) return squeezer - def make_text_widget(self): - root = get_test_tk_root(self) + def make_text_widget(self, root=None): + if root is None: + root = get_test_tk_root(self) text_widget = Text(root) text_widget["font"] = idleConf.GetFont(root, "main", "EditorWindow") 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() @@ -185,10 +198,8 @@ def test_write_stdout(self): def test_auto_squeeze(self): """Test that the auto-squeezing creates an ExpandingButton properly.""" - text_widget = self.make_text_widget() - - editwin = self.make_mock_editor_window() - editwin.text = text_widget + 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) @@ -201,10 +212,8 @@ 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"]: - text_widget = self.make_text_widget() - - editwin = self.make_mock_editor_window() - editwin.text = editwin.per.bottom = text_widget + 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) @@ -230,10 +239,8 @@ def test_squeeze_current_text_event(self): def test_squeeze_current_text_event_no_allowed_tags(self): """Test that the event doesn't squeeze text without a relevant tag.""" - text_widget = self.make_text_widget() - - editwin = self.make_mock_editor_window() - editwin.text = editwin.per.bottom = text_widget + 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) @@ -252,10 +259,8 @@ def test_squeeze_current_text_event_no_allowed_tags(self): def test_squeeze_text_before_existing_squeezed_text(self): """Test squeezing text before existing squeezed text.""" - text_widget = self.make_text_widget() - - editwin = self.make_mock_editor_window() - editwin.text = editwin.per.bottom = text_widget + 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) @@ -280,11 +285,35 @@ def test_squeeze_text_before_existing_squeezed_text(self): def test_reload(self): """Test the reload() class-method.""" - self.assertIsInstance(Squeezer.auto_squeeze_min_lines, int) - idleConf.SetOption('main', 'PyShell', 'auto-squeeze-min-lines', '42') - with patch('idlelib.squeezer.Squeezer.load_font') as mock_load_font: - Squeezer.reload() - self.assertEqual(Squeezer.auto_squeeze_min_lines, 42) + editwin = self.make_mock_editor_window(with_text_widget=True) + text_widget = editwin.text + squeezer = self.make_squeezer_instance(editwin) + + orig_zero_char_width = squeezer.zero_char_width + orig_auto_squeeze_min_lines = squeezer.auto_squeeze_min_lines + + # set the font size to double the original value + prev_font_size = idleConf.GetOption( + 'main', 'EditorWindow', 'font-size', type='int', default=10) + new_font_size = 2 * prev_font_size + self.set_idleconf_option_with_cleanup( + 'main', 'EditorWindow', 'font-size', str(new_font_size)) + # update the Text widget's font from the config + # (this is usually done by the config dialog) + text_widget["font"] = idleConf.GetFont( + editwin.root, "main", "EditorWindow") + + # set auto-squeeze-min-lines to 10 more than the original value + 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.assertAlmostEqual(squeezer.zero_char_width, + 2 * orig_zero_char_width, 0) + self.assertEqual(squeezer.auto_squeeze_min_lines, + new_auto_squeeze_min_lines) class ExpandingButtonTest(unittest.TestCase): diff --git a/Lib/idlelib/squeezer.py b/Lib/idlelib/squeezer.py index 6b2af0b76e572e5..31e17d47a13f985 100644 --- a/Lib/idlelib/squeezer.py +++ b/Lib/idlelib/squeezer.py @@ -241,7 +241,7 @@ def __init__(self, editwin): # however, needs to make such changes. self.base_text = editwin.per.bottom - self._instance_weakref = weakref.ref(self) + Squeezer._instance_weakref = weakref.ref(self) self.load_font() # Twice the text widget's border width and internal padding; From 37daa8a517eb287a810fc47895a187948742e8f5 Mon Sep 17 00:00:00 2001 From: Tal Einat Date: Thu, 3 Jan 2019 13:02:10 +0200 Subject: [PATCH 17/25] add a do-nothing test case for Squeezer.reload(), and fix test cleanup --- Lib/idlelib/idle_test/test_squeezer.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Lib/idlelib/idle_test/test_squeezer.py b/Lib/idlelib/idle_test/test_squeezer.py index b5b3f9872a81b7f..2ab8cc87bf58e52 100644 --- a/Lib/idlelib/idle_test/test_squeezer.py +++ b/Lib/idlelib/idle_test/test_squeezer.py @@ -79,8 +79,15 @@ def test_several_lines_different_lengths(self): 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 tearDown(self): + # Clean up the Squeezer class's reference to its instance, + # to avoid side-effects from one test case upon another. + if Squeezer._instance_weakref is not None: + Squeezer._instance_weakref = None + def make_mock_editor_window(self, with_text_widget=False): """Create a mock EditorWindow instance.""" editwin = NonCallableMagicMock() @@ -315,6 +322,10 @@ def test_reload(self): 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.""" From 62ca6d38a06b2462fb4eaf895aa3506607ed8595 Mon Sep 17 00:00:00 2001 From: Tal Einat Date: Thu, 3 Jan 2019 13:11:18 +0200 Subject: [PATCH 18/25] re-format inline comments --- Lib/idlelib/idle_test/test_squeezer.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Lib/idlelib/idle_test/test_squeezer.py b/Lib/idlelib/idle_test/test_squeezer.py index 2ab8cc87bf58e52..e68e8066fd2f32a 100644 --- a/Lib/idlelib/idle_test/test_squeezer.py +++ b/Lib/idlelib/idle_test/test_squeezer.py @@ -299,18 +299,19 @@ def test_reload(self): orig_zero_char_width = squeezer.zero_char_width orig_auto_squeeze_min_lines = squeezer.auto_squeeze_min_lines - # set the font size to double the original value + # Set the font size to double the original value. prev_font_size = idleConf.GetOption( 'main', 'EditorWindow', 'font-size', type='int', default=10) new_font_size = 2 * prev_font_size self.set_idleconf_option_with_cleanup( 'main', 'EditorWindow', 'font-size', str(new_font_size)) - # update the Text widget's font from the config - # (this is usually done by the config dialog) + # Update the Text widget's font from the config. + # * This is usually done by the config dialog. + # * See: EditorWindow.ResetFont() text_widget["font"] = idleConf.GetFont( editwin.root, "main", "EditorWindow") - # set auto-squeeze-min-lines to 10 more than the original value + # Set auto-squeeze-min-lines to 10 more than the original value. new_auto_squeeze_min_lines = orig_auto_squeeze_min_lines + 10 self.set_idleconf_option_with_cleanup( 'main', 'PyShell', 'auto-squeeze-min-lines', From 3f7790564f0cd71d6081d516ceaf5edb7523847f Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Thu, 10 Jan 2019 19:08:51 -0500 Subject: [PATCH 19/25] Update test_squeezer, trigger retest --- Lib/idlelib/idle_test/test_squeezer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/idlelib/idle_test/test_squeezer.py b/Lib/idlelib/idle_test/test_squeezer.py index e68e8066fd2f32a..d39f833264174be 100644 --- a/Lib/idlelib/idle_test/test_squeezer.py +++ b/Lib/idlelib/idle_test/test_squeezer.py @@ -1,4 +1,4 @@ -"Test squeezer, coverage 93%" +"Test squeezer, coverage 95%" from collections import namedtuple from textwrap import dedent From 17bc8d99caf33bc3f5e17e4b74684e99273f57b2 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Thu, 10 Jan 2019 20:04:56 -0500 Subject: [PATCH 20/25] Reverse asssertions to test 2nd --- Lib/idlelib/idle_test/test_squeezer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/idlelib/idle_test/test_squeezer.py b/Lib/idlelib/idle_test/test_squeezer.py index d39f833264174be..0fa50a29a60fd0c 100644 --- a/Lib/idlelib/idle_test/test_squeezer.py +++ b/Lib/idlelib/idle_test/test_squeezer.py @@ -318,10 +318,10 @@ def test_reload(self): str(new_auto_squeeze_min_lines)) Squeezer.reload() - self.assertAlmostEqual(squeezer.zero_char_width, - 2 * orig_zero_char_width, 0) self.assertEqual(squeezer.auto_squeeze_min_lines, new_auto_squeeze_min_lines) + self.assertAlmostEqual(squeezer.zero_char_width, + 2 * orig_zero_char_width, 0) def test_reload_no_squeezer_instances(self): """Test that Squeezer.reload() runs without any instances existing.""" From 69eaa4fd39f9eb1f61150d609eddb8c35052a137 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Fri, 11 Jan 2019 22:04:06 -0500 Subject: [PATCH 21/25] Change taked to taken --- Lib/idlelib/squeezer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/idlelib/squeezer.py b/Lib/idlelib/squeezer.py index 31e17d47a13f985..999ec79ba7142f9 100644 --- a/Lib/idlelib/squeezer.py +++ b/Lib/idlelib/squeezer.py @@ -310,7 +310,7 @@ def get_line_width(self): self.base_text.winfo_width() - self.window_width_delta # Divide the width of the Text widget by the font width, - # which is taked to be the width of '0' (zero). + # which is taken to be the width of '0' (zero). # http://www.tcl.tk/man/tcl8.6/TkCmd/text.htm#M21 return linewidth_pixels // self.zero_char_width From 3c3fb9de29442944e4e47f72e5d4820ac634e50e Mon Sep 17 00:00:00 2001 From: Tal Einat Date: Sat, 12 Jan 2019 19:14:09 +0200 Subject: [PATCH 22/25] fix test_reload() check that zero_with_char has increased The check was much too restrictive, and failed on some platforms in certain cases. Really, test_reload() just needs to check that the reload() call had an effect. --- Lib/idlelib/idle_test/test_squeezer.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Lib/idlelib/idle_test/test_squeezer.py b/Lib/idlelib/idle_test/test_squeezer.py index 0fa50a29a60fd0c..1a9f9553ccfb89b 100644 --- a/Lib/idlelib/idle_test/test_squeezer.py +++ b/Lib/idlelib/idle_test/test_squeezer.py @@ -318,10 +318,9 @@ def test_reload(self): str(new_auto_squeeze_min_lines)) Squeezer.reload() + self.assertGreater(squeezer.zero_char_width, orig_zero_char_width) self.assertEqual(squeezer.auto_squeeze_min_lines, new_auto_squeeze_min_lines) - self.assertAlmostEqual(squeezer.zero_char_width, - 2 * orig_zero_char_width, 0) def test_reload_no_squeezer_instances(self): """Test that Squeezer.reload() runs without any instances existing.""" From 05543b0791aa44945e25c7dba7898e5082766c59 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Sat, 12 Jan 2019 14:22:10 -0500 Subject: [PATCH 23/25] Change 'name' to 'font' in load_font Font(). --- Lib/idlelib/squeezer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/idlelib/squeezer.py b/Lib/idlelib/squeezer.py index 999ec79ba7142f9..b810060efa13f04 100644 --- a/Lib/idlelib/squeezer.py +++ b/Lib/idlelib/squeezer.py @@ -317,7 +317,7 @@ def get_line_width(self): def load_font(self): text = self.base_text self.zero_char_width = \ - Font(text, name=text.cget('font')).measure('0') + Font(text, font=text.cget('font')).measure('0') def squeeze_current_text_event(self, event): """squeeze-current-text event handler From 66c01e5c94414adb3c7796b61cc7e0f4d65aa7a2 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Sat, 12 Jan 2019 17:13:43 -0500 Subject: [PATCH 24/25] Reset font size by resetting font option. --- Lib/idlelib/idle_test/test_squeezer.py | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/Lib/idlelib/idle_test/test_squeezer.py b/Lib/idlelib/idle_test/test_squeezer.py index 1a9f9553ccfb89b..7c28a107a90fb1e 100644 --- a/Lib/idlelib/idle_test/test_squeezer.py +++ b/Lib/idlelib/idle_test/test_squeezer.py @@ -114,7 +114,7 @@ def make_text_widget(self, root=None): if root is None: root = get_test_tk_root(self) text_widget = Text(root) - text_widget["font"] = idleConf.GetFont(root, "main", "EditorWindow") + text_widget["font"] = ('Courier', 10) text_widget.mark_set("iomark", "1.0") return text_widget @@ -299,19 +299,8 @@ def test_reload(self): orig_zero_char_width = squeezer.zero_char_width orig_auto_squeeze_min_lines = squeezer.auto_squeeze_min_lines - # Set the font size to double the original value. - prev_font_size = idleConf.GetOption( - 'main', 'EditorWindow', 'font-size', type='int', default=10) - new_font_size = 2 * prev_font_size - self.set_idleconf_option_with_cleanup( - 'main', 'EditorWindow', 'font-size', str(new_font_size)) - # Update the Text widget's font from the config. - # * This is usually done by the config dialog. - # * See: EditorWindow.ResetFont() - text_widget["font"] = idleConf.GetFont( - editwin.root, "main", "EditorWindow") - - # Set auto-squeeze-min-lines to 10 more than the original value. + # Increase both font size and auto-squeeze-min-lines. + text_widget["font"] = ('Courier', 20) new_auto_squeeze_min_lines = orig_auto_squeeze_min_lines + 10 self.set_idleconf_option_with_cleanup( 'main', 'PyShell', 'auto-squeeze-min-lines', From ae8a483615ed254084255ca4c67c95c0b890f092 Mon Sep 17 00:00:00 2001 From: Tal Einat Date: Sun, 13 Jan 2019 13:39:19 +0200 Subject: [PATCH 25/25] minor code cleanup in Squeezer's mywrite() --- Lib/idlelib/squeezer.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Lib/idlelib/squeezer.py b/Lib/idlelib/squeezer.py index b810060efa13f04..869498d753a2cd9 100644 --- a/Lib/idlelib/squeezer.py +++ b/Lib/idlelib/squeezer.py @@ -262,13 +262,11 @@ def mywrite(s, tags=(), write=editwin.write): # Only auto-squeeze text with at least the minimum # configured number of lines. - - # First, a very quick check to skip very short texts. - s_len = len(s) auto_squeeze_min_lines = self.auto_squeeze_min_lines - if s_len < 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)