1 """CodeContext - Extension to display the block context above the edit window
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.
13 from configHandler
import idleConf
15 from sys
import maxint
as INFINITY
17 BLOCKOPENERS
= set(["class", "def", "elif", "else", "except", "finally", "for",
18 "if", "try", "while", "with"])
19 UPDATEINTERVAL
= 100 # millisec
20 FONTUPDATEINTERVAL
= 1000 # millisec
23 lambda s
, c
=re
.compile(r
"^(\s*)(\w*)"): c
.match(s
).groups()
26 menudefs
= [('options', [('!Code Conte_xt', '<<toggle-code-context>>')])]
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"]
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)]
46 visible
= idleConf
.GetOption("extensions", "CodeContext",
47 "visible", type="bool", default
=False)
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
)
55 def toggle_code_context_event(self
, event
=None):
57 self
.pad_frame
= Tkinter
.Frame(self
.editwin
.top
,
58 bg
=self
.bgcolor
, border
=2,
60 self
.label
= Tkinter
.Label(self
.pad_frame
,
61 text
="\n" * (self
.context_depth
- 1),
62 anchor
="w", justify
="left",
64 bg
=self
.bgcolor
, fg
=self
.fgcolor
,
66 width
=1, # Don't request more than we get
68 self
.label
.pack(side
="top", fill
="x", expand
=True,
70 self
.pad_frame
.pack(side
="top", fill
="x", expand
=False,
72 after
=self
.editwin
.status_bar
)
75 self
.pad_frame
.destroy()
77 idleConf
.SetOption("extensions", "CodeContext", "visible",
78 str(self
.label
is not None))
79 idleConf
.SaveUserCfgFiles()
81 def get_line_info(self
, linenum
):
82 """Get the line indent value, text, and any block start keyword
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.
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
)] == '#':
95 return indent
, text
, opener
97 def get_context(self
, new_topvisible
, stopline
=1, stopindent
=0):
98 """Get context lines, starting at new_topvisible and working backwards.
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.
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
:
114 if opener
in ("else", "elif"):
115 # We also show the if statement
117 if opener
and linenum
< new_topvisible
and indent
>= stopindent
:
118 lines
.append((linenum
, indent
, text
, opener
))
119 if lastindent
<= stopindent
:
122 return lines
, lastindent
124 def update_code_context(self
):
125 """Update context information and lines visible in the context pane.
128 new_topvisible
= int(self
.text
.index("@0,0").split('.')[0])
129 if self
.topvisible
== new_topvisible
: # haven't scrolled
131 if self
.topvisible
< new_topvisible
: # scroll down
132 lines
, lastindent
= self
.get_context(new_topvisible
,
134 # retain only context info applicable to the region
135 # between topvisible and new_topvisible:
136 while self
.info
[-1][1] >= lastindent
:
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]
145 lines
, lastindent
= self
.get_context(new_topvisible
,
148 self
.info
.extend(lines
)
149 self
.topvisible
= new_topvisible
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
)
157 def timer_event(self
):
159 self
.update_code_context()
160 self
.text
.after(UPDATEINTERVAL
, self
.timer_event
)
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
)