8000 bpo-32631: IDLE: Enable zzdummy extension module · python/cpython@8ea79ec · GitHub
[go: up one dir, main page]

Skip to content

Commit 8ea79ec

Browse files
committed
bpo-32631: IDLE: Enable zzdummy extension module
* Complete functionality to add or remove prefix string to each line. * Add comments and docstrings to zzdummy. * Add unittests in test_zzdummy. * Add help section for extensions tab to configdialog.
1 parent 29c1172 commit 8ea79ec

File tree

5 files changed

+219
-30
lines changed

5 files changed

+219
-30
lines changed

Lib/idlelib/configdialog.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2314,7 +2314,14 @@ def detach(self):
23142314
23152315
Shell Preferences: Auto-Squeeze Min. Lines is the minimum number of lines
23162316
of output to automatically "squeeze".
2317-
'''
2317+
''',
2318+
'Extensions': '''
2319+
ZzDummy: This extension is provided as an example for how to create and
2320+
use an extension. Enable indicates whether the extension is active or not;
2321+
likewise enable_editor and enable_shell indicate which windows it will
2322+
be active on. For this extension, z-text is the text that will be inserted
2323+
at or removed from the beginning of the lines of selected text.
2324+
''',
23182325
}
23192326

23202327

Lib/idlelib/extend.txt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ variables:
2828
(There are a few more, but they are rarely useful.)
2929

3030
The extension class must not directly bind Window Manager (e.g. X) events.
31-
Rather, it must define one or more virtual events, e.g. <<zoom-height>>, and
32-
corresponding methods, e.g. zoom_height_event(). The virtual events will be
31+
Rather, it must define one or more virtual events, e.g. <<z-in>>, and
32+
corresponding methods, e.g. z_in_event(). The virtual events will be
3333
bound to the corresponding methods, and Window Manager events can then be bound
3434
to the virtual events. (This indirection is done so that the key bindings can
3535
easily be changed, and so that other sources of virtual events can exist, such
@@ -56,19 +56,19 @@ case there must be empty bindings in cofig-extensions.def)
5656

5757
Here is a complete example:
5858

59-
class ZoomHeight:
59+
class ZzDummy:
6060

6161
menudefs = [
62-
('edit', [
63-
None, # Separator
< 8000 /td>
64-
('_Zoom Height', '<<zoom-height>>'),
65-
])
62+
('format', [
63+
('Z in', '<<z-in>>'),
64+
('Z out', '<<z-out>>'),
65+
] )
6666
]
6767

6868
def __init__(self, editwin):
6969
self.editwin = editwin
7070

71-
def zoom_height_event(self, event):
71+
def z_in_event(self, event=None):
7272
"...Do what you want here..."
7373

7474
The final piece of the puzzle is the file "config-extensions.def", which is

Lib/idlelib/idle_test/test_zzdummy.py

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
"Test zzdummy, coverage 100%."
2+
3+
from idlelib import zzdummy
4+
import unittest
5+
from test.support import requires
6+
from tkinter import Tk, Text
7+
from unittest import mock
8+
from idlelib import config
9+
from idlelib import editor
10+
11+
12+
usercfg = zzdummy.idleConf.userCfg
13+
testcfg = {
14+
'main': config.IdleUserConfParser(''),
15+
'highlight': config.IdleUserConfParser(''),
16+
'keys': config.IdleUserConfParser(''),
17+
'extensions': config.IdleUserConfParser(''),
18+
}
19+
code_sample = """\
20+
21+
class C1():
22+
# Class comment.
23+
def __init__(self, a, b):
24+
self.a = a
25+
self.b = b
26+
"""
27+
28+
29+
class DummyEditwin:
30+
get_region = editor.EditorWindow.get_region
31+
set_region = editor.EditorWindow.set_region
32+
get_selection_indices = editor.EditorWindow.get_selection_indices
33+
def __init__(self, root, text):
34+
self.root = root
35+
self.top = root
36+
self.text = text
37+
self.text.undo_block_start = mock.Mock()
38+
self.text.undo_block_stop = mock.Mock()
39+
40+
41+
class ZZDummyTest(unittest.TestCase):
42+
43+
@classmethod
44+
def setUpClass(cls):
45+
requires('gui')
46+
root = cls.root = Tk()
47+
root.withdraw()
48+
text = cls.text = Text(cls.root)
49+
cls.editor = DummyEditwin(root, text)
50+
zzdummy.idleConf.userCfg = testcfg
51+
52+
@classmethod
53+
def tearDownClass(cls):
54+
zzdummy.idleConf.userCfg = usercfg
55+
del cls.editor, cls.text
56+
cls.root.update_idletasks()
57+
for id in cls.root.tk.call('after', 'info'):
58+
cls.root.after_cancel(id) # Need for EditorWindow.
59+
cls.root.destroy()
60+
del cls.root
61+
62+
def setUp(self):
63+
text = self.text
64+
text.insert('1.0', code_sample)
65+
text.undo_block_start.reset_mock()
66+
text.undo_block_stop.reset_mock()
67+
zz = self.zz = zzdummy.ZzDummy(self.editor)
68+
zzdummy.ZzDummy.ztext = '# ignore #'
69+
70+
def tearDown(self):
71+
self.text.delete('1.0', 'end')
72+
del self.zz
73+
74+
def checklines(self, text, value):
75+
# Verify that there are lines being checked.
76+
end_line = int(float(text.index('end')))
77+
78+
# Check each line for the starting text.
79+
actual = []
80+
for line in range(1, end_line):
81+
txt = text.get(f'{line}.0', f'{line}.end')
82+
actual.append(txt.startswith(value))
83+
return actual
84+
85+
def test_init(self):
86+
zz = self.zz
87+
self.assertEqual(zz.editwin, self.editor)
88+
self.assertEqual(zz.text, self.editor.text)
89+
90+
def test_reload(self):
91+
self.assertEqual(self.zz.ztext, '# ignore #')
92+
testcfg['extensions'].SetOption('ZzDummy', 'z-text', 'spam')
93+
zzdummy.ZzDummy.reload()
94+
self.assertEqual(self.zz.ztext, 'spam')
95+
96+
def test_z_in_event(self):
97+
eq = self.assertEqual
98+
zz = self.zz
99+
text = zz.text
100+
eq(self.zz.ztext, '# ignore #')
101+
102+
# No lines have the leading text.
103+
expected = [False, False, False, False, False, False, False]
104+
actual = self.checklines(text, zz.ztext)
105+
eq(expected, actual)
106+
107+
text.tag_add('sel', '2.0', '4.end')
108+
eq(zz.z_in_event(), 'break')
109+
expected = [False, True, True, True, False, False, False]
110+
actual = self.checklines(text, zz.ztext)
111+
eq(expected, actual)
112+
113+
text.undo_block_start.assert_called_once()
114+
text.undo_block_stop.assert_called_once()
115+
116+
def test_z_out_event(self):
117+
eq = self.assertEqual
118+
zz = self.zz
119+
text = zz.text
120+
eq(self.zz.ztext, '# ignore #')
121+
122+
# Prepend text.
123+
text.tag_add('sel', '2.0', '5.end')
124+
zz.z_in_event()
125+
text.undo_block_start.reset_mock()
126+
text.undo_block_stop.reset_mock()
127+
128+
# Select a few lines to remove text.
129+
text.tag_remove('sel', '1.0', 'end')
130+
text.tag_add('sel', '3.0', '4.end')
131+
eq(zz.z_out_event(), 'break')
132+
expected = [False, True, False, False, True, False, False]
133+
actual = self.checklines(text, zz.ztext)
134+
eq(expected, actual)
135+
136+
text.undo_block_start.assert_called_once()
137+
text.undo_block_stop.assert_called_once()
138+
139+
def test_roundtrip(self):
140+
# Insert and remove to all code should give back original text.
141+
zz = self.zz
142+
text = zz.text
143+
144+
text.tag_add('sel', '1.0', 'end-1c')
145+
zz.z_in_event()
146+
zz.z_out_event()
147+
148+
self.assertEqual(text.get('1.0', 'end-1c'), code_sample)
149+
150+
151+
if __name__ == '__main__':
152+
unittest.main(verbosity=2)

Lib/idlelib/zzdummy.py

Lines changed: 50 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,71 @@
1-
"Example extension, also used for testing."
1+
"""Example extension, also used for testing.
2+
3+
See extend.txt for more details on creating an extension.
4+
"""
25

36
from idlelib.config import idleConf
7+
from functools import wraps
8+
9+
10+
def format_selection(format_line):
11+
"Apply a formatting function to all of the selected lines."
12+
13+
@wraps(format_line)
14+
def apply(self, event=None):
15+
head, tail, chars, lines = self.editwin.get_region()
16+
for pos in range(len(lines) - 1):
17+
line = lines[pos]
18+
lines[pos] = format_line(self, line)
19+
self.editwin.set_region(head, tail, chars, lines)
20+
return 'break'
421

5-
ztext = idleConf.GetOption('extensions', 'ZzDummy', 'z-text')
22+
return apply
623

724

825
class ZzDummy:
26+
"""Prepend or remove initial text from selected lines."""
927

10-
## menudefs = [
11-
## ('format', [
12-
## ('Z in', '<<z-in>>'),
13-
## ('Z out', '<<z-out>>'),
14-
## ] )
15-
## ]
28+
# Extend the format menu.
29+
menudefs = [
30+
('format', [
31+
('Z in', '<<z-in>>'),
32+
('Z out', '<<z-out>>'),
33+
] )
34+
]
1635

1736
def __init__(self, editwin):
37+
"Initialize the settings for this extension."
38+
self.editwin = editwin
1839
self.text = editwin.text
19-
z_in = False
2040

2141
@classmethod
2242
def reload(cls):
43+
"Load class variables from config."
2344
cls.ztext = idleConf.GetOption('extensions', 'ZzDummy', 'z-text')
2445

25-
def z_in_event(self, event):
46+
@format_selection
47+
def z_in_event(self, line):
48+
"""Insert text at the beginning of each selected line.
49+
50+
This is bound to the <<z-in>> virtual event when the extensions
51+
are loaded.
2652
"""
53+
return f'{self.ztext}{line}'
54+
55+
@format_selection
56+
def z_out_event(self, line):
57+
"""Remove specific text from the beginning of each selected line.
58+
59+
This is bound to the <<z-out>> virtual event when the extensions
60+
are loaded.
2761
"""
28-
text = self.text
29-
text.undo_block_start()
30-
for line in range(1, text.index('end')):
31-
text.insert('%d.0', ztext)
32-
text.undo_block_stop()
33-
return "break"
62+
zlength = 0 if not line.startswith(self.ztext) else len(self.ztext)
63+
return line[zlength:]
3464

35-
def z_out_event(self, event): pass
3665

3766
ZzDummy.reload()
3867

39-
##if __name__ == "__main__":
40-
## import unittest
41-
## unittest.main('idlelib.idle_test.test_zzdummy',
42-
## verbosity=2, exit=False)
68+
69+
if __name__ == "__main__":
70+
import unittest
71+
unittest.main('idlelib.idle_test.test_zzdummy', verbosity=2, exit=False)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add tests and docstrings for the zzdummy extension module.

0 commit comments

Comments
 (0)
0