diff options
-rw-r--r-- | Lib/idlelib/configdialog.py | 15 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_parenmatch.py | 68 | ||||
-rw-r--r-- | Lib/idlelib/parenmatch.py | 80 | ||||
-rw-r--r-- | Misc/ACKS | 1 | ||||
-rw-r--r-- | Misc/NEWS.d/next/IDLE/2017-06-27-19-05-40.bpo-30723.rQh06y.rst | 5 |
5 files changed, 97 insertions, 72 deletions
diff --git a/Lib/idlelib/configdialog.py b/Lib/idlelib/configdialog.py index 96682a9..4a25846 100644 --- a/Lib/idlelib/configdialog.py +++ b/Lib/idlelib/configdialog.py @@ -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). +''' } diff --git a/Lib/idlelib/idle_test/test_parenmatch.py b/Lib/idlelib/idle_test/test_parenmatch.py index cbec350..6943a70 100644 --- a/Lib/idlelib/idle_test/test_parenmatch.py +++ b/Lib/idlelib/idle_test/test_parenmatch.py @@ -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): """ diff --git a/Lib/idlelib/parenmatch.py b/Lib/idlelib/parenmatch.py index dcec34c..c15cb81 100644 --- a/Lib/idlelib/parenmatch.py +++ b/Lib/idlelib/parenmatch.py @@ -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 @@ -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 index 0000000..ceb42a2 --- /dev/null +++ b/Misc/NEWS.d/next/IDLE/2017-06-27-19-05-40.bpo-30723.rQh06y.rst @@ -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. |