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 configHandler
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.
45 STYLE
= idleConf
.GetOption('extensions','ParenMatch','style',
47 FLASH_DELAY
= idleConf
.GetOption('extensions','ParenMatch','flash-delay',
48 type='int',default
=500)
49 HILITE_CONFIG
= idleConf
.GetHighlight(idleConf
.CurrentTheme(),'hilite')
50 BELL
= idleConf
.GetOption('extensions','ParenMatch','bell',
51 type='bool',default
=1)
53 def __init__(self
, editwin
):
54 self
.editwin
= editwin
55 self
.text
= editwin
.text
56 self
.finder
= LastOpenBracketFinder(editwin
)
59 self
.set_style(self
.STYLE
)
61 def set_style(self
, style
):
63 if style
== "default":
64 self
.create_tag
= self
.create_tag_default
65 self
.set_timeout
= self
.set_timeout_last
66 elif style
== "expression":
67 self
.create_tag
= self
.create_tag_expression
68 self
.set_timeout
= self
.set_timeout_none
70 def flash_open_paren_event(self
, event
):
71 index
= self
.finder
.find(keysym_type(event
.keysym
))
73 self
.warn_mismatched()
76 self
.create_tag(index
)
79 def check_restore_event(self
, event
=None):
81 self
.text
.tag_delete("paren")
84 def handle_restore_timer(self
, timer_count
):
85 if timer_count
+ 1 == self
.counter
:
86 self
.check_restore_event()
88 def warn_mismatched(self
):
92 # any one of the create_tag_XXX methods can be used depending on
95 def create_tag_default(self
, index
):
96 """Highlight the single paren that matches"""
97 self
.text
.tag_add("paren", index
)
98 self
.text
.tag_config("paren", self
.HILITE_CONFIG
)
100 def create_tag_expression(self
, index
):
101 """Highlight the entire expression"""
102 self
.text
.tag_add("paren", index
, "insert")
103 self
.text
.tag_config("paren", self
.HILITE_CONFIG
)
105 # any one of the set_timeout_XXX methods can be used depending on
108 def set_timeout_none(self
):
109 """Highlight will remain until user input turns it off"""
112 def set_timeout_last(self
):
113 """The last highlight created will be removed after .5 sec"""
114 # associate a counter with an event; only disable the "paren"
115 # tag if the event is for the most recent timer.
116 self
.editwin
.text_frame
.after(self
.FLASH_DELAY
,
117 lambda self
=self
, c
=self
.counter
: \
118 self
.handle_restore_timer(c
))
119 self
.counter
= self
.counter
+ 1
122 # Not all possible chars or keysyms are checked because of the
123 # limited context in which the function is used.
124 if ks
== "parenright" or ks
== "(":
126 if ks
== "bracketright" or ks
== "[":
128 if ks
== "braceright" or ks
== "{":
131 class LastOpenBracketFinder
:
132 num_context_lines
= AutoIndent
.num_context_lines
133 indentwidth
= AutoIndent
.indentwidth
134 tabwidth
= AutoIndent
.tabwidth
135 context_use_ps1
= AutoIndent
.context_use_ps1
137 def __init__(self
, editwin
):
138 self
.editwin
= editwin
139 self
.text
= editwin
.text
141 def _find_offset_in_buf(self
, lno
):
142 y
= PyParse
.Parser(self
.indentwidth
, self
.tabwidth
)
143 for context
in self
.num_context_lines
:
144 startat
= max(lno
- context
, 1)
145 startatindex
= `startat`
+ ".0"
146 # rawtext needs to contain everything up to the last
147 # character, which was the close paren. the parser also
148 # requires that the last line ends with "\n"
149 rawtext
= self
.text
.get(startatindex
, "insert")[:-1] + "\n"
151 bod
= y
.find_good_parse_start(
152 self
.context_use_ps1
,
153 self
._build
_char
_in
_string
_func
(startatindex
))
154 if bod
is not None or startat
== 1:
157 i
= y
.get_last_open_bracket_pos()
160 def find(self
, right_keysym_type
):
161 """Return the location of the last open paren"""
162 lno
= index2line(self
.text
.index("insert"))
163 i
, buf
= self
._find
_offset
_in
_buf
(lno
)
165 or keysym_type(buf
[i
]) != right_keysym_type
:
167 lines_back
= buf
[i
:].count("\n") - 1
168 # subtract one for the "\n" added to please the parser
170 j
= upto_open
.rfind("\n") + 1 # offset of column 0 of line
172 return "%d.%d" % (lno
- lines_back
, offset
)
174 def _build_char_in_string_func(self
, startindex
):
175 def inner(offset
, startindex
=startindex
,
176 icis
=self
.editwin
.is_char_in_string
):
177 return icis(startindex
+ "%dc" % offset
)