Kaydet (Commit) 8506016f authored tarafından Cheryl Sabella's avatar Cheryl Sabella Kaydeden (comit) Terry Jan Reedy

bpo-33628: IDLE: Minor code cleanup of codecontext.py and its tests (GH-7085)

üst 8ebf5ceb
...@@ -3,6 +3,8 @@ Released on 2018-06-18? ...@@ -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. bpo-32831: Add docstrings and tests for codecontext.py.
Coverage is 100%. Patch by Cheryl Sabella. Coverage is 100%. Patch by Cheryl Sabella.
......
...@@ -23,9 +23,23 @@ UPDATEINTERVAL = 100 # millisec ...@@ -23,9 +23,23 @@ UPDATEINTERVAL = 100 # millisec
FONTUPDATEINTERVAL = 1000 # millisec FONTUPDATEINTERVAL = 1000 # millisec
def getspacesfirstword(s, c=re.compile(r"^(\s*)(\w*)")): def get_spaces_firstword(codeline, c=re.compile(r"^(\s*)(\w*)")):
"Extract the beginning whitespace and first word from s." "Extract the beginning whitespace and first word from codeline."
return c.match(s).groups() 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: class CodeContext:
...@@ -42,15 +56,15 @@ class CodeContext: ...@@ -42,15 +56,15 @@ class CodeContext:
self.textfont is the editor window font. self.textfont is the editor window font.
self.label displays the code context text above the editor text. 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.topvisible is the number of the top text line displayed.
self.info is a list of (line number, indent level, line text, self.info is a list of (line number, indent level, line text,
block keyword) tuples for the block structure above topvisible. block keyword) tuples for the block structure above topvisible.
s self.info[0] is initialized a 'dummy' line which self.info[0] is initialized with a 'dummy' line which
# starts the toplevel 'block' of the module. starts the toplevel 'block' of the module.
self.t1 and self.t2 are two timer events on the editor text widget to 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.editwin = editwin
self.text = editwin.text self.text = editwin.text
...@@ -94,23 +108,21 @@ class CodeContext: ...@@ -94,23 +108,21 @@ class CodeContext:
# All values are passed through getint(), since some # All values are passed through getint(), since some
# values may be pixel objects, which can't simply be added to ints. # values may be pixel objects, which can't simply be added to ints.
widgets = self.editwin.text, self.editwin.text_frame widgets = self.editwin.text, self.editwin.text_frame
# Calculate the required vertical padding # Calculate the required horizontal padding and border width.
padx = 0 padx = 0
border = 0
for widget in widgets: for widget in widgets:
padx += widget.tk.getint(widget.pack_info()['padx']) padx += widget.tk.getint(widget.pack_info()['padx'])
padx += widget.tk.getint(widget.cget('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')) border += widget.tk.getint(widget.cget('border'))
self.label = tkinter.Label( self.label = tkinter.Label(
self.editwin.top, text="\n" * (self.context_depth - 1), self.editwin.top, text="\n" * (self.context_depth - 1),
anchor=W, justify=LEFT, font=self.textfont, anchor=W, justify=LEFT, font=self.textfont,
bg=self.bgcolor, fg=self.fgcolor, 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) padx=padx, border=border, relief=SUNKEN)
# Pack the label widget before and above the text_frame widget, # 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, self.label.pack(side=TOP, fill=X, expand=False,
before=self.editwin.text_frame) before=self.editwin.text_frame)
else: else:
...@@ -118,21 +130,6 @@ class CodeContext: ...@@ -118,21 +130,6 @@ class CodeContext:
self.label = None self.label = None
return "break" 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): def get_context(self, new_topvisible, stopline=1, stopindent=0):
"""Return a list of block line tuples and the 'last' indent. """Return a list of block line tuples and the 'last' indent.
...@@ -144,16 +141,17 @@ class CodeContext: ...@@ -144,16 +141,17 @@ class CodeContext:
""" """
assert stopline > 0 assert stopline > 0
lines = [] lines = []
# The indentation level we are currently in: # The indentation level we are currently in.
lastindent = INFINITY lastindent = INFINITY
# For a line to be interesting, it must begin with a block opening # For a line to be interesting, it must begin with a block opening
# keyword, and have less indentation than lastindent. # keyword, and have less indentation than lastindent.
for linenum in range(new_topvisible, stopline-1, -1): 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: if indent < lastindent:
lastindent = indent lastindent = indent
if opener in ("else", "elif"): if opener in ("else", "elif"):
# We also show the if statement # Also show the if statement.
lastindent += 1 lastindent += 1
if opener and linenum < new_topvisible and indent >= stopindent: if opener and linenum < new_topvisible and indent >= stopindent:
lines.append((linenum, indent, text, opener)) lines.append((linenum, indent, text, opener))
...@@ -172,19 +170,19 @@ class CodeContext: ...@@ -172,19 +170,19 @@ class CodeContext:
the context label. the context label.
""" """
new_topvisible = int(self.text.index("@0,0").split('.')[0]) 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 return
if self.topvisible < new_topvisible: # scroll down if self.topvisible < new_topvisible: # Scroll down.
lines, lastindent = self.get_context(new_topvisible, lines, lastindent = self.get_context(new_topvisible,
self.topvisible) self.topvisible)
# retain only context info applicable to the region # Retain only context info applicable to the region
# between topvisible and new_topvisible: # between topvisible and new_topvisible.
while self.info[-1][1] >= lastindent: while self.info[-1][1] >= lastindent:
del self.info[-1] del self.info[-1]
else: # self.topvisible > new_topvisible: # scroll up else: # self.topvisible > new_topvisible: # Scroll up.
stopindent = self.info[-1][1] + 1 stopindent = self.info[-1][1] + 1
# retain only context info associated # Retain only context info associated
# with lines above new_topvisible: # with lines above new_topvisible.
while self.info[-1][0] >= new_topvisible: while self.info[-1][0] >= new_topvisible:
stopindent = self.info[-1][1] stopindent = self.info[-1][1]
del self.info[-1] del self.info[-1]
...@@ -193,9 +191,9 @@ class CodeContext: ...@@ -193,9 +191,9 @@ class CodeContext:
stopindent) stopindent)
self.info.extend(lines) self.info.extend(lines)
self.topvisible = new_topvisible self.topvisible = new_topvisible
# empty lines in context pane: # Empty lines in context pane.
context_strings = [""] * max(0, self.context_depth - len(self.info)) 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:]] context_strings += [x[2] for x in self.info[-self.context_depth:]]
self.label["text"] = '\n'.join(context_strings) self.label["text"] = '\n'.join(context_strings)
......
...@@ -96,8 +96,6 @@ class CodeContextTest(unittest.TestCase): ...@@ -96,8 +96,6 @@ class CodeContextTest(unittest.TestCase):
eq(self.root.tk.call('after', 'info', self.cc.t2)[1], 'timer') eq(self.root.tk.call('after', 'info', self.cc.t2)[1], 'timer')
def test_del(self): 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__() self.cc.__del__()
with self.assertRaises(TclError) as msg: with self.assertRaises(TclError) as msg:
self.root.tk.call('after', 'info', self.cc.t1) self.root.tk.call('after', 'info', self.cc.t1)
...@@ -135,21 +133,6 @@ class CodeContextTest(unittest.TestCase): ...@@ -135,21 +133,6 @@ class CodeContextTest(unittest.TestCase):
eq(toggle(), 'break') eq(toggle(), 'break')
self.assertIsNone(cc.label) 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): def test_get_context(self):
eq = self.assertEqual eq = self.assertEqual
gc = self.cc.get_context gc = self.cc.get_context
...@@ -323,8 +306,8 @@ class CodeContextTest(unittest.TestCase): ...@@ -323,8 +306,8 @@ class CodeContextTest(unittest.TestCase):
class HelperFunctionText(unittest.TestCase): class HelperFunctionText(unittest.TestCase):
def test_getspacesfirstword(self): def test_get_spaces_firstword(self):
get = codecontext.getspacesfirstword get = codecontext.get_spaces_firstword
test_lines = ( test_lines = (
(' first word', (' ', 'first')), (' first word', (' ', 'first')),
('\tfirst word', ('\t', 'first')), ('\tfirst word', ('\t', 'first')),
...@@ -342,6 +325,24 @@ class HelperFunctionText(unittest.TestCase): ...@@ -342,6 +325,24 @@ class HelperFunctionText(unittest.TestCase):
c=re.compile(r'^(\s*)([^\s]*)')), c=re.compile(r'^(\s*)([^\s]*)')),
(' ', '(continuation)')) (' ', '(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__': if __name__ == '__main__':
unittest.main(verbosity=2) unittest.main(verbosity=2)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment