summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Lib/idlelib/NEWS.txt2
-rw-r--r--Lib/idlelib/codecontext.py78
-rw-r--r--Lib/idlelib/idle_test/test_codecontext.py39
-rw-r--r--Misc/NEWS.d/next/IDLE/2018-05-23-19-51-07.bpo-33628.sLlFLO.rst2
4 files changed, 62 insertions, 59 deletions
diff --git a/Lib/idlelib/NEWS.txt b/Lib/idlelib/NEWS.txt
index 786378e..2f8237d 100644
--- a/Lib/idlelib/NEWS.txt
+++ b/Lib/idlelib/NEWS.txt
@@ -3,6 +3,8 @@ Released on 2018-06-18?
======================================
+bpo-33628: Cleanup codecontext.py and its test.
+
bpo-32831: Add docstrings and tests for codecontext.py.
Coverage is 100%. Patch by Cheryl Sabella.
diff --git a/Lib/idlelib/codecontext.py b/Lib/idlelib/codecontext.py
index efd163e..635f68c 100644
--- a/Lib/idlelib/codecontext.py
+++ b/Lib/idlelib/codecontext.py
@@ -23,9 +23,23 @@ UPDATEINTERVAL = 100 # millisec
FONTUPDATEINTERVAL = 1000 # millisec
-def getspacesfirstword(s, c=re.compile(r"^(\s*)(\w*)")):
- "Extract the beginning whitespace and first word from s."
- return c.match(s).groups()
+def get_spaces_firstword(codeline, c=re.compile(r"^(\s*)(\w*)")):
+ "Extract the beginning whitespace and first word from codeline."
+ return c.match(codeline).groups()
+
+
+def get_line_info(codeline):
+ """Return tuple of (line indent value, codeline, block start keyword).
+
+ The indentation of empty lines (or comment lines) is INFINITY.
+ If the line does not start a block, the keyword value is False.
+ """
+ spaces, firstword = get_spaces_firstword(codeline)
+ indent = len(spaces)
+ if len(codeline) == indent or codeline[indent] == '#':
+ indent = INFINITY
+ opener = firstword in BLOCKOPENERS and firstword
+ return indent, codeline, opener
class CodeContext:
@@ -42,15 +56,15 @@ class CodeContext:
self.textfont is the editor window font.
self.label displays the code context text above the editor text.
- Initially None it is toggled via <<toggle-code-context>>.
+ Initially None, it is toggled via <<toggle-code-context>>.
self.topvisible is the number of the top text line displayed.
self.info is a list of (line number, indent level, line text,
block keyword) tuples for the block structure above topvisible.
- s self.info[0] is initialized a 'dummy' line which
- # starts the toplevel 'block' of the module.
+ self.info[0] is initialized with a 'dummy' line which
+ starts the toplevel 'block' of the module.
self.t1 and self.t2 are two timer events on the editor text widget to
- monitor for changes to the context text or editor font.
+ monitor for changes to the context text or editor font.
"""
self.editwin = editwin
self.text = editwin.text
@@ -94,23 +108,21 @@ class CodeContext:
# All values are passed through getint(), since some
# values may be pixel objects, which can't simply be added to ints.
widgets = self.editwin.text, self.editwin.text_frame
- # Calculate the required vertical padding
+ # Calculate the required horizontal padding and border width.
padx = 0
+ border = 0
for widget in widgets:
padx += widget.tk.getint(widget.pack_info()['padx'])
padx += widget.tk.getint(widget.cget('padx'))
- # Calculate the required border width
- border = 0
- for widget in widgets:
border += widget.tk.getint(widget.cget('border'))
self.label = tkinter.Label(
self.editwin.top, text="\n" * (self.context_depth - 1),
anchor=W, justify=LEFT, font=self.textfont,
bg=self.bgcolor, fg=self.fgcolor,
- width=1, #don't request more than we get
+ width=1, # Don't request more than we get.
padx=padx, border=border, relief=SUNKEN)
# Pack the label widget before and above the text_frame widget,
- # thus ensuring that it will appear directly above text_frame
+ # thus ensuring that it will appear directly above text_frame.
self.label.pack(side=TOP, fill=X, expand=False,
before=self.editwin.text_frame)
else:
@@ -118,21 +130,6 @@ class CodeContext:
self.label = None
return "break"
- def get_line_info(self, linenum):
- """Return tuple of (line indent value, text, and block start keyword).
-
- If the line does not start a block, the keyword value is False.
- The indentation of empty lines (or comment lines) is INFINITY.
- """
- text = self.text.get("%d.0" % linenum, "%d.end" % linenum)
- spaces, firstword = getspacesfirstword(text)
- opener = firstword in BLOCKOPENERS and firstword
- if len(text) == len(spaces) or text[len(spaces)] == '#':
- indent = INFINITY
- else:
- indent = len(spaces)
- return indent, text, opener
-
def get_context(self, new_topvisible, stopline=1, stopindent=0):
"""Return a list of block line tuples and the 'last' indent.
@@ -144,16 +141,17 @@ class CodeContext:
"""
assert stopline > 0
lines = []
- # The indentation level we are currently in:
+ # The indentation level we are currently in.
lastindent = INFINITY
# For a line to be interesting, it must begin with a block opening
# keyword, and have less indentation than lastindent.
for linenum in range(new_topvisible, stopline-1, -1):
- indent, text, opener = self.get_line_info(linenum)
+ codeline = self.text.get(f'{linenum}.0', f'{linenum}.end')
+ indent, text, opener = get_line_info(codeline)
if indent < lastindent:
lastindent = indent
if opener in ("else", "elif"):
- # We also show the if statement
+ # Also show the if statement.
lastindent += 1
if opener and linenum < new_topvisible and indent >= stopindent:
lines.append((linenum, indent, text, opener))
@@ -172,19 +170,19 @@ class CodeContext:
the context label.
"""
new_topvisible = int(self.text.index("@0,0").split('.')[0])
- if self.topvisible == new_topvisible: # haven't scrolled
+ if self.topvisible == new_topvisible: # Haven't scrolled.
return
- if self.topvisible < new_topvisible: # scroll down
+ if self.topvisible < new_topvisible: # Scroll down.
lines, lastindent = self.get_context(new_topvisible,
self.topvisible)
- # retain only context info applicable to the region
- # between topvisible and new_topvisible:
+ # Retain only context info applicable to the region
+ # between topvisible and new_topvisible.
while self.info[-1][1] >= lastindent:
del self.info[-1]
- else: # self.topvisible > new_topvisible: # scroll up
+ else: # self.topvisible > new_topvisible: # Scroll up.
stopindent = self.info[-1][1] + 1
- # retain only context info associated
- # with lines above new_topvisible:
+ # Retain only context info associated
+ # with lines above new_topvisible.
while self.info[-1][0] >= new_topvisible:
stopindent = self.info[-1][1]
del self.info[-1]
@@ -193,9 +191,9 @@ class CodeContext:
stopindent)
self.info.extend(lines)
self.topvisible = new_topvisible
- # empty lines in context pane:
+ # Empty lines in context pane.
context_strings = [""] * max(0, self.context_depth - len(self.info))
- # followed by the context hint lines:
+ # Followed by the context hint lines.
context_strings += [x[2] for x in self.info[-self.context_depth:]]
self.label["text"] = '\n'.join(context_strings)
diff --git a/Lib/idlelib/idle_test/test_codecontext.py b/Lib/idlelib/idle_test/test_codecontext.py
index 448094e..ed45828 100644
--- a/Lib/idlelib/idle_test/test_codecontext.py
+++ b/Lib/idlelib/idle_test/test_codecontext.py
@@ -96,8 +96,6 @@ class CodeContextTest(unittest.TestCase):
eq(self.root.tk.call('after', 'info', self.cc.t2)[1], 'timer')
def test_del(self):
- self.root.tk.call('after', 'info', self.cc.t1)
- self.root.tk.call('after', 'info', self.cc.t2)
self.cc.__del__()
with self.assertRaises(TclError) as msg:
self.root.tk.call('after', 'info', self.cc.t1)
@@ -135,21 +133,6 @@ class CodeContextTest(unittest.TestCase):
eq(toggle(), 'break')
self.assertIsNone(cc.label)
- def test_get_line_info(self):
- eq = self.assertEqual
- gli = self.cc.get_line_info
-
- # Line 1 is not a BLOCKOPENER.
- eq(gli(1), (codecontext.INFINITY, '', False))
- # Line 2 is a BLOCKOPENER without an indent.
- eq(gli(2), (0, 'class C1():', 'class'))
- # Line 3 is not a BLOCKOPENER and does not return the indent level.
- eq(gli(3), (codecontext.INFINITY, ' # Class comment.', False))
- # Line 4 is a BLOCKOPENER and is indented.
- eq(gli(4), (4, ' def __init__(self, a, b):', 'def'))
- # Line 8 is a different BLOCKOPENER and is indented.
- eq(gli(8), (8, ' if a > b:', 'if'))
-
def test_get_context(self):
eq = self.assertEqual
gc = self.cc.get_context
@@ -323,8 +306,8 @@ class CodeContextTest(unittest.TestCase):
class HelperFunctionText(unittest.TestCase):
- def test_getspacesfirstword(self):
- get = codecontext.getspacesfirstword
+ def test_get_spaces_firstword(self):
+ get = codecontext.get_spaces_firstword
test_lines = (
(' first word', (' ', 'first')),
('\tfirst word', ('\t', 'first')),
@@ -342,6 +325,24 @@ class HelperFunctionText(unittest.TestCase):
c=re.compile(r'^(\s*)([^\s]*)')),
(' ', '(continuation)'))
+ def test_get_line_info(self):
+ eq = self.assertEqual
+ gli = codecontext.get_line_info
+ lines = code_sample.splitlines()
+
+ # Line 1 is not a BLOCKOPENER.
+ eq(gli(lines[0]), (codecontext.INFINITY, '', False))
+ # Line 2 is a BLOCKOPENER without an indent.
+ eq(gli(lines[1]), (0, 'class C1():', 'class'))
+ # Line 3 is not a BLOCKOPENER and does not return the indent level.
+ eq(gli(lines[2]), (codecontext.INFINITY, ' # Class comment.', False))
+ # Line 4 is a BLOCKOPENER and is indented.
+ eq(gli(lines[3]), (4, ' def __init__(self, a, b):', 'def'))
+ # Line 8 is a different BLOCKOPENER and is indented.
+ eq(gli(lines[7]), (8, ' if a > b:', 'if'))
+ # Test tab.
+ eq(gli('\tif a == b:'), (1, '\tif a == b:', 'if'))
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/Misc/NEWS.d/next/IDLE/2018-05-23-19-51-07.bpo-33628.sLlFLO.rst b/Misc/NEWS.d/next/IDLE/2018-05-23-19-51-07.bpo-33628.sLlFLO.rst
new file mode 100644
index 0000000..f0b13a2
--- /dev/null
+++ b/Misc/NEWS.d/next/IDLE/2018-05-23-19-51-07.bpo-33628.sLlFLO.rst
@@ -0,0 +1,2 @@
+IDLE: Cleanup codecontext.py and its test.
+