ยปCore Development>Code coverage>Lib/idlelib/CodeContext.py

Python code coverage for Lib/idlelib/CodeContext.py

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