mirror of https://github.com/python/cpython
paren matching extension. warning: in current version of IDLE, can
not run this extension and CallTips extension at the same time.
This commit is contained in:
parent
98b286c217
commit
63c2b250ef
|
@ -0,0 +1,193 @@
|
|||
"""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.
|
||||
"""
|
||||
|
||||
import string
|
||||
|
||||
import PyParse
|
||||
from AutoIndent import AutoIndent, index2line
|
||||
|
||||
class ParenMatch:
|
||||
"""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
|
||||
method.
|
||||
|
||||
The supported styles are:
|
||||
|
||||
default -- When a right paren is typed, highlight the matching
|
||||
left paren for 1/2 sec.
|
||||
|
||||
expression -- When a right paren is typed, highlight the entire
|
||||
expression from the left paren to the right paren.
|
||||
|
||||
TODO:
|
||||
- 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,
|
||||
so I haven't bothered.
|
||||
"""
|
||||
|
||||
menudefs = []
|
||||
|
||||
keydefs = {
|
||||
'<<flash-open-paren>>' : ('<KeyRelease-parenright>',
|
||||
'<KeyRelease-bracketright>',
|
||||
'<KeyRelease-braceright>'),
|
||||
'<<check-restore>>' : ('<KeyPress>',),
|
||||
}
|
||||
|
||||
windows_keydefs = {}
|
||||
unix_keydefs = {}
|
||||
|
||||
STYLE = "default" # or "expression"
|
||||
FLASH_DELAY = 500
|
||||
HILITE_CONFIG = {"foreground": "black",
|
||||
"background": "#43cd80", # SeaGreen3
|
||||
}
|
||||
BELL = 1 # set to false for no bell
|
||||
|
||||
def __init__(self, editwin):
|
||||
self.editwin = editwin
|
||||
self.text = editwin.text
|
||||
self.finder = LastOpenBracketFinder(editwin)
|
||||
self.counter = 0
|
||||
self._restore = None
|
||||
self.set_style(self.STYLE)
|
||||
|
||||
def set_style(self, style):
|
||||
self.STYLE = style
|
||||
if style == "default":
|
||||
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))
|
||||
if index is None:
|
||||
self.warn_mismatched()
|
||||
return
|
||||
self._restore = 1
|
||||
self.create_tag(index)
|
||||
self.set_timeout()
|
||||
|
||||
def check_restore_event(self, event=None):
|
||||
if self._restore:
|
||||
self.text.tag_delete("paren")
|
||||
self._restore = None
|
||||
|
||||
def handle_restore_timer(self, timer_count):
|
||||
if timer_count + 1 == self.counter:
|
||||
self.check_restore_event()
|
||||
|
||||
def warn_mismatched(self):
|
||||
if self.BELL:
|
||||
self.text.bell()
|
||||
|
||||
# any one of the create_tag_XXX methods can be used depending on
|
||||
# the style
|
||||
|
||||
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
|
||||
# the style
|
||||
|
||||
def set_timeout_none(self):
|
||||
"""Highlight will remain until user input turns it off"""
|
||||
pass
|
||||
|
||||
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
|
||||
|
||||
def keysym_type(ks):
|
||||
# Not all possible chars or keysyms are checked because of the
|
||||
# limited context in which the function is used.
|
||||
if ks == "parenright" or ks == "(":
|
||||
return "paren"
|
||||
if ks == "bracketright" or ks == "[":
|
||||
return "bracket"
|
||||
if ks == "braceright" or ks == "{":
|
||||
return "brace"
|
||||
|
||||
class LastOpenBracketFinder:
|
||||
num_context_lines = AutoIndent.num_context_lines
|
||||
indentwidth = AutoIndent.indentwidth
|
||||
tabwidth = AutoIndent.tabwidth
|
||||
context_use_ps1 = AutoIndent.context_use_ps1
|
||||
|
||||
def __init__(self, editwin):
|
||||
self.editwin = editwin
|
||||
self.text = editwin.text
|
||||
|
||||
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 = `startat` + ".0"
|
||||
# rawtext needs to contain everything up to the last
|
||||
# character, which was the close paren. also need to
|
||||
# append "\n" to please the parser, which seems to expect
|
||||
# a complete line
|
||||
rawtext = self.text.get(startatindex, "insert")[:-1] + "\n"
|
||||
y.set_str(rawtext)
|
||||
bod = y.find_good_parse_start(
|
||||
self.context_use_ps1,
|
||||
self._build_char_in_string_func(startatindex))
|
||||
if bod is not None or startat == 1:
|
||||
break
|
||||
y.set_lo(bod or 0)
|
||||
i = y.get_last_open_bracket_pos()
|
||||
return i, y.str
|
||||
|
||||
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)
|
||||
if i is None:
|
||||
return i
|
||||
if keysym_type(buf[i]) != right_keysym_type:
|
||||
return None
|
||||
lines_back = buf[i:].count("\n") - 1
|
||||
# subtract one for the "\n" added to please the parser
|
||||
upto_open = buf[:i]
|
||||
j = upto_open.rfind("\n") + 1 # offset of column 0 of line
|
||||
offset = i - j
|
||||
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)
|
||||
return inner
|
||||
|
Loading…
Reference in New Issue