bpo-33763: IDLE: Replace label widget with text widget in code context (GH-7367)
(cherry picked from commit b609e687a0
)
Co-authored-by: Cheryl Sabella <cheryl.sabella@gmail.com>
This commit is contained in:
parent
87936d03cb
commit
b7eb1024d0
|
@ -52,7 +52,7 @@ class CodeContext:
|
|||
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,
|
||||
|
@ -67,7 +67,7 @@ class CodeContext:
|
|||
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.
|
||||
|
@ -92,11 +92,11 @@ class CodeContext:
|
|||
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.
|
||||
#
|
||||
|
@ -110,20 +110,20 @@ class CodeContext:
|
|||
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):
|
||||
|
@ -161,9 +161,8 @@ class CodeContext:
|
|||
|
||||
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.
|
||||
|
@ -190,24 +189,29 @@ class CodeContext:
|
|||
# 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)
|
||||
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@ class CodeContextTest(unittest.TestCase):
|
|||
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)
|
||||
|
@ -75,11 +75,11 @@ class CodeContextTest(unittest.TestCase):
|
|||
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
|
||||
|
@ -89,7 +89,7 @@ class CodeContextTest(unittest.TestCase):
|
|||
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')
|
||||
|
@ -120,20 +120,20 @@ class CodeContextTest(unittest.TestCase):
|
|||
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
|
||||
|
@ -187,7 +187,7 @@ class CodeContextTest(unittest.TestCase):
|
|||
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.
|
||||
|
@ -200,21 +200,21 @@ class CodeContextTest(unittest.TestCase):
|
|||
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)
|
||||
|
@ -223,7 +223,7 @@ class CodeContextTest(unittest.TestCase):
|
|||
(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.
|
||||
|
@ -235,7 +235,7 @@ class CodeContextTest(unittest.TestCase):
|
|||
(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:')
|
||||
|
@ -249,7 +249,7 @@ class CodeContextTest(unittest.TestCase):
|
|||
(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:')
|
||||
|
@ -262,12 +262,12 @@ class CodeContextTest(unittest.TestCase):
|
|||
(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()
|
||||
|
@ -286,7 +286,7 @@ class CodeContextTest(unittest.TestCase):
|
|||
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.
|
||||
|
@ -303,18 +303,18 @@ class CodeContextTest(unittest.TestCase):
|
|||
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
|
||||
|
@ -322,9 +322,9 @@ class CodeContextTest(unittest.TestCase):
|
|||
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()
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
IDLE: Use read-only text widget for code context instead of label widget.
|
Loading…
Reference in New Issue