bpo-32631: IDLE: Enable zzdummy example extension module (GH-14491)

Make menu items work with formatter, add docstrings, add 100% tests.

Co-authored-by: Terry Jan Reedy <tjreedy@udel.edu>
diff --git a/Lib/idlelib/zzdummy.py b/Lib/idlelib/zzdummy.py
index 3c4b1d2..1247e8f 100644
--- a/Lib/idlelib/zzdummy.py
+++ b/Lib/idlelib/zzdummy.py
@@ -1,42 +1,73 @@
-"Example extension, also used for testing."
+"""Example extension, also used for testing.
+
+See extend.txt for more details on creating an extension.
+See config-extension.def for configuring an extension.
+"""
 
 from idlelib.config import idleConf
+from functools import wraps
 
-ztext = idleConf.GetOption('extensions', 'ZzDummy', 'z-text')
+
+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.formatter.get_region()
+        for pos in range(len(lines) - 1):
+            line = lines[pos]
+            lines[pos] = format_line(self, line)
+        self.formatter.set_region(head, tail, chars, lines)
+        return 'break'
+
+    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
+        self.formatter = editwin.fregion
 
     @classmethod
     def reload(cls):
+        "Load class variables from config."
         cls.ztext = idleConf.GetOption('extensions', 'ZzDummy', 'z-text')
 
-    def z_in_event(self, event):
-        """
-        """
-        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"
+    @format_selection
+    def z_in_event(self, line):
+        """Insert text at the beginning of each selected line.
 
-    def z_out_event(self, event): pass
+        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.
+        """
+        zlength = 0 if not line.startswith(self.ztext) else len(self.ztext)
+        return line[zlength:]
+
 
 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)