diff --git a/Lib/idlelib/CodeContext.py b/Lib/idlelib/CodeContext.py index e9859694b66..5d670667477 100644 --- a/Lib/idlelib/CodeContext.py +++ b/Lib/idlelib/CodeContext.py @@ -1,13 +1,12 @@ -"""CodeContext - Display the block context of code at top of edit window +"""CodeContext - Extension to display the block context above the edit window -Once code has scrolled off the top of the screen, it can be difficult -to determine which block you are in. This extension implements a pane -at the top of each IDLE edit window which provides block structure -hints. These hints are the lines which contain the block opening -keywords, e.g. 'if', for the enclosing block. The number of hint lines -is determined by the numlines variable in the CodeContext section of -config-extensions.def. Lines which do not open blocks are not shown in -the context hints pane. +Once code has scrolled off the top of a window, it can be difficult to +determine which block you are in. This extension implements a pane at the top +of each IDLE edit window which provides block structure hints. These hints are +the lines which contain the block opening keywords, e.g. 'if', for the +enclosing block. The number of hint lines is determined by the numlines +variable in the CodeContext section of config-extensions.def. Lines which do +not open blocks are not shown in the context hints pane. """ import Tkinter @@ -21,13 +20,14 @@ BLOCKOPENERS = Set(["class", "def", "elif", "else", "except", "finally", "for", UPDATEINTERVAL = 100 # millisec FONTUPDATEINTERVAL = 1000 # millisec -getspacesfirstword = lambda s, c=re.compile(r"^(\s*)(\w*)"): c.match(s).groups() +getspacesfirstword =\ + lambda s, c=re.compile(r"^(\s*)(\w*)"): c.match(s).groups() class CodeContext: menudefs = [('options', [('!Code Conte_xt', '<>')])] - numlines = idleConf.GetOption("extensions", "CodeContext", - "numlines", type="int", default=3) + context_depth = idleConf.GetOption("extensions", "CodeContext", + "numlines", type="int", default=3) bgcolor = idleConf.GetOption("extensions", "CodeContext", "bgcolor", type="str", default="LightGray") fgcolor = idleConf.GetOption("extensions", "CodeContext", @@ -37,13 +37,13 @@ class CodeContext: self.text = editwin.text self.textfont = self.text["font"] self.label = None - # self.info holds information about the context lines of line number - # self.lastfirstline. The information is a tuple of the line's - # indentation, the line's text and the keyword at the beginning of the - # line, as returned by get_line_info. At the beginning of the list - # there's a dummy line, which starts the "block" of the whole document. + # self.info is a list of (line number, indent level, line text, block + # keyword) tuples providing the block structure associated with + # self.topvisible (the linenumber of the line displayed at the top of + # the edit window). self.info[0] is initialized as a 'dummy' line which + # starts the toplevel 'block' of the module. self.info = [(0, -1, "", False)] - self.lastfirstline = 1 + self.topvisible = 1 visible = idleConf.GetOption("extensions", "CodeContext", "visible", type="bool", default=False) if visible: @@ -56,7 +56,7 @@ class CodeContext: def toggle_code_context_event(self, event=None): if not self.label: self.label = Tkinter.Label(self.editwin.top, - text="\n" * (self.numlines - 1), + text="\n" * (self.context_depth - 1), anchor="w", justify="left", font=self.textfont, bg=self.bgcolor, fg=self.fgcolor, @@ -77,6 +77,7 @@ class CodeContext: If the line does not start a block, the keyword value is False. The indentation of empty lines (or comment lines) is INFINITY. + """ text = self.text.get("%d.0" % linenum, "%d.end" % linenum) spaces, firstword = getspacesfirstword(text) @@ -87,64 +88,69 @@ class CodeContext: indent = len(spaces) return indent, text, opener - def interesting_lines(self, firstline, stopline=1, stopindent=0): - """ - Find the context lines, starting at firstline. - Will not return lines whose index is smaller than stopline or whose - indentation is smaller than stopindent. - stopline should always be >= 1, so the dummy block start will never - be returned (This function doesn't know what to do about it.) - Returns a list with the context lines, starting from the first (top), - and a number which all context lines above the inspected region should - have a smaller indentation than it. + def get_context(self, new_topvisible, stopline=1, stopindent=0): + """Get context lines, starting at new_topvisible and working backwards. + + Stop when stopline or stopindent is reached. Return a tuple of context + data and the indent level at the top of the region inspected. + """ + assert stopline > 0 lines = [] # The indentation level we are currently in: lastindent = INFINITY # For a line to be interesting, it must begin with a block opening # keyword, and have less indentation than lastindent. - for line_index in xrange(firstline, stopline-1, -1): - indent, text, opener = self.get_line_info(line_index) + for linenum in xrange(new_topvisible, stopline-1, -1): + indent, text, opener = self.get_line_info(linenum) if indent < lastindent: lastindent = indent if opener in ("else", "elif"): # We also show the if statement lastindent += 1 - if opener and line_index < firstline and indent >= stopindent: - lines.append((line_index, indent, text, opener)) + if opener and linenum < new_topvisible and indent >= stopindent: + lines.append((linenum, indent, text, opener)) if lastindent <= stopindent: break lines.reverse() return lines, lastindent - def update_label(self): - """Update the CodeContext label, if needed. + def update_code_context(self): + """Update context information and lines visible in the context pane. + """ - firstline = int(self.text.index("@0,0").split('.')[0]) - if self.lastfirstline == firstline: + new_topvisible = int(self.text.index("@0,0").split('.')[0]) + if self.topvisible == new_topvisible: # haven't scrolled return - if self.lastfirstline < firstline: - lines, lastindent = self.interesting_lines(firstline, - self.lastfirstline) + if self.topvisible < new_topvisible: # scroll down + lines, lastindent = self.get_context(new_topvisible, + self.topvisible) + # retain only context info applicable to the region + # between topvisible and new_topvisible: while self.info[-1][1] >= lastindent: del self.info[-1] - self.info.extend(lines) - else: + elif self.topvisible > new_topvisible: # scroll up stopindent = self.info[-1][1] + 1 - while self.info[-1][0] >= firstline: + # retain only context info associated + # with lines above new_topvisible: + while self.info[-1][0] >= new_topvisible: stopindent = self.info[-1][1] del self.info[-1] - lines, lastindent = self.interesting_lines( - firstline, self.info[-1][0]+1, stopindent) - self.info.extend(lines) - self.lastfirstline = firstline - lines = [""] * max(0, self.numlines - len(self.info)) + \ - [x[2] for x in self.info[-self.numlines:]] - self.label["text"] = '\n'.join(lines) + lines, lastindent = self.get_context(new_topvisible, + self.info[-1][0]+1, + stopindent) + self.info.extend(lines) + self.topvisible = new_topvisible + + # empty lines in context pane: + context_strings = [""] * max(0, self.context_depth - len(self.info)) + # followed by the context hint lines: + context_strings += [x[2] for x in self.info[-self.context_depth:]] + self.label["text"] = '\n'.join(context_strings) def timer_event(self): if self.label: - self.update_label() + self.update_code_context() self.text.after(UPDATEINTERVAL, self.timer_event) def font_timer_event(self):