python-2.5.2/win32/Lib/idlelib/CodeContext.py
changeset 0 ae805ac0140d
equal deleted inserted replaced
-1:000000000000 0:ae805ac0140d
       
     1 """CodeContext - Extension to display the block context above the edit window
       
     2 
       
     3 Once code has scrolled off the top of a window, it can be difficult to
       
     4 determine which block you are in.  This extension implements a pane at the top
       
     5 of each IDLE edit window which provides block structure hints.  These hints are
       
     6 the lines which contain the block opening keywords, e.g. 'if', for the
       
     7 enclosing block.  The number of hint lines is determined by the numlines
       
     8 variable in the CodeContext section of config-extensions.def. Lines which do
       
     9 not open blocks are not shown in the context hints pane.
       
    10 
       
    11 """
       
    12 import Tkinter
       
    13 from configHandler import idleConf
       
    14 import re
       
    15 from sys import maxint as INFINITY
       
    16 
       
    17 BLOCKOPENERS = set(["class", "def", "elif", "else", "except", "finally", "for",
       
    18                     "if", "try", "while", "with"])
       
    19 UPDATEINTERVAL = 100 # millisec
       
    20 FONTUPDATEINTERVAL = 1000 # millisec
       
    21 
       
    22 getspacesfirstword =\
       
    23                    lambda s, c=re.compile(r"^(\s*)(\w*)"): c.match(s).groups()
       
    24 
       
    25 class CodeContext:
       
    26     menudefs = [('options', [('!Code Conte_xt', '<<toggle-code-context>>')])]
       
    27 
       
    28     context_depth = idleConf.GetOption("extensions", "CodeContext",
       
    29                                        "numlines", type="int", default=3)
       
    30     bgcolor = idleConf.GetOption("extensions", "CodeContext",
       
    31                                  "bgcolor", type="str", default="LightGray")
       
    32     fgcolor = idleConf.GetOption("extensions", "CodeContext",
       
    33                                  "fgcolor", type="str", default="Black")
       
    34     def __init__(self, editwin):
       
    35         self.editwin = editwin
       
    36         self.text = editwin.text
       
    37         self.textfont = self.text["font"]
       
    38         self.label = None
       
    39         # self.info is a list of (line number, indent level, line text, block
       
    40         # keyword) tuples providing the block structure associated with
       
    41         # self.topvisible (the linenumber of the line displayed at the top of
       
    42         # the edit window). self.info[0] is initialized as a 'dummy' line which
       
    43         # starts the toplevel 'block' of the module.
       
    44         self.info = [(0, -1, "", False)]
       
    45         self.topvisible = 1
       
    46         visible = idleConf.GetOption("extensions", "CodeContext",
       
    47                                      "visible", type="bool", default=False)
       
    48         if visible:
       
    49             self.toggle_code_context_event()
       
    50             self.editwin.setvar('<<toggle-code-context>>', True)
       
    51         # Start two update cycles, one for context lines, one for font changes.
       
    52         self.text.after(UPDATEINTERVAL, self.timer_event)
       
    53         self.text.after(FONTUPDATEINTERVAL, self.font_timer_event)
       
    54 
       
    55     def toggle_code_context_event(self, event=None):
       
    56         if not self.label:
       
    57             self.pad_frame = Tkinter.Frame(self.editwin.top,
       
    58                                            bg=self.bgcolor, border=2,
       
    59                                            relief="sunken")
       
    60             self.label = Tkinter.Label(self.pad_frame,
       
    61                                       text="\n" * (self.context_depth - 1),
       
    62                                       anchor="w", justify="left",
       
    63                                       font=self.textfont,
       
    64                                       bg=self.bgcolor, fg=self.fgcolor,
       
    65                                       border=0,
       
    66                                       width=1, # Don't request more than we get
       
    67                                       )
       
    68             self.label.pack(side="top", fill="x", expand=True,
       
    69                             padx=4, pady=0)
       
    70             self.pad_frame.pack(side="top", fill="x", expand=False,
       
    71                                 padx=0, pady=0,
       
    72                                 after=self.editwin.status_bar)
       
    73         else:
       
    74             self.label.destroy()
       
    75             self.pad_frame.destroy()
       
    76             self.label = None
       
    77         idleConf.SetOption("extensions", "CodeContext", "visible",
       
    78                            str(self.label is not None))
       
    79         idleConf.SaveUserCfgFiles()
       
    80 
       
    81     def get_line_info(self, linenum):
       
    82         """Get the line indent value, text, and any block start keyword
       
    83 
       
    84         If the line does not start a block, the keyword value is False.
       
    85         The indentation of empty lines (or comment lines) is INFINITY.
       
    86 
       
    87         """
       
    88         text = self.text.get("%d.0" % linenum, "%d.end" % linenum)
       
    89         spaces, firstword = getspacesfirstword(text)
       
    90         opener = firstword in BLOCKOPENERS and firstword
       
    91         if len(text) == len(spaces) or text[len(spaces)] == '#':
       
    92             indent = INFINITY
       
    93         else:
       
    94             indent = len(spaces)
       
    95         return indent, text, opener
       
    96 
       
    97     def get_context(self, new_topvisible, stopline=1, stopindent=0):
       
    98         """Get context lines, starting at new_topvisible and working backwards.
       
    99 
       
   100         Stop when stopline or stopindent is reached. Return a tuple of context
       
   101         data and the indent level at the top of the region inspected.
       
   102 
       
   103         """
       
   104         assert stopline > 0
       
   105         lines = []
       
   106         # The indentation level we are currently in:
       
   107         lastindent = INFINITY
       
   108         # For a line to be interesting, it must begin with a block opening
       
   109         # keyword, and have less indentation than lastindent.
       
   110         for linenum in xrange(new_topvisible, stopline-1, -1):
       
   111             indent, text, opener = self.get_line_info(linenum)
       
   112             if indent < lastindent:
       
   113                 lastindent = indent
       
   114                 if opener in ("else", "elif"):
       
   115                     # We also show the if statement
       
   116                     lastindent += 1
       
   117                 if opener and linenum < new_topvisible and indent >= stopindent:
       
   118                     lines.append((linenum, indent, text, opener))
       
   119                 if lastindent <= stopindent:
       
   120                     break
       
   121         lines.reverse()
       
   122         return lines, lastindent
       
   123 
       
   124     def update_code_context(self):
       
   125         """Update context information and lines visible in the context pane.
       
   126 
       
   127         """
       
   128         new_topvisible = int(self.text.index("@0,0").split('.')[0])
       
   129         if self.topvisible == new_topvisible:      # haven't scrolled
       
   130             return
       
   131         if self.topvisible < new_topvisible:       # scroll down
       
   132             lines, lastindent = self.get_context(new_topvisible,
       
   133                                                  self.topvisible)
       
   134             # retain only context info applicable to the region
       
   135             # between topvisible and new_topvisible:
       
   136             while self.info[-1][1] >= lastindent:
       
   137                 del self.info[-1]
       
   138         elif self.topvisible > new_topvisible:     # scroll up
       
   139             stopindent = self.info[-1][1] + 1
       
   140             # retain only context info associated
       
   141             # with lines above new_topvisible:
       
   142             while self.info[-1][0] >= new_topvisible:
       
   143                 stopindent = self.info[-1][1]
       
   144                 del self.info[-1]
       
   145             lines, lastindent = self.get_context(new_topvisible,
       
   146                                                  self.info[-1][0]+1,
       
   147                                                  stopindent)
       
   148         self.info.extend(lines)
       
   149         self.topvisible = new_topvisible
       
   150 
       
   151         # empty lines in context pane:
       
   152         context_strings = [""] * max(0, self.context_depth - len(self.info))
       
   153         # followed by the context hint lines:
       
   154         context_strings += [x[2] for x in self.info[-self.context_depth:]]
       
   155         self.label["text"] = '\n'.join(context_strings)
       
   156 
       
   157     def timer_event(self):
       
   158         if self.label:
       
   159             self.update_code_context()
       
   160         self.text.after(UPDATEINTERVAL, self.timer_event)
       
   161 
       
   162     def font_timer_event(self):
       
   163         newtextfont = self.text["font"]
       
   164         if self.label and newtextfont != self.textfont:
       
   165             self.textfont = newtextfont
       
   166             self.label["font"] = self.textfont
       
   167         self.text.after(FONTUPDATEINTERVAL, self.font_timer_event)