bpo-32831: IDLE: Add docstrings and tests for codecontext (GH-5638)
(cherry picked from commit 654038d896
)
Co-authored-by: Cheryl Sabella <cheryl.sabella@gmail.com>
This commit is contained in:
parent
66b3f53db9
commit
83aedc4d9a
|
@ -22,32 +22,49 @@ BLOCKOPENERS = {"class", "def", "elif", "else", "except", "finally", "for",
|
||||||
UPDATEINTERVAL = 100 # millisec
|
UPDATEINTERVAL = 100 # millisec
|
||||||
FONTUPDATEINTERVAL = 1000 # millisec
|
FONTUPDATEINTERVAL = 1000 # millisec
|
||||||
|
|
||||||
|
|
||||||
def getspacesfirstword(s, c=re.compile(r"^(\s*)(\w*)")):
|
def getspacesfirstword(s, c=re.compile(r"^(\s*)(\w*)")):
|
||||||
|
"Extract the beginning whitespace and first word from s."
|
||||||
return c.match(s).groups()
|
return c.match(s).groups()
|
||||||
|
|
||||||
|
|
||||||
class CodeContext:
|
class CodeContext:
|
||||||
|
"Display block context above the edit window."
|
||||||
|
|
||||||
bgcolor = "LightGray"
|
bgcolor = "LightGray"
|
||||||
fgcolor = "Black"
|
fgcolor = "Black"
|
||||||
|
|
||||||
def __init__(self, editwin):
|
def __init__(self, editwin):
|
||||||
|
"""Initialize settings for context block.
|
||||||
|
|
||||||
|
editwin is the Editor window for the context block.
|
||||||
|
self.text is the editor window text widget.
|
||||||
|
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>>.
|
||||||
|
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.t1 and self.t2 are two timer events on the editor text widget to
|
||||||
|
monitor for changes to the context text or editor font.
|
||||||
|
"""
|
||||||
self.editwin = editwin
|
self.editwin = editwin
|
||||||
self.text = editwin.text
|
self.text = editwin.text
|
||||||
self.textfont = self.text["font"]
|
self.textfont = self.text["font"]
|
||||||
self.label = None
|
self.label = None
|
||||||
# self.info is a list of (line number, indent level, line text, block
|
|
||||||
# keyword) tuples providing the block structure associated with
|
|
||||||
# self.topvisible (the linenumber of the line displayed at the top of
|
|
||||||
# the edit window). self.info[0] is initialized as a 'dummy' line which
|
|
||||||
# starts the toplevel 'block' of the module.
|
|
||||||
self.info = [(0, -1, "", False)]
|
|
||||||
self.topvisible = 1
|
self.topvisible = 1
|
||||||
|
self.info = [(0, -1, "", False)]
|
||||||
# Start two update cycles, one for context lines, one for font changes.
|
# Start two update cycles, one for context lines, one for font changes.
|
||||||
self.t1 = self.text.after(UPDATEINTERVAL, self.timer_event)
|
self.t1 = self.text.after(UPDATEINTERVAL, self.timer_event)
|
||||||
self.t2 = self.text.after(FONTUPDATEINTERVAL, self.font_timer_event)
|
self.t2 = self.text.after(FONTUPDATEINTERVAL, self.font_timer_event)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def reload(cls):
|
def reload(cls):
|
||||||
|
"Load class variables from config."
|
||||||
cls.context_depth = idleConf.GetOption("extensions", "CodeContext",
|
cls.context_depth = idleConf.GetOption("extensions", "CodeContext",
|
||||||
"numlines", type="int", default=3)
|
"numlines", type="int", default=3)
|
||||||
## cls.bgcolor = idleConf.GetOption("extensions", "CodeContext",
|
## cls.bgcolor = idleConf.GetOption("extensions", "CodeContext",
|
||||||
|
@ -56,6 +73,7 @@ class CodeContext:
|
||||||
## "fgcolor", type="str", default="Black")
|
## "fgcolor", type="str", default="Black")
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
|
"Cancel scheduled events."
|
||||||
try:
|
try:
|
||||||
self.text.after_cancel(self.t1)
|
self.text.after_cancel(self.t1)
|
||||||
self.text.after_cancel(self.t2)
|
self.text.after_cancel(self.t2)
|
||||||
|
@ -63,6 +81,12 @@ class CodeContext:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def toggle_code_context_event(self, event=None):
|
def toggle_code_context_event(self, event=None):
|
||||||
|
"""Toggle code context display.
|
||||||
|
|
||||||
|
If self.label doesn't exist, create it to match the size of the editor
|
||||||
|
window text (toggle on). If it does exist, destroy it (toggle off).
|
||||||
|
Return 'break' to complete the processing of the binding.
|
||||||
|
"""
|
||||||
if not self.label:
|
if not self.label:
|
||||||
# Calculate the border width and horizontal padding required to
|
# Calculate the border width and horizontal padding required to
|
||||||
# align the context with the text in the main Text widget.
|
# align the context with the text in the main Text widget.
|
||||||
|
@ -95,11 +119,10 @@ class CodeContext:
|
||||||
return "break"
|
return "break"
|
||||||
|
|
||||||
def get_line_info(self, linenum):
|
def get_line_info(self, linenum):
|
||||||
"""Get the line indent value, text, and any block start keyword
|
"""Return tuple of (line indent value, text, and block start keyword).
|
||||||
|
|
||||||
If the line does not start a block, the keyword value is False.
|
If the line does not start a block, the keyword value is False.
|
||||||
The indentation of empty lines (or comment lines) is INFINITY.
|
The indentation of empty lines (or comment lines) is INFINITY.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
text = self.text.get("%d.0" % linenum, "%d.end" % linenum)
|
text = self.text.get("%d.0" % linenum, "%d.end" % linenum)
|
||||||
spaces, firstword = getspacesfirstword(text)
|
spaces, firstword = getspacesfirstword(text)
|
||||||
|
@ -111,11 +134,13 @@ class CodeContext:
|
||||||
return indent, text, opener
|
return indent, text, opener
|
||||||
|
|
||||||
def get_context(self, new_topvisible, stopline=1, stopindent=0):
|
def get_context(self, new_topvisible, stopline=1, stopindent=0):
|
||||||
"""Get context lines, starting at new_topvisible and working backwards.
|
"""Return a list of block line tuples and the 'last' indent.
|
||||||
|
|
||||||
Stop when stopline or stopindent is reached. Return a tuple of context
|
|
||||||
data and the indent level at the top of the region inspected.
|
|
||||||
|
|
||||||
|
The tuple fields are (linenum, indent, text, opener).
|
||||||
|
The list represents header lines from new_topvisible back to
|
||||||
|
stopline with successively shorter indents > stopindent.
|
||||||
|
The list is returned ordered by line number.
|
||||||
|
Last indent returned is the smallest indent observed.
|
||||||
"""
|
"""
|
||||||
assert stopline > 0
|
assert stopline > 0
|
||||||
lines = []
|
lines = []
|
||||||
|
@ -140,6 +165,11 @@ class CodeContext:
|
||||||
def update_code_context(self):
|
def update_code_context(self):
|
||||||
"""Update context information and lines visible in the context pane.
|
"""Update context information and lines visible in the context pane.
|
||||||
|
|
||||||
|
No update is done if the text hasn't been scrolled. If the text
|
||||||
|
was scrolled, the lines that should be shown in the context will
|
||||||
|
be retrieved and the label widget will be updated with the code,
|
||||||
|
padded with blank lines so that the code appears on the bottom of
|
||||||
|
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
|
||||||
|
@ -151,7 +181,7 @@ class CodeContext:
|
||||||
# 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]
|
||||||
elif 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:
|
||||||
|
@ -170,11 +200,13 @@ class CodeContext:
|
||||||
self.label["text"] = '\n'.join(context_strings)
|
self.label["text"] = '\n'.join(context_strings)
|
||||||
|
|
||||||
def timer_event(self):
|
def timer_event(self):
|
||||||
|
"Event on editor text widget triggered every UPDATEINTERVAL ms."
|
||||||
if self.label:
|
if self.label:
|
||||||
self.update_code_context()
|
self.update_code_context()
|
||||||
self.t1 = self.text.after(UPDATEINTERVAL, self.timer_event)
|
self.t1 = self.text.after(UPDATEINTERVAL, self.timer_event)
|
||||||
|
|
||||||
def font_timer_event(self):
|
def font_timer_event(self):
|
||||||
|
"Event on editor text widget triggered every FONTUPDATEINTERVAL ms."
|
||||||
newtextfont = self.text["font"]
|
newtextfont = self.text["font"]
|
||||||
if self.label and newtextfont != self.textfont:
|
if self.label and newtextfont != self.textfont:
|
||||||
self.textfont = newtextfont
|
self.textfont = newtextfont
|
||||||
|
@ -183,3 +215,8 @@ class CodeContext:
|
||||||
|
|
||||||
|
|
||||||
CodeContext.reload()
|
CodeContext.reload()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__": # pragma: no cover
|
||||||
|
import unittest
|
||||||
|
unittest.main('idlelib.idle_test.test_codecontext', verbosity=2, exit=False)
|
||||||
|
|
|
@ -0,0 +1,347 @@
|
||||||
|
"""Test idlelib.codecontext.
|
||||||
|
|
||||||
|
Coverage: 100%
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
from unittest import mock
|
||||||
|
from test.support import requires
|
||||||
|
from tkinter import Tk, Frame, Text, TclError
|
||||||
|
|
||||||
|
import idlelib.codecontext as codecontext
|
||||||
|
from idlelib import config
|
||||||
|
|
||||||
|
|
||||||
|
usercfg = codecontext.idleConf.userCfg
|
||||||
|
testcfg = {
|
||||||
|
'main': config.IdleUserConfParser(''),
|
||||||
|
'highlight': config.IdleUserConfParser(''),
|
||||||
|
'keys': config.IdleUserConfParser(''),
|
||||||
|
'extensions': config.IdleUserConfParser(''),
|
||||||
|
}
|
||||||
|
code_sample = """\
|
||||||
|
|
||||||
|
class C1():
|
||||||
|
# Class comment.
|
||||||
|
def __init__(self, a, b):
|
||||||
|
self.a = a
|
||||||
|
self.b = b
|
||||||
|
def compare(self):
|
||||||
|
if a > b:
|
||||||
|
return a
|
||||||
|
elif a < b:
|
||||||
|
return b
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class DummyEditwin:
|
||||||
|
def __init__(self, root, frame, text):
|
||||||
|
self.root = root
|
||||||
|
self.top = root
|
||||||
|
self.text_frame = frame
|
||||||
|
self.text = text
|
||||||
|
|
||||||
|
|
||||||
|
class CodeContextTest(unittest.TestCase):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
requires('gui')
|
||||||
|
root = cls.root = Tk()
|
||||||
|
root.withdraw()
|
||||||
|
frame = cls.frame = Frame(root)
|
||||||
|
text = cls.text = Text(frame)
|
||||||
|
text.insert('1.0', code_sample)
|
||||||
|
# Need to pack for creation of code context label widget.
|
||||||
|
frame.pack(side='left', fill='both', expand=1)
|
||||||
|
text.pack(side='top', fill='both', expand=1)
|
||||||
|
cls.editor = DummyEditwin(root, frame, text)
|
||||||
|
codecontext.idleConf.userCfg = testcfg
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
codecontext.idleConf.userCfg = usercfg
|
||||||
|
cls.editor.text.delete('1.0', 'end')
|
||||||
|
del cls.editor, cls.frame, cls.text
|
||||||
|
cls.root.update_idletasks()
|
||||||
|
cls.root.destroy()
|
||||||
|
del cls.root
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.cc = codecontext.CodeContext(self.editor)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
if self.cc.label:
|
||||||
|
self.cc.label.destroy()
|
||||||
|
# Explicitly call __del__ to remove scheduled scripts.
|
||||||
|
self.cc.__del__()
|
||||||
|
del self.cc.label, self.cc
|
||||||
|
|
||||||
|
def test_init(self):
|
||||||
|
eq = self.assertEqual
|
||||||
|
ed = self.editor
|
||||||
|
cc = self.cc
|
||||||
|
|
||||||
|
eq(cc.editwin, ed)
|
||||||
|
eq(cc.text, ed.text)
|
||||||
|
eq(cc.textfont, ed.text['font'])
|
||||||
|
self.assertIsNone(cc.label)
|
||||||
|
eq(cc.info, [(0, -1, '', False)])
|
||||||
|
eq(cc.topvisible, 1)
|
||||||
|
eq(self.root.tk.call('after', 'info', self.cc.t1)[1], 'timer')
|
||||||
|
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)
|
||||||
|
self.assertIn("doesn't exist", msg)
|
||||||
|
with self.assertRaises(TclError) as msg:
|
||||||
|
self.root.tk.call('after', 'info', self.cc.t2)
|
||||||
|
self.assertIn("doesn't exist", msg)
|
||||||
|
# For coverage on the except. Have to delete because the
|
||||||
|
# above Tcl error is caught by after_cancel.
|
||||||
|
del self.cc.t1, self.cc.t2
|
||||||
|
self.cc.__del__()
|
||||||
|
|
||||||
|
def test_reload(self):
|
||||||
|
codecontext.CodeContext.reload()
|
||||||
|
self.assertEqual(self.cc.context_depth, 3)
|
||||||
|
|
||||||
|
def test_toggle_code_context_event(self):
|
||||||
|
eq = self.assertEqual
|
||||||
|
cc = self.cc
|
||||||
|
toggle = cc.toggle_code_context_event
|
||||||
|
|
||||||
|
# Make sure code context is off.
|
||||||
|
if cc.label:
|
||||||
|
toggle()
|
||||||
|
|
||||||
|
# Toggle on.
|
||||||
|
eq(toggle(), 'break')
|
||||||
|
self.assertIsNotNone(cc.label)
|
||||||
|
eq(cc.label['font'], cc.textfont)
|
||||||
|
eq(cc.label['fg'], cc.fgcolor)
|
||||||
|
eq(cc.label['bg'], cc.bgcolor)
|
||||||
|
eq(cc.label['text'], '\n' * 2)
|
||||||
|
|
||||||
|
# Toggle off.
|
||||||
|
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
|
||||||
|
|
||||||
|
# stopline must be greater than 0.
|
||||||
|
with self.assertRaises(AssertionError):
|
||||||
|
gc(1, stopline=0)
|
||||||
|
|
||||||
|
eq(gc(3), ([(2, 0, 'class C1():', 'class')], 0))
|
||||||
|
|
||||||
|
# Don't return comment.
|
||||||
|
eq(gc(4), ([(2, 0, 'class C1():', 'class')], 0))
|
||||||
|
|
||||||
|
# Two indentation levels and no comment.
|
||||||
|
eq(gc(5), ([(2, 0, 'class C1():', 'class'),
|
||||||
|
(4, 4, ' def __init__(self, a, b):', 'def')], 0))
|
||||||
|
|
||||||
|
# Only one 'def' is returned, not both at the same indent level.
|
||||||
|
eq(gc(10), ([(2, 0, 'class C1():', 'class'),
|
||||||
|
(7, 4, ' def compare(self):', 'def'),
|
||||||
|
(8, 8, ' if a > b:', 'if')], 0))
|
||||||
|
|
||||||
|
# With 'elif', also show the 'if' even though it's at the same level.
|
||||||
|
eq(gc(11), ([(2, 0, 'class C1():', 'class'),
|
||||||
|
(7, 4, ' def compare(self):', 'def'),
|
||||||
|
(8, 8, ' if a > b:', 'if'),
|
||||||
|
(10, 8, ' elif a < b:', 'elif')], 0))
|
||||||
|
|
||||||
|
# Set stop_line to not go back to first line in source code.
|
||||||
|
# Return includes stop_line.
|
||||||
|
eq(gc(11, stopline=2), ([(2, 0, 'class C1():', 'class'),
|
||||||
|
(7, 4, ' def compare(self):', 'def'),
|
||||||
|
(8, 8, ' if a > b:', 'if'),
|
||||||
|
(10, 8, ' elif a < b:', 'elif')], 0))
|
||||||
|
eq(gc(11, stopline=3), ([(7, 4, ' def compare(self):', 'def'),
|
||||||
|
(8, 8, ' if a > b:', 'if'),
|
||||||
|
(10, 8, ' elif a < b:', 'elif')], 4))
|
||||||
|
eq(gc(11, stopline=8), ([(8, 8, ' if a > b:', 'if'),
|
||||||
|
(10, 8, ' elif a < b:', 'elif')], 8))
|
||||||
|
|
||||||
|
# Set stop_indent to test indent level to stop at.
|
||||||
|
eq(gc(11, stopindent=4), ([(7, 4, ' def compare(self):', 'def'),
|
||||||
|
(8, 8, ' if a > b:', 'if'),
|
||||||
|
(10, 8, ' elif a < b:', 'elif')], 4))
|
||||||
|
# Check that the 'if' is included.
|
||||||
|
eq(gc(11, stopindent=8), ([(8, 8, ' if a > b:', 'if'),
|
||||||
|
(10, 8, ' elif a < b:', 'elif')], 8))
|
||||||
|
|
||||||
|
def test_update_code_context(self):
|
||||||
|
eq = self.assertEqual
|
||||||
|
cc = self.cc
|
||||||
|
# Ensure code context is active.
|
||||||
|
if not cc.label:
|
||||||
|
cc.toggle_code_context_event()
|
||||||
|
|
||||||
|
# Invoke update_code_context without scrolling - nothing happens.
|
||||||
|
self.assertIsNone(cc.update_code_context())
|
||||||
|
eq(cc.info, [(0, -1, '', False)])
|
||||||
|
eq(cc.topvisible, 1)
|
||||||
|
|
||||||
|
# Scroll down to line 2.
|
||||||
|
cc.text.yview(2)
|
||||||
|
cc.update_code_context()
|
||||||
|
eq(cc.info, [(0, -1, '', False), (2, 0, 'class C1():', 'class')])
|
||||||
|
eq(cc.topvisible, 3)
|
||||||
|
# context_depth is 3 so it pads with blank lines.
|
||||||
|
eq(cc.label['text'], '\n'
|
||||||
|
'\n'
|
||||||
|
'class C1():')
|
||||||
|
|
||||||
|
# Scroll down to line 3. Since it's a comment, nothing changes.
|
||||||
|
cc.text.yview(3)
|
||||||
|
cc.update_code_context()
|
||||||
|
eq(cc.info, [(0, -1, '', False), (2, 0, 'class C1():', 'class')])
|
||||||
|
eq(cc.topvisible, 4)
|
||||||
|
eq(cc.label['text'], '\n'
|
||||||
|
'\n'
|
||||||
|
'class C1():')
|
||||||
|
|
||||||
|
# Scroll down to line 4.
|
||||||
|
cc.text.yview(4)
|
||||||
|
cc.update_code_context()
|
||||||
|
eq(cc.info, [(0, -1, '', False),
|
||||||
|
(2, 0, 'class C1():', 'class'),
|
||||||
|
(4, 4, ' def __init__(self, a, b):', 'def')])
|
||||||
|
eq(cc.topvisible, 5)
|
||||||
|
eq(cc.label['text'], '\n'
|
||||||
|
'class C1():\n'
|
||||||
|
' def __init__(self, a, b):')
|
||||||
|
|
||||||
|
# Scroll down to line 11. Last 'def' is removed.
|
||||||
|
cc.text.yview(11)
|
||||||
|
cc.update_code_context()
|
||||||
|
eq(cc.info, [(0, -1, '', False),
|
||||||
|
(2, 0, 'class C1():', 'class'),
|
||||||
|
(7, 4, ' def compare(self):', 'def'),
|
||||||
|
(8, 8, ' if a > b:', 'if'),
|
||||||
|
(10, 8, ' elif a < b:', 'elif')])
|
||||||
|
eq(cc.topvisible, 12)
|
||||||
|
eq(cc.label['text'], ' def compare(self):\n'
|
||||||
|
' if a > b:\n'
|
||||||
|
' elif a < b:')
|
||||||
|
|
||||||
|
# No scroll. No update, even though context_depth changed.
|
||||||
|
cc.update_code_context()
|
||||||
|
cc.context_depth = 1
|
||||||
|
eq(cc.info, [(0, -1, '', False),
|
||||||
|
(2, 0, 'class C1():', 'class'),
|
||||||
|
(7, 4, ' def compare(self):', 'def'),
|
||||||
|
(8, 8, ' if a > b:', 'if'),
|
||||||
|
(10, 8, ' elif a < b:', 'elif')])
|
||||||
|
eq(cc.topvisible, 12)
|
||||||
|
eq(cc.label['text'], ' def compare(self):\n'
|
||||||
|
' if a > b:\n'
|
||||||
|
' elif a < b:')
|
||||||
|
|
||||||
|
# Scroll up.
|
||||||
|
cc.text.yview(5)
|
||||||
|
cc.update_code_context()
|
||||||
|
eq(cc.info, [(0, -1, '', False),
|
||||||
|
(2, 0, 'class C1():', 'class'),
|
||||||
|
(4, 4, ' def __init__(self, a, b):', 'def')])
|
||||||
|
eq(cc.topvisible, 6)
|
||||||
|
# context_depth is 1.
|
||||||
|
eq(cc.label['text'], ' def __init__(self, a, b):')
|
||||||
|
|
||||||
|
@mock.patch.object(codecontext.CodeContext, 'update_code_context')
|
||||||
|
def test_timer_event(self, mock_update):
|
||||||
|
# Ensure code context is not active.
|
||||||
|
if self.cc.label:
|
||||||
|
self.cc.toggle_code_context_event()
|
||||||
|
self.cc.timer_event()
|
||||||
|
mock_update.assert_not_called()
|
||||||
|
|
||||||
|
# Activate code context.
|
||||||
|
self.cc.toggle_code_context_event()
|
||||||
|
self.cc.timer_event()
|
||||||
|
mock_update.assert_called()
|
||||||
|
|
||||||
|
def test_font_timer_event(self):
|
||||||
|
eq = self.assertEqual
|
||||||
|
cc = self.cc
|
||||||
|
save_font = cc.text['font']
|
||||||
|
test_font = 'FakeFont'
|
||||||
|
|
||||||
|
# Ensure code context is not active.
|
||||||
|
if cc.label:
|
||||||
|
cc.toggle_code_context_event()
|
||||||
|
|
||||||
|
# Nothing updates on inactive code context.
|
||||||
|
cc.text['font'] = test_font
|
||||||
|
cc.font_timer_event()
|
||||||
|
eq(cc.textfont, save_font)
|
||||||
|
|
||||||
|
# Activate code context, but no change to font.
|
||||||
|
cc.toggle_code_context_event()
|
||||||
|
cc.text['font'] = save_font
|
||||||
|
cc.font_timer_event()
|
||||||
|
eq(cc.textfont, save_font)
|
||||||
|
eq(cc.label['font'], save_font)
|
||||||
|
|
||||||
|
# Active code context, change font.
|
||||||
|
cc.text['font'] = test_font
|
||||||
|
cc.font_timer_event()
|
||||||
|
eq(cc.textfont, test_font)
|
||||||
|
eq(cc.label['font'], test_font)
|
||||||
|
|
||||||
|
cc.text['font'] = save_font
|
||||||
|
cc.font_timer_event()
|
||||||
|
|
||||||
|
|
||||||
|
class HelperFunctionText(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_getspacesfirstword(self):
|
||||||
|
get = codecontext.getspacesfirstword
|
||||||
|
test_lines = (
|
||||||
|
(' first word', (' ', 'first')),
|
||||||
|
('\tfirst word', ('\t', 'first')),
|
||||||
|
(' \u19D4\u19D2: ', (' ', '\u19D4\u19D2')),
|
||||||
|
('no spaces', ('', 'no')),
|
||||||
|
('', ('', '')),
|
||||||
|
('# TEST COMMENT', ('', '')),
|
||||||
|
(' (continuation)', (' ', ''))
|
||||||
|
)
|
||||||
|
for line, expected_output in test_lines:
|
||||||
|
self.assertEqual(get(line), expected_output)
|
||||||
|
|
||||||
|
# Send the pattern in the call.
|
||||||
|
self.assertEqual(get(' (continuation)',
|
||||||
|
c=re.compile(r'^(\s*)([^\s]*)')),
|
||||||
|
(' ', '(continuation)'))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main(verbosity=2)
|
|
@ -0,0 +1 @@
|
||||||
|
Add docstrings and tests for codecontext.
|
Loading…
Reference in New Issue