Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
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.
  • Loading branch information
csabella committed Jun 13, 2020
commit 8ea79ec7330eaab59aa379388c41c7db65ab0505
9 changes: 8 additions & 1 deletion Lib/idlelib/configdialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -2314,7 +2314,14 @@ def detach(self):

Shell Preferences: Auto-Squeeze Min. Lines is the minimum number of lines
of output to automatically "squeeze".
'''
''',
'Extensions': '''
ZzDummy: This extension is provided as an example for how to create and
use an extension. Enable indicates whether the extension is active or not;
likewise enable_editor and enable_shell indicate which windows it will
be active on. For this extension, z-text is the text that will be inserted
at or removed from the beginning of the lines of selected text.
''',
}


Expand Down
16 changes: 8 additions & 8 deletions Lib/idlelib/extend.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ variables:
(There are a few more, but they are rarely useful.)

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

Here is a complete example:

class ZoomHeight:
class ZzDummy:

menudefs = [
('edit', [
None, # Separator
('_Zoom Height', '<<zoom-height>>'),
])
('format', [
('Z in', '<<z-in>>'),
('Z out', '<<z-out>>'),
] )
]

def __init__(self, editwin):
self.editwin = editwin

def zoom_height_event(self, event):
def z_in_event(self, event=None):
"...Do what you want here..."

The final piece of the puzzle is the file "config-extensions.def", which is
Expand Down
152 changes: 152 additions & 0 deletions Lib/idlelib/idle_test/test_zzdummy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
"Test zzdummy, coverage 100%."

from idlelib import zzdummy
import unittest
from test.support import requires
from tkinter import Tk, Text
from unittest import mock
from idlelib import config
from idlelib import editor


usercfg = zzdummy.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
"""


class DummyEditwin:
get_region = editor.EditorWindow.get_region
set_region = editor.EditorWindow.set_region
get_selection_indices = editor.EditorWindow.get_selection_indices
def __init__(self, root, text):
self.root = root
self.top = root
self.text = text
self.text.undo_block_start = mock.Mock()
self.text.undo_block_stop = mock.Mock()


class ZZDummyTest(unittest.TestCase):

@classmethod
def setUpClass(cls):
requires('gui')
root = cls.root = Tk()
root.withdraw()
text = cls.text = Text(cls.root)
cls.editor = DummyEditwin(root, text)
zzdummy.idleConf.userCfg = testcfg

@classmethod
def tearDownClass(cls):
zzdummy.idleConf.userCfg = usercfg
del cls.editor, cls.text
cls.root.update_idletasks()
for id in cls.root.tk.call('after', 'info'):
cls.root.after_cancel(id) # Need for EditorWindow.
cls.root.destroy()
del cls.root

def setUp(self):
text = self.text
text.insert('1.0', code_sample)
text.undo_block_start.reset_mock()
text.undo_block_stop.reset_mock()
zz = self.zz = zzdummy.ZzDummy(self.editor)
zzdummy.ZzDummy.ztext = '# ignore #'

def tearDown(self):
self.text.delete('1.0', 'end')
del self.zz

def checklines(self, text, value):
# Verify that there are lines being checked.
end_line = int(float(text.index('end')))

# Check each line for the starting text.
actual = []
for line in range(1, end_line):
txt = text.get(f'{line}.0', f'{line}.end')
actual.append(txt.startswith(value))
return actual

def test_init(self):
zz = self.zz
self.assertEqual(zz.editwin, self.editor)
self.assertEqual(zz.text, self.editor.text)

def test_reload(self):
self.assertEqual(self.zz.ztext, '# ignore #')
testcfg['extensions'].SetOption('ZzDummy', 'z-text', 'spam')
zzdummy.ZzDummy.reload()
self.assertEqual(self.zz.ztext, 'spam')

def test_z_in_event(self):
eq = self.assertEqual
zz = self.zz
text = zz.text
eq(self.zz.ztext, '# ignore #')

# No lines have the leading text.
expected = [False, False, False, False, False, False, False]
actual = self.checklines(text, zz.ztext)
eq(expected, actual)

text.tag_add('sel', '2.0', '4.end')
eq(zz.z_in_event(), 'break')
expected = [False, True, True, True, False, False, False]
actual = self.checklines(text, zz.ztext)
eq(expected, actual)

text.undo_block_start.assert_called_once()
text.undo_block_stop.assert_called_once()

def test_z_out_event(self):
eq = self.assertEqual
zz = self.zz
text = zz.text
eq(self.zz.ztext, '# ignore #')

# Prepend text.
text.tag_add('sel', '2.0', '5.end')
zz.z_in_event()
text.undo_block_start.reset_mock()
text.undo_block_stop.reset_mock()

# Select a few lines to remove text.
text.tag_remove('sel', '1.0', 'end')
text.tag_add('sel', '3.0', '4.end')
eq(zz.z_out_event(), 'break')
expected = [False, True, False, False, True, False, False]
actual = self.checklines(text, zz.ztext)
eq(expected, actual)

text.undo_block_start.assert_called_once()
text.undo_block_stop.assert_called_once()

def test_roundtrip(self):
# Insert and remove to all code should give back original text.
zz = self.zz
text = zz.text

text.tag_add('sel', '1.0', 'end-1c')
zz.z_in_event()
zz.z_out_event()

self.assertEqual(text.get('1.0', 'end-1c'), code_sample)


if __name__ == '__main__':
unittest.main(verbosity=2)
71 changes: 50 additions & 21 deletions Lib/idlelib/zzdummy.py
Original file line number Diff line number Diff line change
@@ -1,42 +1,71 @@
"Example extension, also used for testing."
"""Example extension, also used for testing.

See extend.txt for more details on creating an extension.
"""

from idlelib.config import idleConf
from functools import wraps


def format_selection(format_line):
"Apply a formatting function to all of the selected lines."

@wraps(format_line)
def apply(self, event=None):
head, tail, chars, lines = self.editwin.get_region()
for pos in range(len(lines) - 1):
line = lines[pos]
lines[pos] = format_line(self, line)
self.editwin.set_region(head, tail, chars, lines)
return 'break'

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


class ZzDummy:
"""Prepend or remove initial text from selected lines."""

## menudefs = [
## ('format', [
## ('Z in', '<<z-in>>'),
## ('Z out', '<<z-out>>'),
## ] )
## ]
# Extend the format menu.
menudefs = [
('format', [
('Z in', '<<z-in>>'),
('Z out', '<<z-out>>'),
] )
]

def __init__(self, editwin):
"Initialize the settings for this extension."
self.editwin = editwin
self.text = editwin.text
z_in = False

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

def z_in_event(self, event):
@format_selection
def z_in_event(self, line):
"""Insert text at the beginning of each selected line.

This is bound to the <<z-in>> virtual event when the extensions
are loaded.
"""
return f'{self.ztext}{line}'

@format_selection
def z_out_event(self, line):
"""Remove specific text from the beginning of each selected line.

This is bound to the <<z-out>> virtual event when the extensions
are loaded.
"""
text = self.text
text.undo_block_start()
for line in range(1, text.index('end')):
text.insert('%d.0', ztext)
text.undo_block_stop()
return "break"
zlength = 0 if not line.startswith(self.ztext) else len(self.ztext)
return line[zlength:]

def z_out_event(self, event): pass

ZzDummy.reload()

##if __name__ == "__main__":
## import unittest
## unittest.main('idlelib.idle_test.test_zzdummy',
## verbosity=2, exit=False)

if __name__ == "__main__":
import unittest
unittest.main('idlelib.idle_test.test_zzdummy', verbosity=2, exit=False)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add tests and docstrings for the zzdummy extension module.