Skip to content

Commit b7eb102

Browse files
bpo-33763: IDLE: Replace label widget with text widget in code context (GH-7367)
(cherry picked from commit b609e68) Co-authored-by: Cheryl Sabella <[email protected]>
1 parent 87936d0 commit b7eb102

File tree

3 files changed

+57
-52
lines changed

3 files changed

+57
-52
lines changed

Lib/idlelib/codecontext.py

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def __init__(self, editwin):
5252
self.text is the editor window text widget.
5353
self.textfont is the editor window font.
5454
55-
self.label displays the code context text above the editor text.
55+
self.context displays the code context text above the editor text.
5656
Initially None, it is toggled via <<toggle-code-context>>.
5757
self.topvisible is the number of the top text line displayed.
5858
self.info is a list of (line number, indent level, line text,
@@ -67,7 +67,7 @@ def __init__(self, editwin):
6767
self.text = editwin.text
6868
self.textfont = self.text["font"]
6969
self.contextcolors = CodeContext.colors
70-
self.label = None
70+
self.context = None
7171
self.topvisible = 1
7272
self.info = [(0, -1, "", False)]
7373
# Start two update cycles, one for context lines, one for font changes.
@@ -92,11 +92,11 @@ def __del__(self):
9292
def toggle_code_context_event(self, event=None):
9393
"""Toggle code context display.
9494
95-
If self.label doesn't exist, create it to match the size of the editor
95+
If self.context doesn't exist, create it to match the size of the editor
9696
window text (toggle on). If it does exist, destroy it (toggle off).
9797
Return 'break' to complete the processing of the binding.
9898
"""
99-
if not self.label:
99+
if not self.context:
100100
# Calculate the border width and horizontal padding required to
101101
# align the context with the text in the main Text widget.
102102
#
@@ -110,20 +110,20 @@ def toggle_code_context_event(self, event=None):
110110
padx += widget.tk.getint(widget.pack_info()['padx'])
111111
padx += widget.tk.getint(widget.cget('padx'))
112112
border += widget.tk.getint(widget.cget('border'))
113-
self.label = tkinter.Label(
114-
self.editwin.top, text="",
115-
anchor=W, justify=LEFT, font=self.textfont,
113+
self.context = tkinter.Text(
114+
self.editwin.top, font=self.textfont,
116115
bg=self.contextcolors['background'],
117116
fg=self.contextcolors['foreground'],
117+
height=1,
118118
width=1, # Don't request more than we get.
119-
padx=padx, border=border, relief=SUNKEN)
120-
# Pack the label widget before and above the text_frame widget,
119+
padx=padx, border=border, relief=SUNKEN, state='disabled')
120+
# Pack the context widget before and above the text_frame widget,
121121
# thus ensuring that it will appear directly above text_frame.
122-
self.label.pack(side=TOP, fill=X, expand=False,
122+
self.context.pack(side=TOP, fill=X, expand=False,
123123
before=self.editwin.text_frame)
124124
else:
125-
self.label.destroy()
126-
self.label = None
125+
self.context.destroy()
126+
self.context = None
127127
return "break"
128128

129129
def get_context(self, new_topvisible, stopline=1, stopindent=0):
@@ -161,9 +161,8 @@ def update_code_context(self):
161161
162162
No update is done if the text hasn't been scrolled. If the text
163163
was scrolled, the lines that should be shown in the context will
164-
be retrieved and the label widget will be updated with the code,
165-
padded with blank lines so that the code appears on the bottom of
166-
the context label.
164+
be retrieved and the context area will be updated with the code,
165+
up to the number of maxlines.
167166
"""
168167
new_topvisible = int(self.text.index("@0,0").split('.')[0])
169168
if self.topvisible == new_topvisible: # Haven't scrolled.
@@ -190,24 +189,29 @@ def update_code_context(self):
190189
# Last context_depth context lines.
191190
context_strings = [x[2] for x in self.info[-self.context_depth:]]
192191
showfirst = 0 if context_strings[0] else 1
193-
self.label["text"] = '\n'.join(context_strings[showfirst:])
192+
# Update widget.
193+
self.context['height'] = len(context_strings) - showfirst
194+
self.context['state'] = 'normal'
195+
self.context.delete('1.0', 'end')
196+
self.context.insert('end', '\n'.join(context_strings[showfirst:]))
197+
self.context['state'] = 'disabled'
194198

195199
def timer_event(self):
196200
"Event on editor text widget triggered every UPDATEINTERVAL ms."
197-
if self.label:
201+
if self.context:
198202
self.update_code_context()
199203
self.t1 = self.text.after(UPDATEINTERVAL, self.timer_event)
200204

201205
def config_timer_event(self):
202206
"Event on editor text widget triggered every CONFIGUPDATEINTERVAL ms."
203207
newtextfont = self.text["font"]
204-
if (self.label and (newtextfont != self.textfont or
208+
if (self.context and (newtextfont != self.textfont or
205209
CodeContext.colors != self.contextcolors)):
206210
self.textfont = newtextfont
207211
self.contextcolors = CodeContext.colors
208-
self.label["font"] = self.textfont
209-
self.label['background'] = self.contextcolors['background']
210-
self.label['foreground'] = self.contextcolors['foreground']
212+
self.context["font"] = self.textfont
213+
self.context['background'] = self.contextcolors['background']
214+
self.context['foreground'] = self.contextcolors['foreground']
211215
self.t2 = self.text.after(CONFIGUPDATEINTERVAL, self.config_timer_event)
212216

213217

Lib/idlelib/idle_test/test_codecontext.py

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def setUpClass(cls):
5656
frame = cls.frame = Frame(root)
5757
text = cls.text = Text(frame)
5858
text.insert('1.0', code_sample)
59-
# Need to pack for creation of code context label widget.
59+
# Need to pack for creation of code context text widget.
6060
frame.pack(side='left', fill='both', expand=1)
6161
text.pack(side='top', fill='both', expand=1)
6262
cls.editor = DummyEditwin(root, frame, text)
@@ -75,11 +75,11 @@ def setUp(self):
7575
self.cc = codecontext.CodeContext(self.editor)
7676

7777
def tearDown(self):
78-
if self.cc.label:
79-
self.cc.label.destroy()
78+
if self.cc.context:
79+
self.cc.context.destroy()
8080
# Explicitly call __del__ to remove scheduled scripts.
8181
self.cc.__del__()
82-
del self.cc.label, self.cc
82+
del self.cc.context, self.cc
8383

8484
def test_init(self):
8585
eq = self.assertEqual
@@ -89,7 +89,7 @@ def test_init(self):
8989
eq(cc.editwin, ed)
9090
eq(cc.text, ed.text)
9191
eq(cc.textfont, ed.text['font'])
92-
self.assertIsNone(cc.label)
92+
self.assertIsNone(cc.context)
9393
eq(cc.info, [(0, -1, '', False)])
9494
eq(cc.topvisible, 1)
9595
eq(self.root.tk.call('after', 'info', self.cc.t1)[1], 'timer')
@@ -120,20 +120,20 @@ def test_toggle_code_context_event(self):
120120
toggle = cc.toggle_code_context_event
121121

122122
# Make sure code context is off.
123-
if cc.label:
123+
if cc.context:
124124
toggle()
125125

126126
# Toggle on.
127127
eq(toggle(), 'break')
128-
self.assertIsNotNone(cc.label)
129-
eq(cc.label['font'], cc.textfont)
130-
eq(cc.label['fg'], cc.colors['foreground'])
131-
eq(cc.label['bg'], cc.colors['background'])
132-
eq(cc.label['text'], '')
128+
self.assertIsNotNone(cc.context)
129+
eq(cc.context['font'], cc.textfont)
130+
eq(cc.context['fg'], cc.colors['foreground'])
131+
eq(cc.context['bg'], cc.colors['background'])
132+
eq(cc.context.get('1.0', 'end-1c'), '')
133133

134134
# Toggle off.
135135
eq(toggle(), 'break')
136-
self.assertIsNone(cc.label)
136+
self.assertIsNone(cc.context)
137137

138138
def test_get_context(self):
139139
eq = self.assertEqual
@@ -187,7 +187,7 @@ def test_update_code_context(self):
187187
eq = self.assertEqual
188188
cc = self.cc
189189
# Ensure code context is active.
190-
if not cc.label:
190+
if not cc.context:
191191
cc.toggle_code_context_event()
192192

193193
# Invoke update_code_context without scrolling - nothing happens.
@@ -200,21 +200,21 @@ def test_update_code_context(self):
200200
cc.update_code_context()
201201
eq(cc.info, [(0, -1, '', False)])
202202
eq(cc.topvisible, 2)
203-
eq(cc.label['text'], '')
203+
eq(cc.context.get('1.0', 'end-1c'), '')
204204

205205
# Scroll down to line 2.
206206
cc.text.yview(2)
207207
cc.update_code_context()
208208
eq(cc.info, [(0, -1, '', False), (2, 0, 'class C1():', 'class')])
209209
eq(cc.topvisible, 3)
210-
eq(cc.label['text'], 'class C1():')
210+
eq(cc.context.get('1.0', 'end-1c'), 'class C1():')
211211

212212
# Scroll down to line 3. Since it's a comment, nothing changes.
213213
cc.text.yview(3)
214214
cc.update_code_context()
215215
eq(cc.info, [(0, -1, '', False), (2, 0, 'class C1():', 'class')])
216216
eq(cc.topvisible, 4)
217-
eq(cc.label['text'], 'class C1():')
217+
eq(cc.context.get('1.0', 'end-1c'), 'class C1():')
218218

219219
# Scroll down to line 4.
220220
cc.text.yview(4)
@@ -223,7 +223,7 @@ def test_update_code_context(self):
223223
(2, 0, 'class C1():', 'class'),
224224
(4, 4, ' def __init__(self, a, b):', 'def')])
225225
eq(cc.topvisible, 5)
226-
eq(cc.label['text'], 'class C1():\n'
226+
eq(cc.context.get('1.0', 'end-1c'), 'class C1():\n'
227227
' def __init__(self, a, b):')
228228

229229
# Scroll down to line 11. Last 'def' is removed.
@@ -235,7 +235,7 @@ def test_update_code_context(self):
235235
(8, 8, ' if a > b:', 'if'),
236236
(10, 8, ' elif a < b:', 'elif')])
237237
eq(cc.topvisible, 12)
238-
eq(cc.label['text'], 'class C1():\n'
238+
eq(cc.context.get('1.0', 'end-1c'), 'class C1():\n'
239239
' def compare(self):\n'
240240
' if a > b:\n'
241241
' elif a < b:')
@@ -249,7 +249,7 @@ def test_update_code_context(self):
249249
(8, 8, ' if a > b:', 'if'),
250250
(10, 8, ' elif a < b:', 'elif')])
251251
eq(cc.topvisible, 12)
252-
eq(cc.label['text'], 'class C1():\n'
252+
eq(cc.context.get('1.0', 'end-1c'), 'class C1():\n'
253253
' def compare(self):\n'
254254
' if a > b:\n'
255255
' elif a < b:')
@@ -262,12 +262,12 @@ def test_update_code_context(self):
262262
(4, 4, ' def __init__(self, a, b):', 'def')])
263263
eq(cc.topvisible, 6)
264264
# context_depth is 1.
265-
eq(cc.label['text'], ' def __init__(self, a, b):')
265+
eq(cc.context.get('1.0', 'end-1c'), ' def __init__(self, a, b):')
266266

267267
@mock.patch.object(codecontext.CodeContext, 'update_code_context')
268268
def test_timer_event(self, mock_update):
269269
# Ensure code context is not active.
270-
if self.cc.label:
270+
if self.cc.context:
271271
self.cc.toggle_code_context_event()
272272
self.cc.timer_event()
273273
mock_update.assert_not_called()
@@ -286,7 +286,7 @@ def test_config_timer_event(self):
286286
test_colors = {'background': '#222222', 'foreground': '#ffff00'}
287287

288288
# Ensure code context is not active.
289-
if cc.label:
289+
if cc.context:
290290
cc.toggle_code_context_event()
291291

292292
# Nothing updates on inactive code context.
@@ -303,28 +303,28 @@ def test_config_timer_event(self):
303303
cc.config_timer_event()
304304
eq(cc.textfont, save_font)
305305
eq(cc.contextcolors, save_colors)
306-
eq(cc.label['font'], save_font)
307-
eq(cc.label['background'], save_colors['background'])
308-
eq(cc.label['foreground'], save_colors['foreground'])
306+
eq(cc.context['font'], save_font)
307+
eq(cc.context['background'], save_colors['background'])
308+
eq(cc.context['foreground'], save_colors['foreground'])
309309

310310
# Active code context, change font.
311311
cc.text['font'] = test_font
312312
cc.config_timer_event()
313313
eq(cc.textfont, test_font)
314314
eq(cc.contextcolors, save_colors)
315-
eq(cc.label['font'], test_font)
316-
eq(cc.label['background'], save_colors['background'])
317-
eq(cc.label['foreground'], save_colors['foreground'])
315+
eq(cc.context['font'], test_font)
316+
eq(cc.context['background'], save_colors['background'])
317+
eq(cc.context['foreground'], save_colors['foreground'])
318318

319319
# Active code context, change color.
320320
cc.text['font'] = save_font
321321
codecontext.CodeContext.colors = test_colors
322322
cc.config_timer_event()
323323
eq(cc.textfont, save_font)
324324
eq(cc.contextcolors, test_colors)
325-
eq(cc.label['font'], save_font)
326-
eq(cc.label['background'], test_colors['background'])
327-
eq(cc.label['foreground'], test_colors['foreground'])
325+
eq(cc.context['font'], save_font)
326+
eq(cc.context['background'], test_colors['background'])
327+
eq(cc.context['foreground'], test_colors['foreground'])
328328
codecontext.CodeContext.colors = save_colors
329329
cc.config_timer_event()
330330

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
IDLE: Use read-only text widget for code context instead of label widget.

0 commit comments

Comments
 (0)