bpo-33763: IDLE: Replace label widget with text widget in code context (GH-7367)
This commit is contained in:
parent
d49dbd9acc
commit
b609e687a0
|
@ -52,7 +52,7 @@ class CodeContext:
|
||||||
self.text is the editor window text widget.
|
self.text is the editor window text widget.
|
||||||
self.textfont is the editor window font.
|
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>>.
|
Initially None, it is toggled via <<toggle-code-context>>.
|
||||||
self.topvisible is the number of the top text line displayed.
|
self.topvisible is the number of the top text line displayed.
|
||||||
self.info is a list of (line number, indent level, line text,
|
self.info is a list of (line number, indent level, line text,
|
||||||
|
@ -67,7 +67,7 @@ class CodeContext:
|
||||||
self.text = editwin.text
|
self.text = editwin.text
|
||||||
self.textfont = self.text["font"]
|
self.textfont = self.text["font"]
|
||||||
self.contextcolors = CodeContext.colors
|
self.contextcolors = CodeContext.colors
|
||||||
self.label = None
|
self.context = None
|
||||||
self.topvisible = 1
|
self.topvisible = 1
|
||||||
self.info = [(0, -1, "", False)]
|
self.info = [(0, -1, "", False)]
|
||||||
# Start two update cycles, one for context lines, one for font changes.
|
# 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):
|
def toggle_code_context_event(self, event=None):
|
||||||
"""Toggle code context display.
|
"""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).
|
window text (toggle on). If it does exist, destroy it (toggle off).
|
||||||
Return 'break' to complete the processing of the binding.
|
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
|
# Calculate the border width and horizontal padding required to
|
||||||
# align the context with the text in the main Text widget.
|
# 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.pack_info()['padx'])
|
||||||
padx += widget.tk.getint(widget.cget('padx'))
|
padx += widget.tk.getint(widget.cget('padx'))
|
||||||
border += widget.tk.getint(widget.cget('border'))
|
border += widget.tk.getint(widget.cget('border'))
|
||||||
self.label = tkinter.Label(
|
self.context = tkinter.Text(
|
||||||
self.editwin.top, text="",
|
self.editwin.top, font=self.textfont,
|
||||||
anchor=W, justify=LEFT, font=self.textfont,
|
|
||||||
bg=self.contextcolors['background'],
|
bg=self.contextcolors['background'],
|
||||||
fg=self.contextcolors['foreground'],
|
fg=self.contextcolors['foreground'],
|
||||||
|
height=1,
|
||||||
width=1, # Don't request more than we get.
|
width=1, # Don't request more than we get.
|
||||||
padx=padx, border=border, relief=SUNKEN)
|
padx=padx, border=border, relief=SUNKEN, state='disabled')
|
||||||
# Pack the label widget before and above the text_frame widget,
|
# Pack the context widget before and above the text_frame widget,
|
||||||
# thus ensuring that it will appear directly above text_frame.
|
# 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)
|
before=self.editwin.text_frame)
|
||||||
else:
|
else:
|
||||||
self.label.destroy()
|
self.context.destroy()
|
||||||
self.label = None
|
self.context = None
|
||||||
return "break"
|
return "break"
|
||||||
|
|
||||||
def get_context(self, new_topvisible, stopline=1, stopindent=0):
|
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
|
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
|
was scrolled, the lines that should be shown in the context will
|
||||||
be retrieved and the label widget will be updated with the code,
|
be retrieved and the context area will be updated with the code,
|
||||||
padded with blank lines so that the code appears on the bottom of
|
up to the number of maxlines.
|
||||||
the context label.
|
|
||||||
"""
|
"""
|
||||||
new_topvisible = int(self.text.index("@0,0").split('.')[0])
|
new_topvisible = int(self.text.index("@0,0").split('.')[0])
|
||||||
if self.topvisible == new_topvisible: # Haven't scrolled.
|
if self.topvisible == new_topvisible: # Haven't scrolled.
|
||||||
|
@ -190,24 +189,29 @@ class CodeContext:
|
||||||
# Last context_depth context lines.
|
# Last context_depth context lines.
|
||||||
context_strings = [x[2] for x in self.info[-self.context_depth:]]
|
context_strings = [x[2] for x in self.info[-self.context_depth:]]
|
||||||
showfirst = 0 if context_strings[0] else 1
|
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):
|
def timer_event(self):
|
||||||
"Event on editor text widget triggered every UPDATEINTERVAL ms."
|
"Event on editor text widget triggered every UPDATEINTERVAL ms."
|
||||||
if self.label:
|
if self.context:
|
||||||
self.update_code_context()
|
self.update_code_context()
|
||||||
self.t1 = self.text.after(UPDATEINTERVAL, self.timer_event)
|
self.t1 = self.text.after(UPDATEINTERVAL, self.timer_event)
|
||||||
|
|
||||||
def config_timer_event(self):
|
def config_timer_event(self):
|
||||||
"Event on editor text widget triggered every CONFIGUPDATEINTERVAL ms."
|
"Event on editor text widget triggered every CONFIGUPDATEINTERVAL ms."
|
||||||
newtextfont = self.text["font"]
|
newtextfont = self.text["font"]
|
||||||
if (self.label and (newtextfont != self.textfont or
|
if (self.context and (newtextfont != self.textfont or
|
||||||
CodeContext.colors != self.contextcolors)):
|
CodeContext.colors != self.contextcolors)):
|
||||||
self.textfont = newtextfont
|
self.textfont = newtextfont
|
||||||
self.contextcolors = CodeContext.colors
|
self.contextcolors = CodeContext.colors
|
||||||
self.label["font"] = self.textfont
|
self.context["font"] = self.textfont
|
||||||
self.label['background'] = self.contextcolors['background']
|
self.context['background'] = self.contextcolors['background']
|
||||||
self.label['foreground'] = self.contextcolors['foreground']
|
self.context['foreground'] = self.contextcolors['foreground']
|
||||||
self.t2 = self.text.after(CONFIGUPDATEINTERVAL, self.config_timer_event)
|
self.t2 = self.text.after(CONFIGUPDATEINTERVAL, self.config_timer_event)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,7 @@ class CodeContextTest(unittest.TestCase):
|
||||||
frame = cls.frame = Frame(root)
|
frame = cls.frame = Frame(root)
|
||||||
text = cls.text = Text(frame)
|
text = cls.text = Text(frame)
|
||||||
text.insert('1.0', code_sample)
|
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)
|
frame.pack(side='left', fill='both', expand=1)
|
||||||
text.pack(side='top', fill='both', expand=1)
|
text.pack(side='top', fill='both', expand=1)
|
||||||
cls.editor = DummyEditwin(root, frame, text)
|
cls.editor = DummyEditwin(root, frame, text)
|
||||||
|
@ -75,11 +75,11 @@ class CodeContextTest(unittest.TestCase):
|
||||||
self.cc = codecontext.CodeContext(self.editor)
|
self.cc = codecontext.CodeContext(self.editor)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
if self.cc.label:
|
if self.cc.context:
|
||||||
self.cc.label.destroy()
|
self.cc.context.destroy()
|
||||||
# Explicitly call __del__ to remove scheduled scripts.
|
# Explicitly call __del__ to remove scheduled scripts.
|
||||||
self.cc.__del__()
|
self.cc.__del__()
|
||||||
del self.cc.label, self.cc
|
del self.cc.context, self.cc
|
||||||
|
|
||||||
def test_init(self):
|
def test_init(self):
|
||||||
eq = self.assertEqual
|
eq = self.assertEqual
|
||||||
|
@ -89,7 +89,7 @@ class CodeContextTest(unittest.TestCase):
|
||||||
eq(cc.editwin, ed)
|
eq(cc.editwin, ed)
|
||||||
eq(cc.text, ed.text)
|
eq(cc.text, ed.text)
|
||||||
eq(cc.textfont, ed.text['font'])
|
eq(cc.textfont, ed.text['font'])
|
||||||
self.assertIsNone(cc.label)
|
self.assertIsNone(cc.context)
|
||||||
eq(cc.info, [(0, -1, '', False)])
|
eq(cc.info, [(0, -1, '', False)])
|
||||||
eq(cc.topvisible, 1)
|
eq(cc.topvisible, 1)
|
||||||
eq(self.root.tk.call('after', 'info', self.cc.t1)[1], 'timer')
|
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
|
toggle = cc.toggle_code_context_event
|
||||||
|
|
||||||
# Make sure code context is off.
|
# Make sure code context is off.
|
||||||
if cc.label:
|
if cc.context:
|
||||||
toggle()
|
toggle()
|
||||||
|
|
||||||
# Toggle on.
|
# Toggle on.
|
||||||
eq(toggle(), 'break')
|
eq(toggle(), 'break')
|
||||||
self.assertIsNotNone(cc.label)
|
self.assertIsNotNone(cc.context)
|
||||||
eq(cc.label['font'], cc.textfont)
|
eq(cc.context['font'], cc.textfont)
|
||||||
eq(cc.label['fg'], cc.colors['foreground'])
|
eq(cc.context['fg'], cc.colors['foreground'])
|
||||||
eq(cc.label['bg'], cc.colors['background'])
|
eq(cc.context['bg'], cc.colors['background'])
|
||||||
eq(cc.label['text'], '')
|
eq(cc.context.get('1.0', 'end-1c'), '')
|
||||||
|
|
||||||
# Toggle off.
|
# Toggle off.
|
||||||
eq(toggle(), 'break')
|
eq(toggle(), 'break')
|
||||||
self.assertIsNone(cc.label)
|
self.assertIsNone(cc.context)
|
||||||
|
|
||||||
def test_get_context(self):
|
def test_get_context(self):
|
||||||
eq = self.assertEqual
|
eq = self.assertEqual
|
||||||
|
@ -187,7 +187,7 @@ class CodeContextTest(unittest.TestCase):
|
||||||
eq = self.assertEqual
|
eq = self.assertEqual
|
||||||
cc = self.cc
|
cc = self.cc
|
||||||
# Ensure code context is active.
|
# Ensure code context is active.
|
||||||
if not cc.label:
|
if not cc.context:
|
||||||
cc.toggle_code_context_event()
|
cc.toggle_code_context_event()
|
||||||
|
|
||||||
# Invoke update_code_context without scrolling - nothing happens.
|
# Invoke update_code_context without scrolling - nothing happens.
|
||||||
|
@ -200,21 +200,21 @@ class CodeContextTest(unittest.TestCase):
|
||||||
cc.update_code_context()
|
cc.update_code_context()
|
||||||
eq(cc.info, [(0, -1, '', False)])
|
eq(cc.info, [(0, -1, '', False)])
|
||||||
eq(cc.topvisible, 2)
|
eq(cc.topvisible, 2)
|
||||||
eq(cc.label['text'], '')
|
eq(cc.context.get('1.0', 'end-1c'), '')
|
||||||
|
|
||||||
# Scroll down to line 2.
|
# Scroll down to line 2.
|
||||||
cc.text.yview(2)
|
cc.text.yview(2)
|
||||||
cc.update_code_context()
|
cc.update_code_context()
|
||||||
eq(cc.info, [(0, -1, '', False), (2, 0, 'class C1():', 'class')])
|
eq(cc.info, [(0, -1, '', False), (2, 0, 'class C1():', 'class')])
|
||||||
eq(cc.topvisible, 3)
|
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.
|
# Scroll down to line 3. Since it's a comment, nothing changes.
|
||||||
cc.text.yview(3)
|
cc.text.yview(3)
|
||||||
cc.update_code_context()
|
cc.update_code_context()
|
||||||
eq(cc.info, [(0, -1, '', False), (2, 0, 'class C1():', 'class')])
|
eq(cc.info, [(0, -1, '', False), (2, 0, 'class C1():', 'class')])
|
||||||
eq(cc.topvisible, 4)
|
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.
|
# Scroll down to line 4.
|
||||||
cc.text.yview(4)
|
cc.text.yview(4)
|
||||||
|
@ -223,7 +223,7 @@ class CodeContextTest(unittest.TestCase):
|
||||||
(2, 0, 'class C1():', 'class'),
|
(2, 0, 'class C1():', 'class'),
|
||||||
(4, 4, ' def __init__(self, a, b):', 'def')])
|
(4, 4, ' def __init__(self, a, b):', 'def')])
|
||||||
eq(cc.topvisible, 5)
|
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):')
|
' def __init__(self, a, b):')
|
||||||
|
|
||||||
# Scroll down to line 11. Last 'def' is removed.
|
# Scroll down to line 11. Last 'def' is removed.
|
||||||
|
@ -235,7 +235,7 @@ class CodeContextTest(unittest.TestCase):
|
||||||
(8, 8, ' if a > b:', 'if'),
|
(8, 8, ' if a > b:', 'if'),
|
||||||
(10, 8, ' elif a < b:', 'elif')])
|
(10, 8, ' elif a < b:', 'elif')])
|
||||||
eq(cc.topvisible, 12)
|
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'
|
' def compare(self):\n'
|
||||||
' if a > b:\n'
|
' if a > b:\n'
|
||||||
' elif a < b:')
|
' elif a < b:')
|
||||||
|
@ -249,7 +249,7 @@ class CodeContextTest(unittest.TestCase):
|
||||||
(8, 8, ' if a > b:', 'if'),
|
(8, 8, ' if a > b:', 'if'),
|
||||||
(10, 8, ' elif a < b:', 'elif')])
|
(10, 8, ' elif a < b:', 'elif')])
|
||||||
eq(cc.topvisible, 12)
|
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'
|
' def compare(self):\n'
|
||||||
' if a > b:\n'
|
' if a > b:\n'
|
||||||
' elif a < b:')
|
' elif a < b:')
|
||||||
|
@ -262,12 +262,12 @@ class CodeContextTest(unittest.TestCase):
|
||||||
(4, 4, ' def __init__(self, a, b):', 'def')])
|
(4, 4, ' def __init__(self, a, b):', 'def')])
|
||||||
eq(cc.topvisible, 6)
|
eq(cc.topvisible, 6)
|
||||||
# context_depth is 1.
|
# 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')
|
@mock.patch.object(codecontext.CodeContext, 'update_code_context')
|
||||||
def test_timer_event(self, mock_update):
|
def test_timer_event(self, mock_update):
|
||||||
# Ensure code context is not active.
|
# Ensure code context is not active.
|
||||||
if self.cc.label:
|
if self.cc.context:
|
||||||
self.cc.toggle_code_context_event()
|
self.cc.toggle_code_context_event()
|
||||||
self.cc.timer_event()
|
self.cc.timer_event()
|
||||||
mock_update.assert_not_called()
|
mock_update.assert_not_called()
|
||||||
|
@ -286,7 +286,7 @@ class CodeContextTest(unittest.TestCase):
|
||||||
test_colors = {'background': '#222222', 'foreground': '#ffff00'}
|
test_colors = {'background': '#222222', 'foreground': '#ffff00'}
|
||||||
|
|
||||||
# Ensure code context is not active.
|
# Ensure code context is not active.
|
||||||
if cc.label:
|
if cc.context:
|
||||||
cc.toggle_code_context_event()
|
cc.toggle_code_context_event()
|
||||||
|
|
||||||
# Nothing updates on inactive code context.
|
# Nothing updates on inactive code context.
|
||||||
|
@ -303,18 +303,18 @@ class CodeContextTest(unittest.TestCase):
|
||||||
cc.config_timer_event()
|
cc.config_timer_event()
|
||||||
eq(cc.textfont, save_font)
|
eq(cc.textfont, save_font)
|
||||||
eq(cc.contextcolors, save_colors)
|
eq(cc.contextcolors, save_colors)
|
||||||
eq(cc.label['font'], save_font)
|
eq(cc.context['font'], save_font)
|
||||||
eq(cc.label['background'], save_colors['background'])
|
eq(cc.context['background'], save_colors['background'])
|
||||||
eq(cc.label['foreground'], save_colors['foreground'])
|
eq(cc.context['foreground'], save_colors['foreground'])
|
||||||
|
|
||||||
# Active code context, change font.
|
# Active code context, change font.
|
||||||
cc.text['font'] = test_font
|
cc.text['font'] = test_font
|
||||||
cc.config_timer_event()
|
cc.config_timer_event()
|
||||||
eq(cc.textfont, test_font)
|
eq(cc.textfont, test_font)
|
||||||
eq(cc.contextcolors, save_colors)
|
eq(cc.contextcolors, save_colors)
|
||||||
eq(cc.label['font'], test_font)
|
eq(cc.context['font'], test_font)
|
||||||
eq(cc.label['background'], save_colors['background'])
|
eq(cc.context['background'], save_colors['background'])
|
||||||
eq(cc.label['foreground'], save_colors['foreground'])
|
eq(cc.context['foreground'], save_colors['foreground'])
|
||||||
|
|
||||||
# Active code context, change color.
|
# Active code context, change color.
|
||||||
cc.text['font'] = save_font
|
cc.text['font'] = save_font
|
||||||
|
@ -322,9 +322,9 @@ class CodeContextTest(unittest.TestCase):
|
||||||
cc.config_timer_event()
|
cc.config_timer_event()
|
||||||
eq(cc.textfont, save_font)
|
eq(cc.textfont, save_font)
|
||||||
eq(cc.contextcolors, test_colors)
|
eq(cc.contextcolors, test_colors)
|
||||||
eq(cc.label['font'], save_font)
|
eq(cc.context['font'], save_font)
|
||||||
eq(cc.label['background'], test_colors['background'])
|
eq(cc.context['background'], test_colors['background'])
|
||||||
eq(cc.label['foreground'], test_colors['foreground'])
|
eq(cc.context['foreground'], test_colors['foreground'])
|
||||||
codecontext.CodeContext.colors = save_colors
|
codecontext.CodeContext.colors = save_colors
|
||||||
cc.config_timer_event()
|
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