Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit d4b4c00

Browse files
authored
bpo-37929: IDLE: avoid Squeezer-related config dialog crashes (GH-15452)
These were caused by keeping around a reference to the Squeezer instance and calling it's load_font() upon config changes, which sometimes happened even if the shell window no longer existed. This change completely removes that mechanism, instead having the editor window properly update its width attribute, which can then be used by Squeezer.
1 parent aef9ad8 commit d4b4c00

File tree

4 files changed

+27
-50
lines changed

4 files changed

+27
-50
lines changed

Lib/idlelib/editor.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import webbrowser
1111

1212
from tkinter import *
13+
from tkinter.font import Font
1314
from tkinter.ttk import Scrollbar
1415
import tkinter.simpledialog as tkSimpleDialog
1516
import tkinter.messagebox as tkMessageBox
@@ -120,14 +121,13 @@ def __init__(self, flist=None, filename=None, key=None, root=None):
120121
self.prompt_last_line = '' # Override in PyShell
121122
self.text_frame = text_frame = Frame(top)
122123
self.vbar = vbar = Scrollbar(text_frame, name='vbar')
123-
self.width = idleConf.GetOption('main', 'EditorWindow',
124-
'width', type='int')
124+
width = idleConf.GetOption('main', 'EditorWindow', 'width', type='int')
125125
text_options = {
126126
'name': 'text',
127127
'padx': 5,
128128
'wrap': 'none',
129129
'highlightthickness': 0,
130-
'width': self.width,
130+
'width': width,
131131
'tabstyle': 'wordprocessor', # new in 8.5
132132
'height': idleConf.GetOption(
133133
'main', 'EditorWindow', 'height', type='int'),
@@ -154,6 +154,7 @@ def __init__(self, flist=None, filename=None, key=None, root=None):
154154
text.bind('<MouseWheel>', self.mousescroll)
155155
text.bind('<Button-4>', self.mousescroll)
156156
text.bind('<Button-5>', self.mousescroll)
157+
text.bind('<Configure>', self.handle_winconfig)
157158
text.bind("<<cut>>", self.cut)
158159
text.bind("<<copy>>", self.copy)
159160
text.bind("<<paste>>", self.paste)
@@ -211,6 +212,7 @@ def __init__(self, flist=None, filename=None, key=None, root=None):
211212
text['font'] = idleConf.GetFont(self.root, 'main', 'EditorWindow')
212213
text.grid(row=1, column=1, sticky=NSEW)
213214
text.focus_set()
215+
self.set_width()
214216

215217
# usetabs true -> literal tab characters are used by indent and
216218
# dedent cmds, possibly mixed with spaces if
@@ -338,6 +340,22 @@ def __init__(self, flist=None, filename=None, key=None, root=None):
338340
else:
339341
self.update_menu_state('options', '*Line Numbers', 'disabled')
340342

343+
def handle_winconfig(self, event=None):
344+
self.set_width()
345+
346+
def set_width(self):
347+
text = self.text
348+
inner_padding = sum(map(text.tk.getint, [text.cget('border'),
349+
text.cget('padx')]))
350+
pixel_width = text.winfo_width() - 2 * inner_padding
351+
352+
# Divide the width of the Text widget by the font width,
353+
# which is taken to be the width of '0' (zero).
354+
# http://www.tcl.tk/man/tcl8.6/TkCmd/text.htm#M21
355+
zero_char_width = \
356+
Font(text, font=text.cget('font')).measure('0')
357+
self.width = pixel_width // zero_char_width
358+
341359
def _filename_to_unicode(self, filename):
342360
"""Return filename as BMP unicode so displayable in Tk."""
343361
# Decode bytes to unicode.
@@ -830,6 +848,7 @@ def ResetFont(self):
830848
# Finally, update the main text widget.
831849
new_font = idleConf.GetFont(self.root, 'main', 'EditorWindow')
832850
self.text['font'] = new_font
851+
self.set_width()
833852

834853
def RemoveKeybindings(self):
835854
"Remove the keybindings before they are changed."

Lib/idlelib/idle_test/test_squeezer.py

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -82,18 +82,10 @@ def test_several_lines_different_lengths(self):
8282

8383
class SqueezerTest(unittest.TestCase):
8484
"""Tests for the Squeezer class."""
85-
def tearDown(self):
86-
# Clean up the Squeezer class's reference to its instance,
87-
# to avoid side-effects from one test case upon another.
88-
if Squeezer._instance_weakref is not None:
89-
Squeezer._instance_weakref = None
90-
9185
def make_mock_editor_window(self, with_text_widget=False):
9286
"""Create a mock EditorWindow instance."""
9387
editwin = NonCallableMagicMock()
94-
# isinstance(editwin, PyShell) must be true for Squeezer to enable
95-
# auto-squeezing; in practice this will always be true.
96-
editwin.__class__ = PyShell
88+
editwin.width = 80
9789

9890
if with_text_widget:
9991
editwin.root = get_test_tk_root(self)
@@ -107,7 +99,6 @@ def make_squeezer_instance(self, editor_window=None):
10799
if editor_window is None:
108100
editor_window = self.make_mock_editor_window()
109101
squeezer = Squeezer(editor_window)
110-
squeezer.get_line_width = Mock(return_value=80)
111102
return squeezer
112103

113104
def make_text_widget(self, root=None):
@@ -143,8 +134,8 @@ def test_count_lines(self):
143134
line_width=line_width,
144135
expected=expected):
145136
text = eval(text_code)
146-
squeezer.get_line_width.return_value = line_width
147-
self.assertEqual(squeezer.count_lines(text), expected)
137+
with patch.object(editwin, 'width', line_width):
138+
self.assertEqual(squeezer.count_lines(text), expected)
148139

149140
def test_init(self):
150141
"""Test the creation of Squeezer instances."""
@@ -294,7 +285,6 @@ def test_reload(self):
294285
"""Test the reload() class-method."""
295286
editwin = self.make_mock_editor_window(with_text_widget=True)
296287
squeezer = self.make_squeezer_instance(editwin)
297-
squeezer.load_font = Mock()
298288

299289
orig_auto_squeeze_min_lines = squeezer.auto_squeeze_min_lines
300290

@@ -307,7 +297,6 @@ def test_reload(self):
307297
Squeezer.reload()
308298
self.assertEqual(squeezer.auto_squeeze_min_lines,
309299
new_auto_squeeze_min_lines)
310-
squeezer.load_font.assert_called()
311300

312301
def test_reload_no_squeezer_instances(self):
313302
"""Test that Squeezer.reload() runs without any instances existing."""

Lib/idlelib/squeezer.py

Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,8 @@
1515
messages and their tracebacks.
1616
"""
1717
import re
18-
import weakref
1918

2019
import tkinter as tk
21-
from tkinter.font import Font
2220
import tkinter.messagebox as tkMessageBox
2321

2422
from idlelib.config import idleConf
@@ -203,8 +201,6 @@ class Squeezer:
203201
This avoids IDLE's shell slowing down considerably, and even becoming
204202
completely unresponsive, when very long outputs are written.
205203
"""
206-
_instance_weakref = None
207-
208204
@classmethod
209205
def reload(cls):
210206
"""Load class variables from config."""
@@ -213,14 +209,6 @@ def reload(cls):
213209
type="int", default=50,
214210
)
215211

216-
# Loading the font info requires a Tk root. IDLE doesn't rely
217-
# on Tkinter's "default root", so the instance will reload
218-
# font info using its editor windows's Tk root.
219-
if cls._instance_weakref is not None:
220-
instance = cls._instance_weakref()
221-
if instance is not None:
222-
instance.load_font()
223-
224212
def __init__(self, editwin):
225213
"""Initialize settings for Squeezer.
226214
@@ -241,9 +229,6 @@ def __init__(self, editwin):
241229
# however, needs to make such changes.
242230
self.base_text = editwin.per.bottom
243231

244-
Squeezer._instance_weakref = weakref.ref(self)
245-
self.load_font()
246-
247232
# Twice the text widget's border width and internal padding;
248233
# pre-calculated here for the get_line_width() method.
249234
self.window_width_delta = 2 * (
@@ -298,24 +283,7 @@ def count_lines(self, s):
298283
299284
Tabs are considered tabwidth characters long.
300285
"""
301-
linewidth = self.get_line_width()
302-
return count_lines_with_wrapping(s, linewidth)
303-
304-
def get_line_width(self):
305-
# The maximum line length in pixels: The width of the text
306-
# widget, minus twice the border width and internal padding.
307-
linewidth_pixels = \
308-
self.base_text.winfo_width() - self.window_width_delta
309-
310-
# Divide the width of the Text widget by the font width,
311-
# which is taken to be the width of '0' (zero).
312-
# http://www.tcl.tk/man/tcl8.6/TkCmd/text.htm#M21
313-
return linewidth_pixels // self.zero_char_width
314-
315-
def load_font(self):
316-
text = self.base_text
317-
self.zero_char_width = \
318-
Font(text, font=text.cget('font')).measure('0')
286+
return count_lines_with_wrapping(s, self.editwin.width)
319287

320288
def squeeze_current_text_event(self, event):
321289
"""squeeze-current-text event handler
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
IDLE Settings dialog now closes properly when there is no shell window.

0 commit comments

Comments
 (0)