0001 """CodeContext - Display the block context of code at top of edit window 0002 0003 Once code has scrolled off the top of the screen, it can be difficult 0004 to determine which block you are in. This extension implements a pane 0005 at the top of each IDLE edit window which provides block structure 0006 hints. These hints are the lines which contain the block opening 0007 keywords, e.g. 'if', for the enclosing block. The number of hint lines 0008 is determined by the numlines variable in the CodeContext section of 0009 config-extensions.def. Lines which do not open blocks are not shown in 0010 the context hints pane. 0011 0012 """ 0013 import Tkinter 0014 from configHandler import idleConf 0015 from sets import Set 0016 import re 0017 0018 BLOCKOPENERS = Set(["class", "def", "elif", "else", "except", "finally", "for", 0019 "if", "try", "while"]) 0020 INFINITY = 1 << 30 0021 UPDATEINTERVAL = 100 # millisec 0022 FONTUPDATEINTERVAL = 1000 # millisec 0023 0024 getspacesfirstword = lambda s, c=re.compile(r"^(\s*)(\w*)"): c.match(s).groups() 0025 0026 class CodeContext: 0027 menudefs = [('options', [('!Code Conte_xt', '<<toggle-code-context>>')])] 0028 0029 numlines = idleConf.GetOption("extensions", "CodeContext", 0030 "numlines", type="int", default=3) 0031 bgcolor = idleConf.GetOption("extensions", "CodeContext", 0032 "bgcolor", type="str", default="LightGray") 0033 fgcolor = idleConf.GetOption("extensions", "CodeContext", 0034 "fgcolor", type="str", default="Black") 0035 def __init__(self, editwin): 0036 self.editwin = editwin 0037 self.text = editwin.text 0038 self.textfont = self.text["font"] 0039 self.label = None 0040 # Dummy line, which starts the "block" of the whole document: 0041 self.info = list(self.interesting_lines(1)) 0042 self.lastfirstline = 1 0043 visible = idleConf.GetOption("extensions", "CodeContext", 0044 "visible", type="bool", default=False) 0045 if visible: 0046 self.toggle_code_context_event() 0047 self.editwin.setvar('<<toggle-code-context>>', True) 0048 # Start two update cycles, one for context lines, one for font changes. 0049 self.text.after(UPDATEINTERVAL, self.timer_event) 0050 self.text.after(FONTUPDATEINTERVAL, self.font_timer_event) 0051 0052 def toggle_code_context_event(self, event=None): 0053 if not self.label: 0054 self.label = Tkinter.Label(self.editwin.top, 0055 text="\n" * (self.numlines - 1), 0056 anchor="w", justify="left", 0057 font=self.textfont, 0058 bg=self.bgcolor, fg=self.fgcolor, 0059 relief="sunken", 0060 width=1, # Don't request more than we get 0061 ) 0062 self.label.pack(side="top", fill="x", expand=0, 0063 after=self.editwin.status_bar) 0064 else: 0065 self.label.destroy() 0066 self.label = None 0067 idleConf.SetOption("extensions", "CodeContext", "visible", 0068 str(self.label is not None)) 0069 idleConf.SaveUserCfgFiles() 0070 0071 def get_line_info(self, linenum): 0072 """Get the line indent value, text, and any block start keyword 0073 0074 If the line does not start a block, the keyword value is False. 0075 The indentation of empty lines (or comment lines) is INFINITY. 0076 There is a dummy block start, with indentation -1 and text "". 0077 0078 Return the indent level, text (including leading whitespace), 0079 and the block opening keyword. 0080 0081 """ 0082 if linenum == 0: 0083 return -1, "", True 0084 text = self.text.get("%d.0" % linenum, "%d.end" % linenum) 0085 spaces, firstword = getspacesfirstword(text) 0086 opener = firstword in BLOCKOPENERS and firstword 0087 if len(text) == len(spaces) or text[len(spaces)] == '#': 0088 indent = INFINITY 0089 else: 0090 indent = len(spaces) 0091 return indent, text, opener 0092 0093 def interesting_lines(self, firstline): 0094 """Generator which yields context lines, starting at firstline.""" 0095 # The indentation level we are currently in: 0096 lastindent = INFINITY 0097 # For a line to be interesting, it must begin with a block opening 0098 # keyword, and have less indentation than lastindent. 0099 for line_index in xrange(firstline, -1, -1): 0100 indent, text, opener = self.get_line_info(line_index) 0101 if indent < lastindent: 0102 lastindent = indent 0103 if opener in ("else", "elif"): 0104 # We also show the if statement 0105 lastindent += 1 0106 if opener and line_index < firstline: 0107 yield line_index, text 0108 0109 def update_label(self): 0110 firstline = int(self.text.index("@0,0").split('.')[0]) 0111 if self.lastfirstline == firstline: 0112 return 0113 self.lastfirstline = firstline 0114 tmpstack = [] 0115 for line_index, text in self.interesting_lines(firstline): 0116 # Remove irrelevant self.info items, and when we reach a relevant 0117 # item (which must happen because of the dummy element), break. 0118 while self.info[-1][0] > line_index: 0119 del self.info[-1] 0120 if self.info[-1][0] == line_index: 0121 break 0122 tmpstack.append((line_index, text)) 0123 while tmpstack: 0124 self.info.append(tmpstack.pop()) 0125 lines = [""] * max(0, self.numlines - len(self.info)) + \ 0126 [x[1] for x in self.info[-self.numlines:]] 0127 self.label["text"] = '\n'.join(lines) 0128 0129 def timer_event(self): 0130 if self.label: 0131 self.update_label() 0132 self.text.after(UPDATEINTERVAL, self.timer_event) 0133 0134 def font_timer_event(self): 0135 newtextfont = self.text["font"] 0136 if self.label and newtextfont != self.textfont: 0137 self.textfont = newtextfont 0138 self.label["font"] = self.textfont 0139 self.text.after(FONTUPDATEINTERVAL, self.font_timer_event) 0140
Generated by PyXR 0.9.4