"""ParenMatch -- An IDLE extension for parenthesis matching.
When you hit a right paren, the cursor should move briefly to the left
paren. Paren here is used generically; the matching applies to
parentheses, square brackets, and curly braces.
WARNING: This extension will fight with the CallTips extension,
because they both are interested in the KeyRelease-parenright event.
We'll have to fix IDLE to do something reasonable when two or more
extensions what to capture the same event.
from EditorWindow
import EditorWindow
, index2line
from configHandler
import idleConf
"""Highlight matching parentheses
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
The supported styles are:
default -- When a right paren is typed, highlight the matching
expression -- When a right paren is typed, highlight the entire
expression from the left paren to the right paren.
- fix interaction with CallTips
- 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,
STYLE
= idleConf
.GetOption('extensions','ParenMatch','style',
FLASH_DELAY
= idleConf
.GetOption('extensions','ParenMatch','flash-delay',
HILITE_CONFIG
= idleConf
.GetHighlight(idleConf
.CurrentTheme(),'hilite')
BELL
= idleConf
.GetOption('extensions','ParenMatch','bell',
def __init__(self
, editwin
):
self
.finder
= LastOpenBracketFinder(editwin
)
self
.set_style(self
.STYLE
)
def set_style(self
, style
):
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
def flash_open_paren_event(self
, event
):
index
= self
.finder
.find(keysym_type(event
.keysym
))
def check_restore_event(self
, event
=None):
self
.text
.tag_delete("paren")
def handle_restore_timer(self
, timer_count
):
if timer_count
+ 1 == self
.counter
:
self
.check_restore_event()
def warn_mismatched(self
):
# any one of the create_tag_XXX methods can be used depending on
def create_tag_default(self
, index
):
"""Highlight the single paren that matches"""
self
.text
.tag_add("paren", index
)
self
.text
.tag_config("paren", self
.HILITE_CONFIG
)
def create_tag_expression(self
, index
):
"""Highlight the entire expression"""
self
.text
.tag_add("paren", index
, "insert")
self
.text
.tag_config("paren", self
.HILITE_CONFIG
)
# any one of the set_timeout_XXX methods can be used depending on
def set_timeout_none(self
):
"""Highlight will remain until user input turns it off"""
def set_timeout_last(self
):
"""The last highlight created will be removed after .5 sec"""
# associate a counter with an event; only disable the "paren"
# tag if the event is for the most recent timer.
self
.editwin
.text_frame
.after(self
.FLASH_DELAY
,
lambda self
=self
, c
=self
.counter
: \
self
.handle_restore_timer(c
))
self
.counter
= self
.counter
+ 1
# Not all possible chars or keysyms are checked because of the
# limited context in which the function is used.
if ks
== "parenright" or ks
== "(":
if ks
== "bracketright" or ks
== "[":
if ks
== "braceright" or ks
== "{":
class LastOpenBracketFinder
:
num_context_lines
= EditorWindow
.num_context_lines
indentwidth
= EditorWindow
.indentwidth
tabwidth
= EditorWindow
.tabwidth
context_use_ps1
= EditorWindow
.context_use_ps1
def __init__(self
, editwin
):
def _find_offset_in_buf(self
, lno
):
y
= PyParse
.Parser(self
.indentwidth
, self
.tabwidth
)
for context
in self
.num_context_lines
:
startat
= max(lno
- context
, 1)
startatindex
= repr(startat
) + ".0"
# rawtext needs to contain everything up to the last
# character, which was the close paren. the parser also
# requires that the last line ends with "\n"
rawtext
= self
.text
.get(startatindex
, "insert")[:-1] + "\n"
bod
= y
.find_good_parse_start(
self
._build
_char
_in
_string
_func
(startatindex
))
if bod
is not None or startat
== 1:
i
= y
.get_last_open_bracket_pos()
def find(self
, right_keysym_type
):
"""Return the location of the last open paren"""
lno
= index2line(self
.text
.index("insert"))
i
, buf
= self
._find
_offset
_in
_buf
(lno
)
or keysym_type(buf
[i
]) != right_keysym_type
:
lines_back
= buf
[i
:].count("\n") - 1
# subtract one for the "\n" added to please the parser
j
= upto_open
.rfind("\n") + 1 # offset of column 0 of line
return "%d.%d" % (lno
- lines_back
, offset
)
def _build_char_in_string_func(self
, startindex
):
def inner(offset
, startindex
=startindex
,
icis
=self
.editwin
.is_char_in_string
):
return icis(startindex
+ "%dc" % offset
)