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