]> granicus.if.org Git - python/commitdiff
bpo-30723: IDLE -- Enhance parenmatch; add style, flash, and help (#2306)
authorwohlganger <charles.wohlganger@gmail.com>
Wed, 28 Jun 2017 02:36:23 +0000 (21:36 -0500)
committerterryjreedy <tjreedy@udel.edu>
Wed, 28 Jun 2017 02:36:23 +0000 (22:36 -0400)
* Add 'parens' style to highlight both opener and closer.
* Make 'default' style, which is not default, a synonym for 'opener'.
* Make time-delay work the same with all styles.
* Add help for config dialog extensions tab, including parenmatch.
* Add new tests.
Original patch by Charles Wohlganger.

Lib/idlelib/configdialog.py
Lib/idlelib/idle_test/test_parenmatch.py
Lib/idlelib/parenmatch.py
Misc/ACKS
Misc/NEWS.d/next/IDLE/2017-06-27-19-05-40.bpo-30723.rQh06y.rst [new file with mode: 0644]

index 96682a922147690bc89d8a2c94f64a053cc27725..4a25846238f6e1a30ca9529d9a8a8030a24ee8aa 100644 (file)
@@ -1407,6 +1407,21 @@ The IDLE Modern Unix key set is new in June 2016.  It can only
 be used with older IDLE releases if it is saved as a custom
 key set, with a different name.
 ''',
+    'Extensions': '''
+Extensions:
+
+Autocomplete: Popupwait is milleseconds to wait after key char, without
+cursor movement, before popping up completion box.  Key char is '.' after
+identifier or a '/' (or '\\' on Windows) within a string.
+
+FormatParagraph: Max-width is max chars in lines after re-formatting.
+Use with paragraphs in both strings and comment blocks.
+
+ParenMatch: Style indicates what is highlighted when closer is entered:
+'opener' - opener '({[' corresponding to closer; 'parens' - both chars;
+'expression' (default) - also everything in between.  Flash-delay is how
+long to highlight if cursor is not moved (0 means forever).
+'''
 }
 
 
index cbec35018b990f85bce93581c3b690ce1faf62bf..6943a70c997c1c7a2c4a6b5a2ceace3de099b81a 100644 (file)
@@ -3,13 +3,14 @@
 This must currently be a gui test because ParenMatch methods use
 several text methods not defined on idlelib.idle_test.mock_tk.Text.
 '''
+from idlelib.parenmatch import ParenMatch
 from test.support import requires
 requires('gui')
 
 import unittest
 from unittest.mock import Mock
 from tkinter import Tk, Text
-from idlelib.parenmatch import ParenMatch
+
 
 class DummyEditwin:
     def __init__(self, text):
@@ -44,46 +45,39 @@ class ParenMatchTest(unittest.TestCase):
         pm.bell = lambda: None
         return pm
 
-    def test_paren_expression(self):
+    def test_paren_styles(self):
         """
-        Test ParenMatch with 'expression' style.
+        Test ParenMatch with each style.
         """
         text = self.text
         pm = self.get_parenmatch()
-        pm.set_style('expression')
-
-        text.insert('insert', 'def foobar(a, b')
-        pm.flash_paren_event('event')
-        self.assertIn('<<parenmatch-check-restore>>', text.event_info())
-        self.assertTupleEqual(text.tag_prevrange('paren', 'end'),
-                             ('1.10', '1.15'))
-        text.insert('insert', ')')
-        pm.restore_event()
-        self.assertNotIn('<<parenmatch-check-restore>>', text.event_info())
-        self.assertEqual(text.tag_prevrange('paren', 'end'), ())
-
-        # paren_closed_event can only be tested as below
-        pm.paren_closed_event('event')
-        self.assertTupleEqual(text.tag_prevrange('paren', 'end'),
-                                                ('1.10', '1.16'))
-
-    def test_paren_default(self):
-        """
-        Test ParenMatch with 'default' style.
-        """
-        text = self.text
-        pm = self.get_parenmatch()
-        pm.set_style('default')
-
-        text.insert('insert', 'def foobar(a, b')
-        pm.flash_paren_event('event')
-        self.assertIn('<<parenmatch-check-restore>>', text.event_info())
-        self.assertTupleEqual(text.tag_prevrange('paren', 'end'),
-                             ('1.10', '1.11'))
-        text.insert('insert', ')')
-        pm.restore_event()
-        self.assertNotIn('<<parenmatch-check-restore>>', text.event_info())
-        self.assertEqual(text.tag_prevrange('paren', 'end'), ())
+        for style, range1, range2 in (
+                ('opener', ('1.10', '1.11'), ('1.10', '1.11')),
+                ('default',('1.10', '1.11'),('1.10', '1.11')),
+                ('parens', ('1.14', '1.15'), ('1.15', '1.16')),
+                ('expression', ('1.10', '1.15'), ('1.10', '1.16'))):
+            with self.subTest(style=style):
+                text.delete('1.0', 'end')
+                pm.set_style(style)
+                text.insert('insert', 'def foobar(a, b')
+
+                pm.flash_paren_event('event')
+                self.assertIn('<<parenmatch-check-restore>>', text.event_info())
+                if style == 'parens':
+                    self.assertTupleEqual(text.tag_nextrange('paren', '1.0'),
+                                          ('1.10', '1.11'))
+                self.assertTupleEqual(
+                        text.tag_prevrange('paren', 'end'), range1)
+
+                text.insert('insert', ')')
+                pm.restore_event()
+                self.assertNotIn('<<parenmatch-check-restore>>',
+                                 text.event_info())
+                self.assertEqual(text.tag_prevrange('paren', 'end'), ())
+
+                pm.paren_closed_event('event')
+                self.assertTupleEqual(
+                        text.tag_prevrange('paren', 'end'), range2)
 
     def test_paren_corner(self):
         """
index dcec34c18f92875ebaddfd37639d3a242117e690..c15cb818cd8fbd0d4cea0007e96526bba5fbc6b5 100644 (file)
@@ -11,43 +11,37 @@ _openers = {')':'(',']':'[','}':'{'}
 CHECK_DELAY = 100 # milliseconds
 
 class ParenMatch:
-    """Highlight matching parentheses
+    """Highlight matching openers and closers, (), [], and {}.
 
-    There are three supported style of paren matching, based loosely
-    on the Emacs options.  The style is select based on the
-    HILITE_STYLE attribute; it can be changed used the set_style
-    method.
+    There are three supported styles of paren matching.  When a right
+    paren (opener) is typed:
 
-    The supported styles are:
+    opener -- highlight the matching left paren (closer);
+    parens -- highlight the left and right parens (opener and closer);
+    expression -- highlight the entire expression from opener to closer.
+    (For back compatibility, 'default' is a synonym for 'opener').
 
-    default -- When a right paren is typed, highlight the matching
-        left paren for 1/2 sec.
-
-    expression -- When a right paren is typed, highlight the entire
-        expression from the left paren to the right paren.
+    Flash-delay is the maximum milliseconds the highlighting remains.
+    Any cursor movement (key press or click) before that removes the
+    highlight.  If flash-delay is 0, there is no maximum.
 
     TODO:
-        - extend IDLE with configuration dialog to change options
-        - implement rest of Emacs highlight styles (see below)
-        - print mismatch warning in IDLE status window
-
-    Note: In Emacs, there are several styles of highlight where the
-    matching paren is highlighted whenever the cursor is immediately
-    to the right of a right paren.  I don't know how to do that in Tk,
-    so I haven't bothered.
+    - Augment bell() with mismatch warning in status window.
+    - Highlight when cursor is moved to the right of a closer.
+      This might be too expensive to check.
     """
     menudefs = [
         ('edit', [
             ("Show surrounding parens", "<<flash-paren>>"),
         ])
     ]
-    STYLE = idleConf.GetOption('extensions','ParenMatch','style',
-            default='expression')
-    FLASH_DELAY = idleConf.GetOption('extensions','ParenMatch','flash-delay',
-            type='int',default=500)
+    STYLE = idleConf.GetOption(
+            'extensions','ParenMatch','style', default='expression')
+    FLASH_DELAY = idleConf.GetOption(
+            'extensions','ParenMatch','flash-delay', type='int',default=500)
+    BELL = idleConf.GetOption(
+            'extensions','ParenMatch','bell', type='bool',default=1)
     HILITE_CONFIG = idleConf.GetHighlight(idleConf.CurrentTheme(),'hilite')
-    BELL = idleConf.GetOption('extensions','ParenMatch','bell',
-            type='bool',default=1)
 
     RESTORE_VIRTUAL_EVENT_NAME = "<<parenmatch-check-restore>>"
     # We want the restore event be called before the usual return and
@@ -69,27 +63,32 @@ class ParenMatch:
         self.set_style(self.STYLE)
 
     def activate_restore(self):
+        "Activate mechanism to restore text from highlighting."
         if not self.is_restore_active:
             for seq in self.RESTORE_SEQUENCES:
                 self.text.event_add(self.RESTORE_VIRTUAL_EVENT_NAME, seq)
             self.is_restore_active = True
 
     def deactivate_restore(self):
+        "Remove restore event bindings."
         if self.is_restore_active:
             for seq in self.RESTORE_SEQUENCES:
                 self.text.event_delete(self.RESTORE_VIRTUAL_EVENT_NAME, seq)
             self.is_restore_active = False
 
     def set_style(self, style):
+        "Set tag and timeout functions."
         self.STYLE = style
-        if style == "default":
-            self.create_tag = self.create_tag_default
-            self.set_timeout = self.set_timeout_last
-        elif style == "expression":
-            self.create_tag = self.create_tag_expression
-            self.set_timeout = self.set_timeout_none
+        self.create_tag = (
+                self.create_tag_opener if style in {"opener", "default"} else
+                self.create_tag_parens if style == "parens" else
+                self.create_tag_expression)  # "expression" or unknown
+
+        self.set_timeout = (self.set_timeout_last if self.FLASH_DELAY else
+                            self.set_timeout_none)
 
     def flash_paren_event(self, event):
+        "Handle editor 'show surrounding parens' event (menu or shortcut)."
         indices = (HyperParser(self.editwin, "insert")
                    .get_surrounding_brackets())
         if indices is None:
@@ -97,11 +96,12 @@ class ParenMatch:
             return "break"
         self.activate_restore()
         self.create_tag(indices)
-        self.set_timeout_last()
+        self.set_timeout()
         return "break"
 
     def paren_closed_event(self, event):
-        # If it was a shortcut and not really a closing paren, quit.
+        "Handle user input of closer."
+        # If user bound non-closer to <<paren-closed>>, quit.
         closer = self.text.get("insert-1c")
         if closer not in _openers:
             return "break"
@@ -118,6 +118,7 @@ class ParenMatch:
         return "break"
 
     def restore_event(self, event=None):
+        "Remove effect of doing match."
         self.text.tag_delete("paren")
         self.deactivate_restore()
         self.counter += 1   # disable the last timer, if there is one.
@@ -129,11 +130,20 @@ class ParenMatch:
     # any one of the create_tag_XXX methods can be used depending on
     # the style
 
-    def create_tag_default(self, indices):
+    def create_tag_opener(self, indices):
         """Highlight the single paren that matches"""
         self.text.tag_add("paren", indices[0])
         self.text.tag_config("paren", self.HILITE_CONFIG)
 
+    def create_tag_parens(self, indices):
+        """Highlight the left and right parens"""
+        if self.text.get(indices[1]) in (')', ']', '}'):
+            rightindex = indices[1]+"+1c"
+        else:
+            rightindex = indices[1]
+        self.text.tag_add("paren", indices[0], indices[0]+"+1c", rightindex+"-1c", rightindex)
+        self.text.tag_config("paren", self.HILITE_CONFIG)
+
     def create_tag_expression(self, indices):
         """Highlight the entire expression"""
         if self.text.get(indices[1]) in (')', ']', '}'):
@@ -162,7 +172,7 @@ class ParenMatch:
         self.editwin.text_frame.after(CHECK_DELAY, callme, callme)
 
     def set_timeout_last(self):
-        """The last highlight created will be removed after .5 sec"""
+        """The last highlight created will be removed after FLASH_DELAY millisecs"""
         # associate a counter with an event; only disable the "paren"
         # tag if the event is for the most recent timer.
         self.counter += 1
index a6d1e0ffaf4a103c5d72a3f95e0ca7583468d745..910f8196a764afed3fd1b572530ca560c43e62c5 100644 (file)
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -1713,6 +1713,7 @@ John Wiseman
 Chris Withers
 Stefan Witzel
 Irek Wlizlo
+Charles Wohlganger
 David Wolever
 Klaus-Juergen Wolf
 Dan Wolfe
diff --git a/Misc/NEWS.d/next/IDLE/2017-06-27-19-05-40.bpo-30723.rQh06y.rst b/Misc/NEWS.d/next/IDLE/2017-06-27-19-05-40.bpo-30723.rQh06y.rst
new file mode 100644 (file)
index 0000000..ceb42a2
--- /dev/null
@@ -0,0 +1,5 @@
+IDLE: Make several improvements to parenmatch. Add 'parens' style to
+highlight both opener and closer. Make 'default' style, which is not
+default, a synonym for 'opener'. Make time-delay work the same with all
+styles. Add help for config dialog extensions tab, including help for
+parenmatch. Add new tests.  Original patch by Charles Wohlganger.