-
-
Notifications
You must be signed in to change notification settings - Fork 30.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
gh-63075: IDLE editor - Auto insertion of the closers #3520
base: main
Are you sure you want to change the base?
Changes from 63 commits
5c57faf
d49170f
8c3a966
757bb38
d7e136c
b178af5
2adebb9
86c3d4c
1c246fc
0418ad0
097a0d8
4232e77
a7b29f5
e30b7b4
252672b
999b573
1a74591
3501c45
232f711
f095c53
a661bfa
5c90a8f
5a6a007
709b434
b9b1f9a
8b76ccb
b986c12
f86acaa
5805462
165b548
aee5d02
cb161bd
dc09a2b
100463e
3fbaa90
60ff82f
e3f6f16
a253f0c
fe8eb7a
caa7774
c29184e
c62f726
d0e009f
9e889c2
6afdea3
7de27db
a2e76a7
2265b53
03eed24
207b0ca
00acd22
2d2bfbd
fe76cd3
d2966e0
6845bb0
b7afebb
b1a4101
7a53366
d3b6848
eb58d39
119bbfc
814efbe
7f596b9
26323e2
7862d8a
2f19ca8
6d2fa26
15f8d82
d141abc
caa9240
21f4482
25ba0f8
a4cd0e3
cffa9d6
9aaa395
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
""" | ||
Parens and Ticks Closing Extension | ||
|
||
When you hit left paren or tick, | ||
automatically creates the closing paren or tick. | ||
|
||
Author: Charles M. Wohlganger | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove. This already has input from 3 other people. I will list Charles as original author in the news entry. |
||
charles.wohlganger@gmail.com | ||
|
||
Last Updated: 12-Aug-2017 by Charles Wohlganger | ||
|
||
Add to the end of config-extensions.def : | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Config options will be added to config-general.def. The general tab already needs to be split for another issue. This issue, and a complete patch, will depend on how we do that, and then how to lay out the option buttons. |
||
|
||
[ParenClose] | ||
enable = True | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Delete. |
||
paren_close = True | ||
tick_close = True | ||
skip_closures = True | ||
comment_space = True | ||
[ParenClose_cfgBindings] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Delete section. Not needed for hard-coded features. |
||
p-open = <Key-parenleft> <Key-bracketleft> <Key-braceleft> | ||
t-open = <Key-'> <Key-"> | ||
p-close = <Key-parenright> <Key-bracketright> <Key-braceright> | ||
terryjreedy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
""" | ||
|
||
from idlelib.config import idleConf | ||
|
||
|
||
class ParenClose: | ||
''' | ||
When loaded as an extension to IDLE, and paren_close is True, the symbols | ||
([{ will have their closures printed after them and the insertion cursor | ||
moved between the two. The same is true for tick closures and the symbols | ||
' and ". When \'\'\' or """ are typed and tick_close is True, it will also | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will consider rewording docstring, but this is secondary. Why are triples single quotes escaped? |
||
produce the closing symbols. If skip_closures is True, then when a closure | ||
symbol is typed and the same one is to the right of it, that symbols is | ||
deleted before the new one is typed, effectively skipping over the closure. | ||
''' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't really understand these. |
||
def __init__(self, editwin=None): #setting default to none makes testing easier | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Inline comments should be written like a sentence - begin with a capital letter and end with a period. |
||
if editwin: | ||
self.text = editwin.text | ||
else: | ||
self.text=None | ||
self.paren_close = idleConf.GetOption( | ||
'extensions', 'ParenClose', 'paren_close', default=True) | ||
self.tick_close = idleConf.GetOption( | ||
'extensions', 'ParenClose', 'tick_close', default=True) | ||
self.skip_closures = idleConf.GetOption( | ||
'extensions', 'ParenClose', 'skip_closures', default=True) | ||
#sometimes idleConf loads boolean False as string "False" | ||
if self.paren_close == 'False': | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. GetOption has a |
||
self.paren_close = False | ||
if self.tick_close == 'False': | ||
self.tick_close = False | ||
if self.skip_closures == 'False': | ||
self.skip_closures = False | ||
|
||
def p_open_event(self, event): | ||
if self.paren_close: | ||
closer = {'(': ')', '[': ']', '{': '}'}[event.char] | ||
pos = self.text.index('insert') | ||
self.text.insert(pos, closer) | ||
self.text.mark_set('insert', pos) | ||
|
||
def p_close_event(self, event): | ||
pos = self.text.index('insert') | ||
if self.skip_closures \ | ||
and self.text.get(pos, pos + ' +1c') == event.char: | ||
self.text.delete(pos, pos + '+1c') | ||
|
||
def t_open_event(self, event): | ||
if self.tick_close: | ||
pos = self.text.index('insert') | ||
if self.text.get(pos + ' -2c', pos) == event.char * 2 \ | ||
and self.text.get(pos, pos + ' +1c') != event.char: | ||
# Instead of one tick, add two ticks if there are two; | ||
# user wants to make docstring or multiline. | ||
self.text.insert(pos, event.char * 3) | ||
self.text.mark_set('insert', pos) | ||
else: | ||
if self.skip_closures \ | ||
and self.text.get(pos, pos + ' +1c') == event.char: | ||
self.text.delete(pos, pos + ' +1c') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Although there aren't a lot of lines here, this is duplicate code with |
||
else: | ||
self.text.insert(pos, event.char) | ||
self.text.mark_set('insert', pos) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ditto here with |
||
|
||
if __name__ == '__main__': | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Already in parenmatch. |
||
import unittest | ||
unittest.main('idlelib.idle_test.test_parenclose', verbosity=2) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -63,3 +63,13 @@ z-text= Z | |
z-in= <Control-Shift-KeyRelease-Insert> | ||
[ZzDummy_bindings] | ||
z-out= <Control-Shift-KeyRelease-Delete> | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As I said above, put in config-general. |
||
[ParenClose] | ||
enable = True | ||
paren_close = True | ||
tick_close = True | ||
skip_closures = True | ||
[ParenClose_cfgBindings] | ||
p-open = <Key-parenleft> <Key-bracketleft> <Key-braceleft> | ||
t-open = <Key-'> <Key-"> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I downloaded the PR to take a look at it and couldn't open IDLE because of this error:
I looked at this page for the keysyms: and it has Also, |
||
p-close = <Key-parenright> <Key-bracketright> <Key-braceright> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
'''Test idlelib.parenclose. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add MockEvent and ParenCloseTest to test_parenmatch. I have not reviewed in detail, but have a couple of comments. |
||
''' | ||
from idlelib.ParenClose import ParenClose | ||
import unittest | ||
from unittest.mock import Mock | ||
from tkinter import Text # mocktk doesn't do text position addition correctly | ||
|
||
|
||
class MockEvent(object): | ||
def __init__(self, char): | ||
self.char = char | ||
|
||
|
||
class ParenCloseTest(unittest.TestCase): | ||
def test_parenClose(self): | ||
self.p_open_event = ParenClose.p_open_event | ||
self.p_close_event = ParenClose.p_close_event | ||
self.t_open_event = ParenClose.t_open_event | ||
self.text = Text() | ||
p_open = self.p_open_event | ||
p_close = self.p_close_event | ||
t_open = self.t_open_event | ||
for paren_close_set, tick_close_set, skip_closures_set, \ | ||
insert, func, pos, char, result, posend in [ | ||
(1, 1, 1, 'def abuse', p_open, '1.9', '(', | ||
'def abuse()', '1.10'), | ||
(1, 1, 1, 'spam = ', p_open, '1.7', '[', | ||
'spam = []', '1.8'), | ||
(1, 1, 1, 'eggs = ', p_open, '1.7', '{', | ||
'eggs = {}', '1.8'), | ||
(1, 1, 1, 'def abuse2()', p_close, '1.11', ')', | ||
'def abuse2()', '1.12'), | ||
(1, 1, 1, 'spam2 = []', p_close, '1.9', ']', | ||
'spam2 = []', '1.10'), | ||
(1, 1, 1, 'eggs2 = {}', p_close, '1.9', '}', | ||
'eggs2 = {}', '1.10'), | ||
(1, 1, 1, "color = ", t_open, '1.8', "'", | ||
"color = ''", '1.9'), | ||
(1, 1, 1, "velocity = ", t_open, '1.11', '"', | ||
'velocity = ""', '1.12'), | ||
(1, 1, 1, "more_spam = ''", t_open, '1.14', "'", | ||
"more_spam = ''''''", '1.15'), | ||
(1, 1, 1, 'more_eggs = ""', t_open, '1.14', '"', | ||
'more_eggs = """"""', '1.15'), | ||
(1, 1, 1, "more_spam2 = ''''''", t_open, '1.16', "'", | ||
"more_spam2 = ''''''", '1.17'), | ||
(1, 1, 1, 'more_eggs2 = """"""', t_open, '1.16', '"', | ||
'more_eggs2 = """"""', '1.17'), | ||
(0, 1, 1, 'no_spam = ', p_open, '1.10', '(', | ||
'no_spam = (', '1.11'), | ||
(1, 0, 1, 'no_velocity = ', t_open, '1.14', "'", | ||
"no_velocity = '", '1.15'), | ||
(1, 0, 1, 'no_more_eggs = ""', t_open, '1.17', '"', | ||
'no_more_eggs = """', '1.18'), | ||
(1, 1, 0, 'tinny = []', p_close, '1.9', ']', | ||
'tinny = []]', '1.10'), | ||
(1, 1, 0, 'gone = ""', t_open, '1.8', '"', | ||
'gone = """"', '1.9')]: | ||
# reset text and self | ||
self.paren_close = paren_close_set | ||
self.tick_close = tick_close_set | ||
self.skip_closures = skip_closures_set | ||
self.text.delete('1.0', 'end') | ||
# write text, move to current position, write character | ||
self.text.insert('1.0', insert) | ||
self.text.mark_set('insert', pos) | ||
event = MockEvent(char) | ||
func(self, event) | ||
p = self.text.index('insert') | ||
self.text.insert(p, char) | ||
# checking position and text at the same time | ||
# makes it easier to spot where errors occur | ||
p = self.text.index('insert') | ||
actual = self.text.get('1.0', 'end') | ||
self.assertTupleEqual((actual, p), (result+'\n', posend)) | ||
|
||
if __name__ == '__main__': | ||
unittest.main(verbosity=2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add ParenClose to parenmatch.py.