summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwohlganger <charles.wohlganger@gmail.com>2017-06-28 02:36:23 (GMT)
committerterryjreedy <tjreedy@udel.edu>2017-06-28 02:36:23 (GMT)
commitfae2c3538ecbcdd37b6eca891c0815d2093c39e3 (patch)
tree020cc2337e161624e52fccaf0765cb17838566c6
parent84d9d14a1fa395fbd21262ba195490be25a7b3bc (diff)
downloadcpython-fae2c3538ecbcdd37b6eca891c0815d2093c39e3.zip
cpython-fae2c3538ecbcdd37b6eca891c0815d2093c39e3.tar.gz
cpython-fae2c3538ecbcdd37b6eca891c0815d2093c39e3.tar.bz2
bpo-30723: IDLE -- Enhance parenmatch; add style, flash, and help (#2306)
* 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.
-rw-r--r--Lib/idlelib/configdialog.py15
-rw-r--r--Lib/idlelib/idle_test/test_parenmatch.py68
-rw-r--r--Lib/idlelib/parenmatch.py80
-rw-r--r--Misc/ACKS1
-rw-r--r--Misc/NEWS.d/next/IDLE/2017-06-27-19-05-40.bpo-30723.rQh06y.rst5
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
diff --git a/Misc/ACKS b/Misc/ACKS
index a6d1e0f..910f819 100644
--- 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
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.