Skip to content

Commit 7036e1d

Browse files
authored
bpo-37530: simplify, optimize and clean up IDLE code context (pythonGH-14675)
* 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. * Update font of the code context widget before the main text widget
1 parent bd26a44 commit 7036e1d

File tree

5 files changed

+140
-99
lines changed

5 files changed

+140
-99
lines changed

Lib/idlelib/codecontext.py

+33-38
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@
1919

2020
BLOCKOPENERS = {"class", "def", "elif", "else", "except", "finally", "for",
2121
"if", "try", "while", "with", "async"}
22-
UPDATEINTERVAL = 100 # millisec
23-
CONFIGUPDATEINTERVAL = 1000 # millisec
2422

2523

2624
def get_spaces_firstword(codeline, c=re.compile(r"^(\s*)(\w*)")):
@@ -44,13 +42,13 @@ def get_line_info(codeline):
4442

4543
class CodeContext:
4644
"Display block context above the edit window."
45+
UPDATEINTERVAL = 100 # millisec
4746

4847
def __init__(self, editwin):
4948
"""Initialize settings for context block.
5049
5150
editwin is the Editor window for the context block.
5251
self.text is the editor window text widget.
53-
self.textfont is the editor window font.
5452
5553
self.context displays the code context text above the editor text.
5654
Initially None, it is toggled via <<toggle-code-context>>.
@@ -65,29 +63,26 @@ def __init__(self, editwin):
6563
"""
6664
self.editwin = editwin
6765
self.text = editwin.text
68-
self.textfont = self.text["font"]
69-
self.contextcolors = CodeContext.colors
7066
self.context = None
7167
self.topvisible = 1
7268
self.info = [(0, -1, "", False)]
73-
# Start two update cycles, one for context lines, one for font changes.
74-
self.t1 = self.text.after(UPDATEINTERVAL, self.timer_event)
75-
self.t2 = self.text.after(CONFIGUPDATEINTERVAL, self.config_timer_event)
69+
self.t1 = None
7670

7771
@classmethod
7872
def reload(cls):
7973
"Load class variables from config."
8074
cls.context_depth = idleConf.GetOption("extensions", "CodeContext",
81-
"maxlines", type="int", default=15)
82-
cls.colors = idleConf.GetHighlight(idleConf.CurrentTheme(), 'context')
75+
"maxlines", type="int",
76+
default=15)
8377

8478
def __del__(self):
8579
"Cancel scheduled events."
86-
try:
87-
self.text.after_cancel(self.t1)
88-
self.text.after_cancel(self.t2)
89-
except:
90-
pass
80+
if self.t1 is not None:
81+
try:
82+
self.text.after_cancel(self.t1)
83+
except tkinter.TclError:
84+
pass
85+
self.t1 = None
9186

9287
def toggle_code_context_event(self, event=None):
9388
"""Toggle code context display.
@@ -96,7 +91,7 @@ def toggle_code_context_event(self, event=None):
9691
window text (toggle on). If it does exist, destroy it (toggle off).
9792
Return 'break' to complete the processing of the binding.
9893
"""
99-
if not self.context:
94+
if self.context is None:
10095
# Calculate the border width and horizontal padding required to
10196
# align the context with the text in the main Text widget.
10297
#
@@ -111,21 +106,23 @@ def toggle_code_context_event(self, event=None):
111106
padx += widget.tk.getint(widget.cget('padx'))
112107
border += widget.tk.getint(widget.cget('border'))
113108
self.context = tkinter.Text(
114-
self.editwin.top, font=self.textfont,
115-
bg=self.contextcolors['background'],
116-
fg=self.contextcolors['foreground'],
117-
height=1,
118-
width=1, # Don't request more than we get.
119-
padx=padx, border=border, relief=SUNKEN, state='disabled')
109+
self.editwin.top, font=self.text['font'],
110+
height=1,
111+
width=1, # Don't request more than we get.
112+
padx=padx, border=border, relief=SUNKEN, state='disabled')
113+
self.update_highlight_colors()
120114
self.context.bind('<ButtonRelease-1>', self.jumptoline)
121115
# Pack the context widget before and above the text_frame widget,
122116
# thus ensuring that it will appear directly above text_frame.
123117
self.context.pack(side=TOP, fill=X, expand=False,
124-
before=self.editwin.text_frame)
118+
before=self.editwin.text_frame)
125119
menu_status = 'Hide'
120+
self.t1 = self.text.after(self.UPDATEINTERVAL, self.timer_event)
126121
else:
127122
self.context.destroy()
128123
self.context = None
124+
self.text.after_cancel(self.t1)
125+
self.t1 = None
129126
menu_status = 'Show'
130127
self.editwin.update_menu_label(menu='options', index='* Code Context',
131128
label=f'{menu_status} Code Context')
@@ -169,7 +166,7 @@ def update_code_context(self):
169166
be retrieved and the context area will be updated with the code,
170167
up to the number of maxlines.
171168
"""
172-
new_topvisible = int(self.text.index("@0,0").split('.')[0])
169+
new_topvisible = self.editwin.getlineno("@0,0")
173170
if self.topvisible == new_topvisible: # Haven't scrolled.
174171
return
175172
if self.topvisible < new_topvisible: # Scroll down.
@@ -217,21 +214,19 @@ def jumptoline(self, event=None):
217214

218215
def timer_event(self):
219216
"Event on editor text widget triggered every UPDATEINTERVAL ms."
220-
if self.context:
217+
if self.context is not None:
221218
self.update_code_context()
222-
self.t1 = self.text.after(UPDATEINTERVAL, self.timer_event)
223-
224-
def config_timer_event(self):
225-
"Event on editor text widget triggered every CONFIGUPDATEINTERVAL ms."
226-
newtextfont = self.text["font"]
227-
if (self.context and (newtextfont != self.textfont or
228-
CodeContext.colors != self.contextcolors)):
229-
self.textfont = newtextfont
230-
self.contextcolors = CodeContext.colors
231-
self.context["font"] = self.textfont
232-
self.context['background'] = self.contextcolors['background']
233-
self.context['foreground'] = self.contextcolors['foreground']
234-
self.t2 = self.text.after(CONFIGUPDATEINTERVAL, self.config_timer_event)
219+
self.t1 = self.text.after(self.UPDATEINTERVAL, self.timer_event)
220+
221+
def update_font(self, font):
222+
if self.context is not None:
223+
self.context['font'] = font
224+
225+
def update_highlight_colors(self):
226+
if self.context is not None:
227+
colors = idleConf.GetHighlight(idleConf.CurrentTheme(), 'context')
228+
self.context['background'] = colors['background']
229+
self.context['foreground'] = colors['foreground']
235230

236231

237232
CodeContext.reload()

Lib/idlelib/editor.py

+16-3
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ class EditorWindow(object):
6262
filesystemencoding = sys.getfilesystemencoding() # for file names
6363
help_url = None
6464

65+
allow_codecontext = True
66+
6567
def __init__(self, flist=None, filename=None, key=None, root=None):
6668
# Delay import: runscript imports pyshell imports EditorWindow.
6769
from idlelib.runscript import ScriptBinding
@@ -247,6 +249,7 @@ def __init__(self, flist=None, filename=None, key=None, root=None):
247249
self.good_load = False
248250
self.set_indentation_params(False)
249251
self.color = None # initialized below in self.ResetColorizer
252+
self.codecontext = None
250253
if filename:
251254
if os.path.exists(filename) and not os.path.isdir(filename):
252255
if io.loadfile(filename):
@@ -312,8 +315,10 @@ def __init__(self, flist=None, filename=None, key=None, root=None):
312315
text.bind("<<refresh-calltip>>", ctip.refresh_calltip_event)
313316
text.bind("<<force-open-calltip>>", ctip.force_open_calltip_event)
314317
text.bind("<<zoom-height>>", self.ZoomHeight(self).zoom_height_event)
315-
text.bind("<<toggle-code-context>>",
316-
self.CodeContext(self).toggle_code_context_event)
318+
if self.allow_codecontext:
319+
self.codecontext = self.CodeContext(self)
320+
text.bind("<<toggle-code-context>>",
321+
self.codecontext.toggle_code_context_event)
317322

318323
def _filename_to_unicode(self, filename):
319324
"""Return filename as BMP unicode so displayable in Tk."""
@@ -773,6 +778,9 @@ def ResetColorizer(self):
773778
self._addcolorizer()
774779
EditorWindow.color_config(self.text)
775780

781+
if self.codecontext is not None:
782+
self.codecontext.update_highlight_colors()
783+
776784
IDENTCHARS = string.ascii_letters + string.digits + "_"
777785

778786
def colorize_syntax_error(self, text, pos):
@@ -790,7 +798,12 @@ def ResetFont(self):
790798
"Update the text widgets' font if it is changed"
791799
# Called from configdialog.py
792800

793-
self.text['font'] = idleConf.GetFont(self.root, 'main','EditorWindow')
801+
new_font = idleConf.GetFont(self.root, 'main', 'EditorWindow')
802+
# Update the code context widget first, since its height affects
803+
# the height of the text widget. This avoids double re-rendering.
804+
if self.codecontext is not None:
805+
self.codecontext.update_font(new_font)
806+
self.text['font'] = new_font
794807

795808
def RemoveKeybindings(self):
796809
"Remove the keybindings before they are changed."

0 commit comments

Comments
 (0)