From 5e50c2942aa8f6985ac4cbb81a294814247c8179 Mon Sep 17 00:00:00 2001 From: Tal Einat Date: Tue, 9 Jul 2019 17:05:05 +0300 Subject: [PATCH 1/7] simplify, optimize and clean up code context * Only create CodeContext instances for "real" editors windows, but not e.g. shell or output windows. * Remove configuration update Tk event fired every second, by having the editor window ask its code context widget to update when necessary, i.e. upon font or highlighting updates. * When code context isn't being shown, avoid having a Tk event fired every 100ms to check whether the code context needs to be updated. * Use the editor window's getlineno() method where applicable. * Fix a bare "except:" --- Lib/idlelib/codecontext.py | 72 +++++++++++++++++--------------------- Lib/idlelib/editor.py | 12 +++++-- Lib/idlelib/outwin.py | 2 ++ 3 files changed, 45 insertions(+), 41 deletions(-) diff --git a/Lib/idlelib/codecontext.py b/Lib/idlelib/codecontext.py index 2aed76de7fb6f8..074d2241baa33f 100644 --- a/Lib/idlelib/codecontext.py +++ b/Lib/idlelib/codecontext.py @@ -19,8 +19,6 @@ BLOCKOPENERS = {"class", "def", "elif", "else", "except", "finally", "for", "if", "try", "while", "with", "async"} -UPDATEINTERVAL = 100 # millisec -CONFIGUPDATEINTERVAL = 1000 # millisec def get_spaces_firstword(codeline, c=re.compile(r"^(\s*)(\w*)")): @@ -44,13 +42,13 @@ def get_line_info(codeline): class CodeContext: "Display block context above the edit window." + UPDATEINTERVAL = 100 # millisec def __init__(self, editwin): """Initialize settings for context block. editwin is the Editor window for the context block. self.text is the editor window text widget. - self.textfont is the editor window font. self.context displays the code context text above the editor text. Initially None, it is toggled via <>. @@ -65,29 +63,25 @@ def __init__(self, editwin): """ self.editwin = editwin self.text = editwin.text - self.textfont = self.text["font"] - self.contextcolors = CodeContext.colors self.context = None self.topvisible = 1 self.info = [(0, -1, "", False)] - # Start two update cycles, one for context lines, one for font changes. - self.t1 = self.text.after(UPDATEINTERVAL, self.timer_event) - self.t2 = self.text.after(CONFIGUPDATEINTERVAL, self.config_timer_event) + self.t1 = None @classmethod def reload(cls): "Load class variables from config." cls.context_depth = idleConf.GetOption("extensions", "CodeContext", - "maxlines", type="int", default=15) - cls.colors = idleConf.GetHighlight(idleConf.CurrentTheme(), 'context') + "maxlines", type="int", + default=15) def __del__(self): "Cancel scheduled events." - try: - self.text.after_cancel(self.t1) - self.text.after_cancel(self.t2) - except: - pass + if self.t1 is not None: + try: + self.text.after_cancel(self.t1) + except tkinter.TclError: + pass def toggle_code_context_event(self, event=None): """Toggle code context display. @@ -96,7 +90,7 @@ def toggle_code_context_event(self, event=None): window text (toggle on). If it does exist, destroy it (toggle off). Return 'break' to complete the processing of the binding. """ - if not self.context: + if self.context is None: # Calculate the border width and horizontal padding required to # align the context with the text in the main Text widget. # @@ -111,21 +105,23 @@ def toggle_code_context_event(self, event=None): padx += widget.tk.getint(widget.cget('padx')) border += widget.tk.getint(widget.cget('border')) 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, state='disabled') + self.editwin.top, font=self.text['font'], + height=1, + width=1, # Don't request more than we get. + padx=padx, border=border, relief=SUNKEN, state='disabled') + self.update_highlight_colors() self.context.bind('', self.jumptoline) # Pack the context widget before and above the text_frame widget, # thus ensuring that it will appear directly above text_frame. self.context.pack(side=TOP, fill=X, expand=False, - before=self.editwin.text_frame) + before=self.editwin.text_frame) menu_status = 'Hide' + self.t1 = self.text.after(self.UPDATEINTERVAL, self.timer_event) else: self.context.destroy() self.context = None + self.text.after_cancel(self.t1) + self.t1 = None menu_status = 'Show' self.editwin.update_menu_label(menu='options', index='* Code Context', label=f'{menu_status} Code Context') @@ -169,7 +165,7 @@ def update_code_context(self): 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]) + new_topvisible = self.editwin.getlineno("@0,0") if self.topvisible == new_topvisible: # Haven't scrolled. return if self.topvisible < new_topvisible: # Scroll down. @@ -208,7 +204,7 @@ def jumptoline(self, event=None): newtop = 1 else: # Line number clicked. - contextline = int(float(self.context.index('insert'))) + contextline = self.editwin.getlineno('insert') # Lines not displayed due to maxlines. offset = max(1, lines - self.context_depth) - 1 newtop = self.info[offset + contextline][0] @@ -217,21 +213,19 @@ def jumptoline(self, event=None): def timer_event(self): "Event on editor text widget triggered every UPDATEINTERVAL ms." - if self.context: + if self.context is not None: 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.context and (newtextfont != self.textfont or - CodeContext.colors != self.contextcolors)): - self.textfont = newtextfont - self.contextcolors = CodeContext.colors - 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) + self.t1 = self.text.after(self.UPDATEINTERVAL, self.timer_event) + + def update_font(self, font): + if self.context is not None: + self.context['font'] = font + + def update_highlight_colors(self): + if self.context is not None: + colors = idleConf.GetHighlight(idleConf.CurrentTheme(), 'context') + self.context['background'] = colors['background'] + self.context['foreground'] = colors['foreground'] CodeContext.reload() diff --git a/Lib/idlelib/editor.py b/Lib/idlelib/editor.py index 606de71a6add88..0ffcc428f5d764 100644 --- a/Lib/idlelib/editor.py +++ b/Lib/idlelib/editor.py @@ -62,6 +62,8 @@ class EditorWindow(object): filesystemencoding = sys.getfilesystemencoding() # for file names help_url = None + allow_codecontext = True + def __init__(self, flist=None, filename=None, key=None, root=None): # Delay import: runscript imports pyshell imports EditorWindow. from idlelib.runscript import ScriptBinding @@ -247,6 +249,7 @@ def __init__(self, flist=None, filename=None, key=None, root=None): self.good_load = False self.set_indentation_params(False) self.color = None # initialized below in self.ResetColorizer + self.codecontext = None if filename: if os.path.exists(filename) and not os.path.isdir(filename): if io.loadfile(filename): @@ -312,8 +315,10 @@ def __init__(self, flist=None, filename=None, key=None, root=None): text.bind("<>", ctip.refresh_calltip_event) text.bind("<>", ctip.force_open_calltip_event) text.bind("<>", self.ZoomHeight(self).zoom_height_event) - text.bind("<>", - self.CodeContext(self).toggle_code_context_event) + if self.allow_codecontext: + self.codecontext = self.CodeContext(self) + text.bind("<>", + self.codecontext.toggle_code_context_event) def _filename_to_unicode(self, filename): """Return filename as BMP unicode so displayable in Tk.""" @@ -773,6 +778,9 @@ def ResetColorizer(self): self._addcolorizer() EditorWindow.color_config(self.text) + if self.codecontext is not None: + self.codecontext.update_highlight_colors() + IDENTCHARS = string.ascii_letters + string.digits + "_" def colorize_syntax_error(self, text, pos): diff --git a/Lib/idlelib/outwin.py b/Lib/idlelib/outwin.py index ecc53ef0195dc6..38c59bdf9b4e66 100644 --- a/Lib/idlelib/outwin.py +++ b/Lib/idlelib/outwin.py @@ -74,6 +74,8 @@ class OutputWindow(EditorWindow): ("Go to file/line", "<>", None), ] + allow_codecontext = False + def __init__(self, *args): EditorWindow.__init__(self, *args) self.text.bind("<>", self.goto_file_line) From 7f40200167f011c9d885d12c8d76bbb2642e7e4a Mon Sep 17 00:00:00 2001 From: Tal Einat Date: Tue, 9 Jul 2019 18:30:40 +0300 Subject: [PATCH 2/7] fix a couple of bugs and update tests --- Lib/idlelib/codecontext.py | 12 +- Lib/idlelib/editor.py | 2 + Lib/idlelib/idle_test/test_codecontext.py | 144 +++++++++++++--------- 3 files changed, 93 insertions(+), 65 deletions(-) diff --git a/Lib/idlelib/codecontext.py b/Lib/idlelib/codecontext.py index 074d2241baa33f..82a03bf6bca1e8 100644 --- a/Lib/idlelib/codecontext.py +++ b/Lib/idlelib/codecontext.py @@ -78,10 +78,8 @@ def reload(cls): def __del__(self): "Cancel scheduled events." if self.t1 is not None: - try: - self.text.after_cancel(self.t1) - except tkinter.TclError: - pass + self.text.after_cancel(self.t1) + self.t1 = None def toggle_code_context_event(self, event=None): """Toggle code context display. @@ -204,7 +202,7 @@ def jumptoline(self, event=None): newtop = 1 else: # Line number clicked. - contextline = self.editwin.getlineno('insert') + contextline = int(float(self.context.index('insert'))) # Lines not displayed due to maxlines. offset = max(1, lines - self.context_depth) - 1 newtop = self.info[offset + contextline][0] @@ -217,9 +215,9 @@ def timer_event(self): self.update_code_context() self.t1 = self.text.after(self.UPDATEINTERVAL, self.timer_event) - def update_font(self, font): + def update_font(self): if self.context is not None: - self.context['font'] = font + self.context['font'] = self.text['font'] def update_highlight_colors(self): if self.context is not None: diff --git a/Lib/idlelib/editor.py b/Lib/idlelib/editor.py index 0ffcc428f5d764..b681686df22156 100644 --- a/Lib/idlelib/editor.py +++ b/Lib/idlelib/editor.py @@ -799,6 +799,8 @@ def ResetFont(self): # Called from configdialog.py self.text['font'] = idleConf.GetFont(self.root, 'main','EditorWindow') + if self.codecontext is not None: + self.codecontext.update_font() def RemoveKeybindings(self): "Remove the keybindings before they are changed." diff --git a/Lib/idlelib/idle_test/test_codecontext.py b/Lib/idlelib/idle_test/test_codecontext.py index 6c6893580f42f6..9d315e8348a2f5 100644 --- a/Lib/idlelib/idle_test/test_codecontext.py +++ b/Lib/idlelib/idle_test/test_codecontext.py @@ -2,6 +2,7 @@ from idlelib import codecontext import unittest +import unittest.mock from test.support import requires from tkinter import Tk, Frame, Text, TclError @@ -42,6 +43,9 @@ def __init__(self, root, frame, text): self.text = text self.label = '' + def getlineno(self, index): + return int(float(self.text.index(index))) + def update_menu_label(self, **kwargs): self.label = kwargs['label'] @@ -75,6 +79,18 @@ def setUp(self): self.text.yview(0) self.cc = codecontext.CodeContext(self.editor) + self.highlight_cfg = {"background": '#abcdef', + "foreground": '#123456'} + orig_idleConf_GetHighlight = codecontext.idleConf.GetHighlight + def mock_idleconf_GetHighlight(theme, element): + if element == 'context': + return self.highlight_cfg + return orig_idleConf_GetHighlight(theme, element) + patcher = unittest.mock.patch.object( + codecontext.idleConf, 'GetHighlight', mock_idleconf_GetHighlight) + patcher.start() + self.addCleanup(patcher.stop) + def tearDown(self): if self.cc.context: self.cc.context.destroy() @@ -89,30 +105,24 @@ def test_init(self): eq(cc.editwin, ed) eq(cc.text, ed.text) - eq(cc.textfont, ed.text['font']) + eq(cc.text['font'], ed.text['font']) 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') - eq(self.root.tk.call('after', 'info', self.cc.t2)[1], 'timer') + self.assertIsNone(self.cc.t1) def test_del(self): self.cc.__del__() - with self.assertRaises(TclError) as msg: - self.root.tk.call('after', 'info', self.cc.t1) - self.assertIn("doesn't exist", msg) - with self.assertRaises(TclError) as msg: - self.root.tk.call('after', 'info', self.cc.t2) - self.assertIn("doesn't exist", msg) - # For coverage on the except. Have to delete because the - # above Tcl error is caught by after_cancel. - del self.cc.t1, self.cc.t2 + + def test_del_with_timer(self): + timer = self.cc.t1 = self.text.after(10000, lambda: None) self.cc.__del__() + with self.assertRaises(TclError) as cm: + self.root.tk.call('after', 'info', timer) + self.assertIn("doesn't exist", str(cm.exception)) def test_reload(self): codecontext.CodeContext.reload() - self.assertEqual(self.cc.colors, {'background': 'lightgray', - 'foreground': '#000000'}) self.assertEqual(self.cc.context_depth, 15) def test_toggle_code_context_event(self): @@ -127,16 +137,18 @@ def test_toggle_code_context_event(self): # Toggle on. eq(toggle(), 'break') 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['font'], self.text['font']) + eq(cc.context['fg'], self.highlight_cfg['foreground']) + eq(cc.context['bg'], self.highlight_cfg['background']) eq(cc.context.get('1.0', 'end-1c'), '') eq(cc.editwin.label, 'Hide Code Context') + eq(self.root.tk.call('after', 'info', self.cc.t1)[1], 'timer') # Toggle off. eq(toggle(), 'break') self.assertIsNone(cc.context) eq(cc.editwin.label, 'Show Code Context') + self.assertIsNone(self.cc.t1) def test_get_context(self): eq = self.assertEqual @@ -227,7 +239,7 @@ def test_update_code_context(self): (4, 4, ' def __init__(self, a, b):', 'def')]) eq(cc.topvisible, 5) 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. cc.text.yview(11) @@ -239,9 +251,9 @@ def test_update_code_context(self): (10, 8, ' elif a < b:', 'elif')]) eq(cc.topvisible, 12) eq(cc.context.get('1.0', 'end-1c'), 'class C1():\n' - ' def compare(self):\n' - ' if a > b:\n' - ' elif a < b:') + ' def compare(self):\n' + ' if a > b:\n' + ' elif a < b:') # No scroll. No update, even though context_depth changed. cc.update_code_context() @@ -253,9 +265,9 @@ def test_update_code_context(self): (10, 8, ' elif a < b:', 'elif')]) eq(cc.topvisible, 12) eq(cc.context.get('1.0', 'end-1c'), 'class C1():\n' - ' def compare(self):\n' - ' if a > b:\n' - ' elif a < b:') + ' def compare(self):\n' + ' if a > b:\n' + ' elif a < b:') # Scroll up. cc.text.yview(5) @@ -276,7 +288,7 @@ def test_jumptoline(self): cc.toggle_code_context_event() # Empty context. - cc.text.yview(f'{2}.0') + cc.text.yview('2.0') cc.update_code_context() eq(cc.topvisible, 2) cc.context.mark_set('insert', '1.5') @@ -284,7 +296,7 @@ def test_jumptoline(self): eq(cc.topvisible, 1) # 4 lines of context showing. - cc.text.yview(f'{12}.0') + cc.text.yview('12.0') cc.update_code_context() eq(cc.topvisible, 12) cc.context.mark_set('insert', '3.0') @@ -293,7 +305,7 @@ def test_jumptoline(self): # More context lines than limit. cc.context_depth = 2 - cc.text.yview(f'{12}.0') + cc.text.yview('12.0') cc.update_code_context() eq(cc.topvisible, 12) cc.context.mark_set('insert', '1.0') @@ -313,56 +325,72 @@ def test_timer_event(self, mock_update): self.cc.timer_event() mock_update.assert_called() - def test_config_timer_event(self): + def test_font(self): eq = self.assertEqual cc = self.cc save_font = cc.text['font'] - save_colors = codecontext.CodeContext.colors - test_font = 'FakeFont' - test_colors = {'background': '#222222', 'foreground': '#ffff00'} + test_font = 'TkFixedFont' # Ensure code context is not active. if cc.context: cc.toggle_code_context_event() - # Nothing updates on inactive code context. - cc.text['font'] = test_font - codecontext.CodeContext.colors = test_colors - cc.config_timer_event() - eq(cc.textfont, save_font) - eq(cc.contextcolors, save_colors) + # Nothing breaks with inactive code context. + cc.update_font() - # Activate code context, but no change to font or color. + # Activate code context, but no change to font. cc.toggle_code_context_event() - cc.text['font'] = save_font - codecontext.CodeContext.colors = save_colors - cc.config_timer_event() - eq(cc.textfont, save_font) - eq(cc.contextcolors, save_colors) eq(cc.context['font'], save_font) - eq(cc.context['background'], save_colors['background']) - eq(cc.context['foreground'], save_colors['foreground']) + # Call font update, but no change to font. + cc.update_font() + eq(cc.context['font'], save_font) + cc.toggle_code_context_event() - # Active code context, change font. + # Change font and activate code context. cc.text['font'] = test_font - cc.config_timer_event() - eq(cc.textfont, test_font) - eq(cc.contextcolors, save_colors) + cc.toggle_code_context_event() eq(cc.context['font'], test_font) + + # Change font and call the font update. + cc.update_font() + eq(cc.context['font'], save_font) + cc.text['font'] = save_font + + def test_highlight_colors(self): + eq = self.assertEqual + cc = self.cc + save_colors = dict(self.highlight_cfg) + test_colors = {'background': '#222222', 'foreground': '#ffff00'} + + # Ensure code context is not active. + if cc.context: + cc.toggle_code_context_event() + + # Nothing breaks with inactive code context. + cc.update_highlight_colors() + + # Activate code context, but no change to colors. + cc.toggle_code_context_event() 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.context['font'], save_font) + # Call colors update, but no change to font. + cc.update_highlight_colors() + eq(cc.context['background'], save_colors['background']) + eq(cc.context['foreground'], save_colors['foreground']) + cc.toggle_code_context_event() + + # Change colors and activate code context. + self.highlight_cfg = test_colors + cc.toggle_code_context_event() eq(cc.context['background'], test_colors['background']) eq(cc.context['foreground'], test_colors['foreground']) - codecontext.CodeContext.colors = save_colors - cc.config_timer_event() + + # Change colors and call highlight colors update. + self.highlight_cfg = save_colors + cc.update_highlight_colors() + eq(cc.context['background'], save_colors['background']) + eq(cc.context['foreground'], save_colors['foreground']) class HelperFunctionText(unittest.TestCase): From 6d81dc65624c5b758324af3ad28b274f1e1afffb Mon Sep 17 00:00:00 2001 From: Tal Einat Date: Thu, 11 Jul 2019 00:05:54 +0300 Subject: [PATCH 3/7] add a NEWS entry --- Misc/NEWS.d/next/IDLE/2019-07-11-00-05-31.bpo-37530.AuyCyD.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/IDLE/2019-07-11-00-05-31.bpo-37530.AuyCyD.rst diff --git a/Misc/NEWS.d/next/IDLE/2019-07-11-00-05-31.bpo-37530.AuyCyD.rst b/Misc/NEWS.d/next/IDLE/2019-07-11-00-05-31.bpo-37530.AuyCyD.rst new file mode 100644 index 00000000000000..56dd7ac47e8cf3 --- /dev/null +++ b/Misc/NEWS.d/next/IDLE/2019-07-11-00-05-31.bpo-37530.AuyCyD.rst @@ -0,0 +1 @@ +Optimize code context to reduce unneeded background activity. From bb35baf51d023a1cfcb7016a24c0e663f7a82dc7 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Tue, 16 Jul 2019 01:16:35 -0400 Subject: [PATCH 4/7] Update 2019-07-11-00-05-31.bpo-37530.AuyCyD.rst --- Misc/NEWS.d/next/IDLE/2019-07-11-00-05-31.bpo-37530.AuyCyD.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Misc/NEWS.d/next/IDLE/2019-07-11-00-05-31.bpo-37530.AuyCyD.rst b/Misc/NEWS.d/next/IDLE/2019-07-11-00-05-31.bpo-37530.AuyCyD.rst index 56dd7ac47e8cf3..0b80860b8fc22f 100644 --- a/Misc/NEWS.d/next/IDLE/2019-07-11-00-05-31.bpo-37530.AuyCyD.rst +++ b/Misc/NEWS.d/next/IDLE/2019-07-11-00-05-31.bpo-37530.AuyCyD.rst @@ -1 +1,3 @@ Optimize code context to reduce unneeded background activity. +Font and highlight changes now occur along with text changes +instead of after a random delay. From 1ce39ebe30de09cd62412144b6055a2875df646e Mon Sep 17 00:00:00 2001 From: Tal Einat Date: Tue, 16 Jul 2019 09:22:15 +0300 Subject: [PATCH 5/7] update font of code context before the main text widget --- Lib/idlelib/codecontext.py | 4 ++-- Lib/idlelib/editor.py | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Lib/idlelib/codecontext.py b/Lib/idlelib/codecontext.py index 82a03bf6bca1e8..d6980a0e5780da 100644 --- a/Lib/idlelib/codecontext.py +++ b/Lib/idlelib/codecontext.py @@ -215,9 +215,9 @@ def timer_event(self): self.update_code_context() self.t1 = self.text.after(self.UPDATEINTERVAL, self.timer_event) - def update_font(self): + def update_font(self, font): if self.context is not None: - self.context['font'] = self.text['font'] + self.context['font'] = font def update_highlight_colors(self): if self.context is not None: diff --git a/Lib/idlelib/editor.py b/Lib/idlelib/editor.py index b681686df22156..1803af23b2962b 100644 --- a/Lib/idlelib/editor.py +++ b/Lib/idlelib/editor.py @@ -798,9 +798,12 @@ def ResetFont(self): "Update the text widgets' font if it is changed" # Called from configdialog.py - self.text['font'] = idleConf.GetFont(self.root, 'main','EditorWindow') + new_font = idleConf.GetFont(self.root, 'main', 'EditorWindow') + # Update the code context widget first, since its height affects + # the height of the text widget. This avoids double re-rendering. if self.codecontext is not None: - self.codecontext.update_font() + self.codecontext.update_font(new_font) + self.text['font'] = new_font def RemoveKeybindings(self): "Remove the keybindings before they are changed." From e852138345bb1df37376f006bb4d16dbdb509c40 Mon Sep 17 00:00:00 2001 From: Tal Einat Date: Wed, 17 Jul 2019 09:18:24 +0300 Subject: [PATCH 6/7] fix test_font --- Lib/idlelib/idle_test/test_codecontext.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Lib/idlelib/idle_test/test_codecontext.py b/Lib/idlelib/idle_test/test_codecontext.py index 9d315e8348a2f5..05d3209db56888 100644 --- a/Lib/idlelib/idle_test/test_codecontext.py +++ b/Lib/idlelib/idle_test/test_codecontext.py @@ -332,27 +332,27 @@ def test_font(self): test_font = 'TkFixedFont' # Ensure code context is not active. - if cc.context: + if cc.context is not None: cc.toggle_code_context_event() - # Nothing breaks with inactive code context. - cc.update_font() + # Nothing breaks or changes with inactive code context. + cc.update_font(test_font) # Activate code context, but no change to font. cc.toggle_code_context_event() eq(cc.context['font'], save_font) - # Call font update, but no change to font. - cc.update_font() + # Call font update with the existing font. + cc.update_font(save_font) eq(cc.context['font'], save_font) cc.toggle_code_context_event() - # Change font and activate code context. + # Change text widget font and activate code context. cc.text['font'] = test_font - cc.toggle_code_context_event() + cc.toggle_code_context_event(test_font) eq(cc.context['font'], test_font) - # Change font and call the font update. - cc.update_font() + # Just call the font update. + cc.update_font(save_font) eq(cc.context['font'], save_font) cc.text['font'] = save_font From b3dfed293428044b28b84a7b5d9739c4b4c4fff7 Mon Sep 17 00:00:00 2001 From: Tal Einat Date: Wed, 17 Jul 2019 10:48:12 +0300 Subject: [PATCH 7/7] avoid potential exceptions during app shutdown in __del__ --- Lib/idlelib/codecontext.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Lib/idlelib/codecontext.py b/Lib/idlelib/codecontext.py index d6980a0e5780da..9bd0fa1753fc6b 100644 --- a/Lib/idlelib/codecontext.py +++ b/Lib/idlelib/codecontext.py @@ -78,7 +78,10 @@ def reload(cls): def __del__(self): "Cancel scheduled events." if self.t1 is not None: - self.text.after_cancel(self.t1) + try: + self.text.after_cancel(self.t1) + except tkinter.TclError: + pass self.t1 = None def toggle_code_context_event(self, event=None):