Skip to content

[3.6] bpo-33763: IDLE: Replace label widget with text widget in code context (GH-7367) #7400

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 4, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 25 additions & 21 deletions Lib/idlelib/codecontext.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def __init__(self, editwin):
self.text is the editor window text widget.
self.textfont is the editor window font.

self.label displays the code context text above the editor text.
self.context displays the code context text above the editor text.
Initially None, it is toggled via <<toggle-code-context>>.
self.topvisible is the number of the top text line displayed.
self.info is a list of (line number, indent level, line text,
Expand All @@ -67,7 +67,7 @@ def __init__(self, editwin):
self.text = editwin.text
self.textfont = self.text["font"]
self.contextcolors = CodeContext.colors
self.label = None
self.context = None
self.topvisible = 1
self.info = [(0, -1, "", False)]
# Start two update cycles, one for context lines, one for font changes.
Expand All @@ -92,11 +92,11 @@ def __del__(self):
def toggle_code_context_event(self, event=None):
"""Toggle code context display.

If self.label doesn't exist, create it to match the size of the editor
If self.context doesn't exist, create it to match the size of the editor
window text (toggle on). If it does exist, destroy it (toggle off).
Return 'break' to complete the processing of the binding.
"""
if not self.label:
if not self.context:
# Calculate the border width and horizontal padding required to
# align the context with the text in the main Text widget.
#
Expand All @@ -110,20 +110,20 @@ def toggle_code_context_event(self, event=None):
padx += widget.tk.getint(widget.pack_info()['padx'])
padx += widget.tk.getint(widget.cget('padx'))
border += widget.tk.getint(widget.cget('border'))
self.label = tkinter.Label(
self.editwin.top, text="",
anchor=W, justify=LEFT, font=self.textfont,
self.context = tkinter.Text(
self.editwin.top, font=self.textfont,
bg=self.contextcolors['background'],
fg=self.contextcolors['foreground'],
height=1,
width=1, # Don't request more than we get.
padx=padx, border=border, relief=SUNKEN)
# Pack the label widget before and above the text_frame widget,
padx=padx, border=border, relief=SUNKEN, state='disabled')
# Pack the context widget before and above the text_frame widget,
# thus ensuring that it will appear directly above text_frame.
self.label.pack(side=TOP, fill=X, expand=False,
self.context.pack(side=TOP, fill=X, expand=False,
before=self.editwin.text_frame)
else:
self.label.destroy()
self.label = None
self.context.destroy()
self.context = None
return "break"

def get_context(self, new_topvisible, stopline=1, stopindent=0):
Expand Down Expand Up @@ -161,9 +161,8 @@ def update_code_context(self):

No update is done if the text hasn't been scrolled. If the text
was scrolled, the lines that should be shown in the context will
be retrieved and the label widget will be updated with the code,
padded with blank lines so that the code appears on the bottom of
the context label.
be retrieved and the context area will be updated with the code,
up to the number of maxlines.
"""
new_topvisible = int(self.text.index("@0,0").split('.')[0])
if self.topvisible == new_topvisible: # Haven't scrolled.
Expand All @@ -190,24 +189,29 @@ def update_code_context(self):
# Last context_depth context lines.
context_strings = [x[2] for x in self.info[-self.context_depth:]]
showfirst = 0 if context_strings[0] else 1
self.label["text"] = '\n'.join(context_strings[showfirst:])
# Update widget.
self.context['height'] = len(context_strings) - showfirst
self.context['state'] = 'normal'
self.context.delete('1.0', 'end')
self.context.insert('end', '\n'.join(context_strings[showfirst:]))
self.context['state'] = 'disabled'

def timer_event(self):
"Event on editor text widget triggered every UPDATEINTERVAL ms."
if self.label:
if self.context:
self.update_code_context()
self.t1 = self.text.after(UPDATEINTERVAL, self.timer_event)

def config_timer_event(self):
"Event on editor text widget triggered every CONFIGUPDATEINTERVAL ms."
newtextfont = self.text["font"]
if (self.label and (newtextfont != self.textfont or
if (self.context and (newtextfont != self.textfont or
CodeContext.colors != self.contextcolors)):
self.textfont = newtextfont
self.contextcolors = CodeContext.colors
self.label["font"] = self.textfont
self.label['background'] = self.contextcolors['background']
self.label['foreground'] = self.contextcolors['foreground']
self.context["font"] = self.textfont
self.context['background'] = self.contextcolors['background']
self.context['foreground'] = self.contextcolors['foreground']
self.t2 = self.text.after(CONFIGUPDATEINTERVAL, self.config_timer_event)


Expand Down
62 changes: 31 additions & 31 deletions Lib/idlelib/idle_test/test_codecontext.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def setUpClass(cls):
frame = cls.frame = Frame(root)
text = cls.text = Text(frame)
text.insert('1.0', code_sample)
# Need to pack for creation of code context label widget.
# Need to pack for creation of code context text widget.
frame.pack(side='left', fill='both', expand=1)
text.pack(side='top', fill='both', expand=1)
cls.editor = DummyEditwin(root, frame, text)
Expand All @@ -75,11 +75,11 @@ def setUp(self):
self.cc = codecontext.CodeContext(self.editor)

def tearDown(self):
if self.cc.label:
self.cc.label.destroy()
if self.cc.context:
self.cc.context.destroy()
# Explicitly call __del__ to remove scheduled scripts.
self.cc.__del__()
del self.cc.label, self.cc
del self.cc.context, self.cc

def test_init(self):
eq = self.assertEqual
Expand All @@ -89,7 +89,7 @@ def test_init(self):
eq(cc.editwin, ed)
eq(cc.text, ed.text)
eq(cc.textfont, ed.text['font'])
self.assertIsNone(cc.label)
self.assertIsNone(cc.context)
eq(cc.info, [(0, -1, '', False)])
eq(cc.topvisible, 1)
eq(self.root.tk.call('after', 'info', self.cc.t1)[1], 'timer')
Expand Down Expand Up @@ -120,20 +120,20 @@ def test_toggle_code_context_event(self):
toggle = cc.toggle_code_context_event

# Make sure code context is off.
if cc.label:
if cc.context:
toggle()

# Toggle on.
eq(toggle(), 'break')
self.assertIsNotNone(cc.label)
eq(cc.label['font'], cc.textfont)
eq(cc.label['fg'], cc.colors['foreground'])
eq(cc.label['bg'], cc.colors['background'])
eq(cc.label['text'], '')
self.assertIsNotNone(cc.context)
eq(cc.context['font'], cc.textfont)
eq(cc.context['fg'], cc.colors['foreground'])
eq(cc.context['bg'], cc.colors['background'])
eq(cc.context.get('1.0', 'end-1c'), '')

# Toggle off.
eq(toggle(), 'break')
self.assertIsNone(cc.label)
self.assertIsNone(cc.context)

def test_get_context(self):
eq = self.assertEqual
Expand Down Expand Up @@ -187,7 +187,7 @@ def test_update_code_context(self):
eq = self.assertEqual
cc = self.cc
# Ensure code context is active.
if not cc.label:
if not cc.context:
cc.toggle_code_context_event()

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

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

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

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

# Scroll down to line 11. Last 'def' is removed.
Expand All @@ -235,7 +235,7 @@ def test_update_code_context(self):
(8, 8, ' if a > b:', 'if'),
(10, 8, ' elif a < b:', 'elif')])
eq(cc.topvisible, 12)
eq(cc.label['text'], 'class C1():\n'
eq(cc.context.get('1.0', 'end-1c'), 'class C1():\n'
' def compare(self):\n'
' if a > b:\n'
' elif a < b:')
Expand All @@ -249,7 +249,7 @@ def test_update_code_context(self):
(8, 8, ' if a > b:', 'if'),
(10, 8, ' elif a < b:', 'elif')])
eq(cc.topvisible, 12)
eq(cc.label['text'], 'class C1():\n'
eq(cc.context.get('1.0', 'end-1c'), 'class C1():\n'
' def compare(self):\n'
' if a > b:\n'
' elif a < b:')
Expand All @@ -262,12 +262,12 @@ def test_update_code_context(self):
(4, 4, ' def __init__(self, a, b):', 'def')])
eq(cc.topvisible, 6)
# context_depth is 1.
eq(cc.label['text'], ' def __init__(self, a, b):')
eq(cc.context.get('1.0', 'end-1c'), ' def __init__(self, a, b):')

@mock.patch.object(codecontext.CodeContext, 'update_code_context')
def test_timer_event(self, mock_update):
# Ensure code context is not active.
if self.cc.label:
if self.cc.context:
self.cc.toggle_code_context_event()
self.cc.timer_event()
mock_update.assert_not_called()
Expand All @@ -286,7 +286,7 @@ def test_config_timer_event(self):
test_colors = {'background': '#222222', 'foreground': '#ffff00'}

# Ensure code context is not active.
if cc.label:
if cc.context:
cc.toggle_code_context_event()

# Nothing updates on inactive code context.
Expand All @@ -303,28 +303,28 @@ def test_config_timer_event(self):
cc.config_timer_event()
eq(cc.textfont, save_font)
eq(cc.contextcolors, save_colors)
eq(cc.label['font'], save_font)
eq(cc.label['background'], save_colors['background'])
eq(cc.label['foreground'], save_colors['foreground'])
eq(cc.context['font'], save_font)
eq(cc.context['background'], save_colors['background'])
eq(cc.context['foreground'], save_colors['foreground'])

# Active code context, change font.
cc.text['font'] = test_font
cc.config_timer_event()
eq(cc.textfont, test_font)
eq(cc.contextcolors, save_colors)
eq(cc.label['font'], test_font)
eq(cc.label['background'], save_colors['background'])
eq(cc.label['foreground'], save_colors['foreground'])
eq(cc.context['font'], test_font)
eq(cc.context['background'], save_colors['background'])
eq(cc.context['foreground'], save_colors['foreground'])

# Active code context, change color.
cc.text['font'] = save_font
codecontext.CodeContext.colors = test_colors
cc.config_timer_event()
eq(cc.textfont, save_font)
eq(cc.contextcolors, test_colors)
eq(cc.label['font'], save_font)
eq(cc.label['background'], test_colors['background'])
eq(cc.label['foreground'], test_colors['foreground'])
eq(cc.context['font'], save_font)
eq(cc.context['background'], test_colors['background'])
eq(cc.context['foreground'], test_colors['foreground'])
codecontext.CodeContext.colors = save_colors
cc.config_timer_event()

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
IDLE: Use read-only text widget for code context instead of label widget.