Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 63c2b25

Browse files
committed
paren matching extension. warning: in current version of IDLE, can
not run this extension and CallTips extension at the same time.
1 parent 98b286c commit 63c2b25

1 file changed

Lines changed: 193 additions & 0 deletions

File tree

Tools/idle/ParenMatch.py

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
"""ParenMatch -- An IDLE extension for parenthesis matching.
2+
3+
When you hit a right paren, the cursor should move briefly to the left
4+
paren. Paren here is used generically; the matching applies to
5+
parentheses, square brackets, and curly braces.
6+
7+
WARNING: This extension will fight with the CallTips extension,
8+
because they both are interested in the KeyRelease-parenright event.
9+
We'll have to fix IDLE to do something reasonable when two or more
10+
extensions what to capture the same event.
11+
"""
12+
13+
import string
14+
15+
import PyParse
16+
from AutoIndent import AutoIndent, index2line
17+
18+
class ParenMatch:
19+
"""Highlight matching parentheses
20+
21+
There are three supported style of paren matching, based loosely
22+
on the Emacs options. The style is select based on the
23+
HILITE_STYLE attribute; it can be changed used the set_style
24+
method.
25+
26+
The supported styles are:
27+
28+
default -- When a right paren is typed, highlight the matching
29+
left paren for 1/2 sec.
30+
31+
expression -- When a right paren is typed, highlight the entire
32+
expression from the left paren to the right paren.
33+
34+
TODO:
35+
- fix interaction with CallTips
36+
- extend IDLE with configuration dialog to change options
37+
- implement rest of Emacs highlight styles (see below)
38+
- print mismatch warning in IDLE status window
39+
40+
Note: In Emacs, there are several styles of highlight where the
41+
matching paren is highlighted whenever the cursor is immediately
42+
to the right of a right paren. I don't know how to do that in Tk,
43+
so I haven't bothered.
44+
"""
45+
46+
menudefs = []
47+
48+
keydefs = {
49+
'<<flash-open-paren>>' : ('<KeyRelease-parenright>',
50+
'<KeyRelease-bracketright>',
51+
'<KeyRelease-braceright>'),
52+
'<<check-restore>>' : ('<KeyPress>',),
53+
}
54+
55+
windows_keydefs = {}
56+
unix_keydefs = {}
57+
58+
STYLE = "default" # or "expression"
59+
FLASH_DELAY = 500
60+
HILITE_CONFIG = {"foreground": "black",
61+
"background": "#43cd80", # SeaGreen3
62+
}
63+
BELL = 1 # set to false for no bell
64+
65+
def __init__(self, editwin):
66+
self.editwin = editwin
67+
self.text = editwin.text
68+
self.finder = LastOpenBracketFinder(editwin)
69+
self.counter = 0
70+
self._restore = None
71+
self.set_style(self.STYLE)
72+
73+
def set_style(self, style):
74+
self.STYLE = style
75+
if style == "default":
76+
self.create_tag = self.create_tag_default
77+
self.set_timeout = self.set_timeout_last
78+
elif style == "expression":
79+
self.create_tag = self.create_tag_expression
80+
self.set_timeout = self.set_timeout_none
81+
82+
def flash_open_paren_event(self, event):
83+
index = self.finder.find(keysym_type(event.keysym))
84+
if index is None:
85+
self.warn_mismatched()
86+
return
87+
self._restore = 1
88+
self.create_tag(index)
89+
self.set_timeout()
90+
91+
def check_restore_event(self, event=None):
92+
if self._restore:
93+
self.text.tag_delete("paren")
94+
self._restore = None
95+
96+
def handle_restore_timer(self, timer_count):
97+
if timer_count + 1 == self.counter:
98+
self.check_restore_event()
99+
100+
def warn_mismatched(self):
101+
if self.BELL:
102+
self.text.bell()
103+
104+
# any one of the create_tag_XXX methods can be used depending on
105+
# the style
106+
107+
def create_tag_default(self, index):
108+
"""Highlight the single paren that matches"""
109+
self.text.tag_add("paren", index)
110+
self.text.tag_config("paren", self.HILITE_CONFIG)
111+
112+
def create_tag_expression(self, index):
113+
"""Highlight the entire expression"""
114+
self.text.tag_add("paren", index, "insert")
115+
self.text.tag_config("paren", self.HILITE_CONFIG)
116+
117+
# any one of the set_timeout_XXX methods can be used depending on
118+
# the style
119+
120+
def set_timeout_none(self):
121+
"""Highlight will remain until user input turns it off"""
122+
pass
123+
124+
def set_timeout_last(self):
125+
"""The last highlight created will be removed after .5 sec"""
126+
# associate a counter with an event; only disable the "paren"
127+
# tag if the event is for the most recent timer.
128+
self.editwin.text_frame.after(self.FLASH_DELAY,
129+
lambda self=self, c=self.counter: \
130+
self.handle_restore_timer(c))
131+
self.counter = self.counter + 1
132+
133+
def keysym_type(ks):
134+
# Not all possible chars or keysyms are checked because of the
135+
# limited context in which the function is used.
136+
if ks == "parenright" or ks == "(":
137+
return "paren"
138+
if ks == "bracketright" or ks == "[":
139+
return "bracket"
140+
if ks == "braceright" or ks == "{":
141+
return "brace"
142+
143+
class LastOpenBracketFinder:
144+
num_context_lines = AutoIndent.num_context_lines
145+
indentwidth = AutoIndent.indentwidth
146+
tabwidth = AutoIndent.tabwidth
147+
context_use_ps1 = AutoIndent.context_use_ps1
148+
149+
def __init__(self, editwin):
150+
self.editwin = editwin
151+
self.text = editwin.text
152+
153+
def _find_offset_in_buf(self, lno):
154+
y = PyParse.Parser(self.indentwidth, self.tabwidth)
155+
for context in self.num_context_lines:
156+
startat = max(lno - context, 1)
157+
startatindex = `startat` + ".0"
158+
# rawtext needs to contain everything up to the last
159+
# character, which was the close paren. also need to
160+
# append "\n" to please the parser, which seems to expect
161+
# a complete line
162+
rawtext = self.text.get(startatindex, "insert")[:-1] + "\n"
163+
y.set_str(rawtext)
164+
bod = y.find_good_parse_start(
165+
self.context_use_ps1,
166+
self._build_char_in_string_func(startatindex))
167+
if bod is not None or startat == 1:
168+
break
169+
y.set_lo(bod or 0)
170+
i = y.get_last_open_bracket_pos()
171+
return i, y.str
172+
173+
def find(self, right_keysym_type):
174+
"""Return the location of the last open paren"""
175+
lno = index2line(self.text.index("insert"))
176+
i, buf = self._find_offset_in_buf(lno)
177+
if i is None:
178+
return i
179+
if keysym_type(buf[i]) != right_keysym_type:
180+
return None
181+
lines_back = buf[i:].count("\n") - 1
182+
# subtract one for the "\n" added to please the parser
183+
upto_open = buf[:i]
184+
j = upto_open.rfind("\n") + 1 # offset of column 0 of line
185+
offset = i - j
186+
return "%d.%d" % (lno - lines_back, offset)
187+
188+
def _build_char_in_string_func(self, startindex):
189+
def inner(offset, startindex=startindex,
190+
icis=self.editwin.is_char_in_string):
191+
return icis(startindex + "%dc" % offset)
192+
return inner
193+

0 commit comments

Comments
 (0)