From 1700630f1535b1557530568fd818a18aa539c615 Mon Sep 17 00:00:00 2001 From: Charles Wohlganger Date: Tue, 20 Jun 2017 16:40:19 -0500 Subject: [PATCH 1/7] trivial - made FLASH_DELAY configure for all styles. added highlight both parens option. --- Lib/idlelib/parenmatch.py | 31 +++++++++++++++++++++++++------ Misc/ACKS | 1 + 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/Lib/idlelib/parenmatch.py b/Lib/idlelib/parenmatch.py index ccec708f31f436..74edeb239d8d64 100644 --- a/Lib/idlelib/parenmatch.py +++ b/Lib/idlelib/parenmatch.py @@ -13,19 +13,26 @@ class ParenMatch: """Highlight matching parentheses - There are three supported style of paren matching, based loosely + There are three supported styles 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 + HILITE_STYLE attribute; it can be changed using the set_style method. The supported styles are: default -- When a right paren is typed, highlight the matching - left paren for 1/2 sec. + left paren. expression -- When a right paren is typed, highlight the entire expression from the left paren to the right paren. + parens -- When a right paren is typed, highlight the left and + right parens. + + + flash-delay option determines how long (milliseconds) the highlighting + remains. If set to 0, it does not timeout. + TODO: - extend IDLE with configuration dialog to change options - implement rest of Emacs highlight styles (see below) @@ -84,9 +91,13 @@ def set_style(self, style): 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 + elif style == "parens": + self.create_tag = self.create_tag_parens + if self.FLASH_DELAY: + self.set_timeout = self.set_timeout_last + else: self.set_timeout = self.set_timeout_none def flash_paren_event(self, event): @@ -97,7 +108,7 @@ def flash_paren_event(self, event): return self.activate_restore() self.create_tag(indices) - self.set_timeout_last() + self.set_timeout() def paren_closed_event(self, event): # If it was a shortcut and not really a closing paren, quit. @@ -141,6 +152,14 @@ def create_tag_expression(self, indices): self.text.tag_add("paren", indices[0], rightindex) 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) # any one of the set_timeout_XXX methods can be used depending on # the style @@ -160,7 +179,7 @@ def callme(callme, self=self, c=self.counter, 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 diff --git a/Misc/ACKS b/Misc/ACKS index 4f98e980bd971f..170a2de81a634a 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -1710,6 +1710,7 @@ John Wiseman Chris Withers Stefan Witzel Irek Wlizlo +Charles Wohlganger David Wolever Klaus-Juergen Wolf Dan Wolfe From de706a4afb307f20a3cb7ec9439a5868d98f2011 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Tue, 27 Jun 2017 16:41:25 -0400 Subject: [PATCH 2/7] Edit parenmatch; make 'default' synonym of 'opener' --- Lib/idlelib/idle_test/test_parenmatch.py | 11 +-- Lib/idlelib/parenmatch.py | 92 +++++++++++------------- 2 files changed, 47 insertions(+), 56 deletions(-) diff --git a/Lib/idlelib/idle_test/test_parenmatch.py b/Lib/idlelib/idle_test/test_parenmatch.py index cbec35018b990f..46dfec7aed084d 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): @@ -46,7 +47,7 @@ def get_parenmatch(self): def test_paren_expression(self): """ - Test ParenMatch with 'expression' style. + Test ParenMatch with 'expression' (or unknown) style. """ text = self.text pm = self.get_parenmatch() @@ -67,13 +68,13 @@ def test_paren_expression(self): self.assertTupleEqual(text.tag_prevrange('paren', 'end'), ('1.10', '1.16')) - def test_paren_default(self): + def test_paren_opener(self): """ - Test ParenMatch with 'default' style. + Test ParenMatch with 'opener' (or 'default') style. """ text = self.text pm = self.get_parenmatch() - pm.set_style('default') + pm.set_style('opener') text.insert('insert', 'def foobar(a, b') pm.flash_paren_event('event') diff --git a/Lib/idlelib/parenmatch.py b/Lib/idlelib/parenmatch.py index 728be3ab79ce69..c15cb818cd8fbd 100644 --- a/Lib/idlelib/parenmatch.py +++ b/Lib/idlelib/parenmatch.py @@ -11,50 +11,37 @@ CHECK_DELAY = 100 # milliseconds class ParenMatch: - """Highlight matching parentheses + """Highlight matching openers and closers, (), [], and {}. - There are three supported styles of paren matching, based loosely - on the Emacs options. The style is select based on the - HILITE_STYLE attribute; it can be changed using 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. - - expression -- When a right paren is typed, highlight the entire - expression from the left paren to the right paren. - - parens -- When a right paren is typed, highlight the left and - right parens. - - - flash-delay option determines how long (milliseconds) the highlighting - remains. If set to 0, it does not timeout. + 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", "<>"), ]) ] - 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 = "<>" # We want the restore event be called before the usual return and @@ -76,31 +63,32 @@ def __init__(self, editwin): 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 - elif style == "expression": - self.create_tag = self.create_tag_expression - elif style == "parens": - self.create_tag = self.create_tag_parens - if self.FLASH_DELAY: - self.set_timeout = self.set_timeout_last - else: - 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: @@ -111,9 +99,9 @@ def flash_paren_event(self, event): 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 <>, quit. closer = self.text.get("insert-1c") if closer not in _openers: return "break" @@ -130,6 +118,7 @@ def paren_closed_event(self, event): 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. @@ -141,28 +130,29 @@ def handle_restore_timer(self, timer_count): # 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_expression(self, indices): - """Highlight the entire expression""" + 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], rightindex) + self.text.tag_add("paren", indices[0], indices[0]+"+1c", rightindex+"-1c", rightindex) self.text.tag_config("paren", self.HILITE_CONFIG) - def create_tag_parens(self, indices): - """Highlight the left and right parens""" + def create_tag_expression(self, indices): + """Highlight the entire expression""" 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_add("paren", indices[0], rightindex) self.text.tag_config("paren", self.HILITE_CONFIG) + # any one of the set_timeout_XXX methods can be used depending on # the style From 280790a658a1bcd32709afb55a54229c01fc98bb Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Tue, 27 Jun 2017 18:05:03 -0400 Subject: [PATCH 3/7] Add help for config dialog extensions tab --- Lib/idlelib/configdialog.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Lib/idlelib/configdialog.py b/Lib/idlelib/configdialog.py index 96682a92214769..4a25846238f6e1 100644 --- a/Lib/idlelib/configdialog.py +++ b/Lib/idlelib/configdialog.py @@ -1407,6 +1407,21 @@ def save_all_changed_extensions(self): 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). +''' } From 615f1b4569db9416aa23bc9179abe41cefaa11c6 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Tue, 27 Jun 2017 18:54:12 -0400 Subject: [PATCH 4/7] Add and revise tests --- Lib/idlelib/idle_test/test_parenmatch.py | 65 +++++++++++------------- 1 file changed, 29 insertions(+), 36 deletions(-) diff --git a/Lib/idlelib/idle_test/test_parenmatch.py b/Lib/idlelib/idle_test/test_parenmatch.py index 46dfec7aed084d..6943a70c997c1c 100644 --- a/Lib/idlelib/idle_test/test_parenmatch.py +++ b/Lib/idlelib/idle_test/test_parenmatch.py @@ -45,46 +45,39 @@ def get_parenmatch(self): pm.bell = lambda: None return pm - def test_paren_expression(self): + def test_paren_styles(self): """ - Test ParenMatch with 'expression' (or unknown) 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('<>', text.event_info()) - self.assertTupleEqual(text.tag_prevrange('paren', 'end'), - ('1.10', '1.15')) - text.insert('insert', ')') - pm.restore_event() - self.assertNotIn('<>', 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_opener(self): - """ - Test ParenMatch with 'opener' (or 'default') style. - """ - text = self.text - pm = self.get_parenmatch() - pm.set_style('opener') - - text.insert('insert', 'def foobar(a, b') - pm.flash_paren_event('event') - self.assertIn('<>', text.event_info()) - self.assertTupleEqual(text.tag_prevrange('paren', 'end'), - ('1.10', '1.11')) - text.insert('insert', ')') - pm.restore_event() - self.assertNotIn('<>', 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('<>', 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('<>', + 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): """ From 2979a8bfc6dc0738a28e01e70c3a3522a68b2322 Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Tue, 27 Jun 2017 19:05:55 -0400 Subject: [PATCH 5/7] Add news --- .../next/IDLE/2017-06-27-19-05-40.bpo-30723.rQh06y.rst | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 Misc/NEWS.d/next/IDLE/2017-06-27-19-05-40.bpo-30723.rQh06y.rst 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 00000000000000..37ed38895c980a --- /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. From 268dee82e907e3a7a1bba9dff6d0d3c03520342c Mon Sep 17 00:00:00 2001 From: terryjreedy Date: Tue, 27 Jun 2017 19:07:54 -0400 Subject: [PATCH 6/7] Update 2017-06-27-19-05-40.bpo-30723.rQh06y.rst --- .../next/IDLE/2017-06-27-19-05-40.bpo-30723.rQh06y.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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 index 37ed38895c980a..ca58c346ee0e46 100644 --- 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 @@ -1,5 +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. +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. From 17e55588e2ced2cd05a690f837f2af87076b951a Mon Sep 17 00:00:00 2001 From: terryjreedy Date: Tue, 27 Jun 2017 19:09:22 -0400 Subject: [PATCH 7/7] Update 2017-06-27-19-05-40.bpo-30723.rQh06y.rst --- Misc/NEWS.d/next/IDLE/2017-06-27-19-05-40.bpo-30723.rQh06y.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index ca58c346ee0e46..ceb42a2bbcaf2f 100644 --- 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 @@ -2,4 +2,4 @@ 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. +parenmatch. Add new tests. Original patch by Charles Wohlganger.