1 """ParenMatch -- An IDLE extension for parenthesis matching.
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.
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.
14 from AutoIndent
import AutoIndent
, index2line
15 from IdleConf
import idleconf
18 """Highlight matching parentheses
20 There are three supported style of paren matching, based loosely
21 on the Emacs options. The style is select based on the
22 HILITE_STYLE attribute; it can be changed used the set_style
25 The supported styles are:
27 default -- When a right paren is typed, highlight the matching
28 left paren for 1/2 sec.
30 expression -- When a right paren is typed, highlight the entire
31 expression from the left paren to the right paren.
34 - fix interaction with CallTips
35 - extend IDLE with configuration dialog to change options
36 - implement rest of Emacs highlight styles (see below)
37 - print mismatch warning in IDLE status window
39 Note: In Emacs, there are several styles of highlight where the
40 matching paren is highlighted whenever the cursor is immediately
41 to the right of a right paren. I don't know how to do that in Tk,
42 so I haven't bothered.
48 '<<flash-open-paren>>' : ('<KeyRelease-parenright>',
49 '<KeyRelease-bracketright>',
50 '<KeyRelease-braceright>'),
51 '<<check-restore>>' : ('<KeyPress>',),
57 iconf
= idleconf
.getsection('ParenMatch')
58 STYLE
= iconf
.getdef('style', 'default')
59 FLASH_DELAY
= iconf
.getint('flash-delay')
60 HILITE_CONFIG
= iconf
.getcolor('hilite')
61 BELL
= iconf
.getboolean('bell')
64 def __init__(self
, editwin
):
65 self
.editwin
= editwin
66 self
.text
= editwin
.text
67 self
.finder
= LastOpenBracketFinder(editwin
)
70 self
.set_style(self
.STYLE
)
72 def set_style(self
, style
):
74 if style
== "default":
75 self
.create_tag
= self
.create_tag_default
76 self
.set_timeout
= self
.set_timeout_last
77 elif style
== "expression":
78 self
.create_tag
= self
.create_tag_expression
79 self
.set_timeout
= self
.set_timeout_none
81 def flash_open_paren_event(self
, event
):
82 index
= self
.finder
.find(keysym_type(event
.keysym
))
84 self
.warn_mismatched()
87 self
.create_tag(index
)
90 def check_restore_event(self
, event
=None):
92 self
.text
.tag_delete("paren")
95 def handle_restore_timer(self
, timer_count
):
96 if timer_count
+ 1 == self
.counter
:
97 self
.check_restore_event()
99 def warn_mismatched(self
):
103 # any one of the create_tag_XXX methods can be used depending on
106 def create_tag_default(self
, index
):
107 """Highlight the single paren that matches"""
108 self
.text
.tag_add("paren", index
)
109 self
.text
.tag_config("paren", self
.HILITE_CONFIG
)
111 def create_tag_expression(self
, index
):
112 """Highlight the entire expression"""
113 self
.text
.tag_add("paren", index
, "insert")
114 self
.text
.tag_config("paren", self
.HILITE_CONFIG
)
116 # any one of the set_timeout_XXX methods can be used depending on
119 def set_timeout_none(self
):
120 """Highlight will remain until user input turns it off"""
123 def set_timeout_last(self
):
124 """The last highlight created will be removed after .5 sec"""
125 # associate a counter with an event; only disable the "paren"
126 # tag if the event is for the most recent timer.
127 self
.editwin
.text_frame
.after(self
.FLASH_DELAY
,
128 lambda self
=self
, c
=self
.counter
: \
129 self
.handle_restore_timer(c
))
130 self
.counter
= self
.counter
+ 1
133 # Not all possible chars or keysyms are checked because of the
134 # limited context in which the function is used.
135 if ks
== "parenright" or ks
== "(":
137 if ks
== "bracketright" or ks
== "[":
139 if ks
== "braceright" or ks
== "{":
142 class LastOpenBracketFinder
:
143 num_context_lines
= AutoIndent
.num_context_lines
144 indentwidth
= AutoIndent
.indentwidth
145 tabwidth
= AutoIndent
.tabwidth
146 context_use_ps1
= AutoIndent
.context_use_ps1
148 def __init__(self
, editwin
):
149 self
.editwin
= editwin
150 self
.text
= editwin
.text
152 def _find_offset_in_buf(self
, lno
):
153 y
= PyParse
.Parser(self
.indentwidth
, self
.tabwidth
)
154 for context
in self
.num_context_lines
:
155 startat
= max(lno
- context
, 1)
156 startatindex
= `startat`
+ ".0"
157 # rawtext needs to contain everything up to the last
158 # character, which was the close paren. the parser also
159 # requires that the last line ends with "\n"
160 rawtext
= self
.text
.get(startatindex
, "insert")[:-1] + "\n"
162 bod
= y
.find_good_parse_start(
163 self
.context_use_ps1
,
164 self
._build
_char
_in
_string
_func
(startatindex
))
165 if bod
is not None or startat
== 1:
168 i
= y
.get_last_open_bracket_pos()
171 def find(self
, right_keysym_type
):
172 """Return the location of the last open paren"""
173 lno
= index2line(self
.text
.index("insert"))
174 i
, buf
= self
._find
_offset
_in
_buf
(lno
)
176 or keysym_type(buf
[i
]) != right_keysym_type
:
178 lines_back
= buf
[i
:].count("\n") - 1
179 # subtract one for the "\n" added to please the parser
181 j
= upto_open
.rfind("\n") + 1 # offset of column 0 of line
183 return "%d.%d" % (lno
- lines_back
, offset
)
185 def _build_char_in_string_func(self
, startindex
):
186 def inner(offset
, startindex
=startindex
,
187 icis
=self
.editwin
.is_char_in_string
):
188 return icis(startindex
+ "%dc" % offset
)