167 lines · 7.5 KB · Python
RawDownload
"""CodeContext - Extension to display the block context above the edit window Once code has scrolled off the top of a window, it can be difficult todetermine which block you are in.  This extension implements a pane at the topof each IDLE edit window which provides block structure hints.  These hints arethe lines which contain the block opening keywords, e.g. 'if', for theenclosing block.  The number of hint lines is determined by the numlinesvariable in the CodeContext section of config-extensions.def. Lines which donot open blocks are not shown in the context hints pane. """import Tkinterfrom configHandler import idleConfimport refrom sys import maxint as INFINITY BLOCKOPENERS = set(["class", "def", "elif", "else", "except", "finally", "for",                    "if", "try", "while", "with"])UPDATEINTERVAL = 100 # millisecFONTUPDATEINTERVAL = 1000 # millisec getspacesfirstword =\                   lambda s, c=re.compile(r"^(\s*)(\w*)"): c.match(s).groups() class CodeContext:    menudefs = [('options', [('!Code Conte_xt', '<<toggle-code-context>>')])]     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",                                 "fgcolor", type="str", default="Black")    def __init__(self, editwin):        self.editwin = editwin        self.text = editwin.text        self.textfont = self.text["font"]        self.label = None        # 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.topvisible = 1        visible = idleConf.GetOption("extensions", "CodeContext",                                     "visible", type="bool", default=False)        if visible:            self.toggle_code_context_event()            self.editwin.setvar('<<toggle-code-context>>', True)        # Start two update cycles, one for context lines, one for font changes.        self.text.after(UPDATEINTERVAL, self.timer_event)        self.text.after(FONTUPDATEINTERVAL, self.font_timer_event)     def toggle_code_context_event(self, event=None):        if not self.label:            self.pad_frame = Tkinter.Frame(self.editwin.top,                                           bg=self.bgcolor, border=2,                                           relief="sunken")            self.label = Tkinter.Label(self.pad_frame,                                      text="\n" * (self.context_depth - 1),                                      anchor="w", justify="left",                                      font=self.textfont,                                      bg=self.bgcolor, fg=self.fgcolor,                                      border=0,                                      width=1, # Don't request more than we get                                      )            self.label.pack(side="top", fill="x", expand=True,                            padx=4, pady=0)            self.pad_frame.pack(side="top", fill="x", expand=False,                                padx=0, pady=0,                                after=self.editwin.status_bar)        else:            self.label.destroy()            self.pad_frame.destroy()            self.label = None        idleConf.SetOption("extensions", "CodeContext", "visible",                           str(self.label is not None))        idleConf.SaveUserCfgFiles()     def get_line_info(self, linenum):        """Get the line indent value, text, and any block start keyword         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)        opener = firstword in BLOCKOPENERS and firstword        if len(text) == len(spaces) or text[len(spaces)] == '#':            indent = INFINITY        else:            indent = len(spaces)        return indent, text, opener     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 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 linenum < new_topvisible and indent >= stopindent:                    lines.append((linenum, indent, text, opener))                if lastindent <= stopindent:                    break        lines.reverse()        return lines, lastindent     def update_code_context(self):        """Update context information and lines visible in the context pane.         """        new_topvisible = int(self.text.index("@0,0").split('.')[0])        if self.topvisible == new_topvisible:      # haven't scrolled            return        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]        elif self.topvisible > new_topvisible:     # scroll up            stopindent = self.info[-1][1] + 1            # 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.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_code_context()        self.text.after(UPDATEINTERVAL, self.timer_event)     def font_timer_event(self):        newtextfont = self.text["font"]        if self.label and newtextfont != self.textfont:            self.textfont = newtextfont            self.label["font"] = self.textfont        self.text.after(FONTUPDATEINTERVAL, self.font_timer_event)