Skip to content

Commit

Permalink
bpo-37530: simplify, optimize and clean up IDLE code context (GH-14675)
Browse files Browse the repository at this point in the history
* 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
(cherry picked from commit 7036e1d)

Co-authored-by: Tal Einat <[email protected]>
  • Loading branch information
miss-islington and taleinat authored Jul 17, 2019
1 parent ba3c89f commit bb79ab8
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 99 deletions.
71 changes: 33 additions & 38 deletions Lib/idlelib/codecontext.py
Original file line number Diff line number Diff line change
Expand Up @@ -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*)")):
Expand All @@ -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 <<toggle-code-context>>.
Expand All @@ -65,29 +63,26 @@ 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
self.t1 = None

def toggle_code_context_event(self, event=None):
"""Toggle code context display.
Expand All @@ -96,7 +91,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.
#
Expand All @@ -111,21 +106,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('<ButtonRelease-1>', 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')
Expand Down Expand Up @@ -169,7 +166,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.
Expand Down Expand Up @@ -217,21 +214,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()
Expand Down
19 changes: 16 additions & 3 deletions Lib/idlelib/editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -312,8 +315,10 @@ def __init__(self, flist=None, filename=None, key=None, root=None):
text.bind("<<refresh-calltip>>", ctip.refresh_calltip_event)
text.bind("<<force-open-calltip>>", ctip.force_open_calltip_event)
text.bind("<<zoom-height>>", self.ZoomHeight(self).zoom_height_event)
text.bind("<<toggle-code-context>>",
self.CodeContext(self).toggle_code_context_event)
if self.allow_codecontext:
self.codecontext = self.CodeContext(self)
text.bind("<<toggle-code-context>>",
self.codecontext.toggle_code_context_event)

def _filename_to_unicode(self, filename):
"""Return filename as BMP unicode so displayable in Tk."""
Expand Down Expand Up @@ -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):
Expand All @@ -790,7 +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(new_font)
self.text['font'] = new_font

def RemoveKeybindings(self):
"Remove the keybindings before they are changed."
Expand Down
Loading

0 comments on commit bb79ab8

Please sign in to comment.