Merge IDLE-syntax-branch r39668:41449 into trunk

A    idlelib/AutoCompleteWindow.py
A    idlelib/AutoComplete.py
A    idlelib/HyperParser.py
M    idlelib/PyShell.py
M    idlelib/ParenMatch.py
M    idlelib/configDialog.py
M    idlelib/EditorWindow.py
M    idlelib/PyParse.py
M    idlelib/CallTips.py
M    idlelib/CallTipWindow.py
M    idlelib/run.py
M    idlelib/config-extensions.def
A    idlelib/MultiCall.py
This commit is contained in:
Kurt B. Kaiser 2005-11-18 22:05:48 +00:00
parent c85c74cd08
commit b17544551f
13 changed files with 1593 additions and 202 deletions

View file

@ -3,17 +3,14 @@
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 PyParse
from EditorWindow import EditorWindow, index2line
from HyperParser import HyperParser
from configHandler import idleConf
keysym_opener = {"parenright":'(', "bracketright":'[', "braceright":'{'}
CHECK_DELAY = 100 # miliseconds
class ParenMatch:
"""Highlight matching parentheses
@ -31,7 +28,6 @@ class ParenMatch:
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
@ -41,7 +37,11 @@ class ParenMatch:
to the right of a right paren. I don't know how to do that in Tk,
so I haven't bothered.
"""
menudefs = []
menudefs = [
('edit', [
("Show surrounding parens", "<<flash-paren>>"),
])
]
STYLE = idleConf.GetOption('extensions','ParenMatch','style',
default='expression')
FLASH_DELAY = idleConf.GetOption('extensions','ParenMatch','flash-delay',
@ -50,14 +50,36 @@ class ParenMatch:
BELL = idleConf.GetOption('extensions','ParenMatch','bell',
type='bool',default=1)
RESTORE_VIRTUAL_EVENT_NAME = "<<parenmatch-check-restore>>"
# We want the restore event be called before the usual return and
# backspace events.
RESTORE_SEQUENCES = ("<KeyPress>", "<ButtonPress>",
"<Key-Return>", "<Key-BackSpace>")
def __init__(self, editwin):
self.editwin = editwin
self.text = editwin.text
self.finder = LastOpenBracketFinder(editwin)
# Bind the check-restore event to the function restore_event,
# so that we can then use activate_restore (which calls event_add)
# and deactivate_restore (which calls event_delete).
editwin.text.bind(self.RESTORE_VIRTUAL_EVENT_NAME,
self.restore_event)
self.counter = 0
self._restore = None
self.is_restore_active = 0
self.set_style(self.STYLE)
def activate_restore(self):
if not self.is_restore_active:
for seq in self.RESTORE_SEQUENCES:
self.text.event_add(self.RESTORE_VIRTUAL_EVENT_NAME, seq)
self.is_restore_active = True
def deactivate_restore(self):
if self.is_restore_active:
for seq in self.RESTORE_SEQUENCES:
self.text.event_delete(self.RESTORE_VIRTUAL_EVENT_NAME, seq)
self.is_restore_active = False
def set_style(self, style):
self.STYLE = style
if style == "default":
@ -67,23 +89,38 @@ class ParenMatch:
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:
def flash_paren_event(self, event):
indices = HyperParser(self.editwin, "insert").get_surrounding_brackets()
if indices is None:
self.warn_mismatched()
return
self._restore = 1
self.create_tag(index)
self.activate_restore()
self.create_tag(indices)
self.set_timeout_last()
def paren_closed_event(self, event):
# If it was a shortcut and not really a closing paren, quit.
if self.text.get("insert-1c") not in (')',']','}'):
return
hp = HyperParser(self.editwin, "insert-1c")
if not hp.is_in_code():
return
indices = hp.get_surrounding_brackets(keysym_opener[event.keysym], True)
if indices is None:
self.warn_mismatched()
return
self.activate_restore()
self.create_tag(indices)
self.set_timeout()
def check_restore_event(self, event=None):
if self._restore:
self.text.tag_delete("paren")
self._restore = None
def restore_event(self, event=None):
self.text.tag_delete("paren")
self.deactivate_restore()
self.counter += 1 # disable the last timer, if there is one.
def handle_restore_timer(self, timer_count):
if timer_count + 1 == self.counter:
self.check_restore_event()
if timer_count == self.counter:
self.restore_event()
def warn_mismatched(self):
if self.BELL:
@ -92,87 +129,43 @@ class ParenMatch:
# any one of the create_tag_XXX methods can be used depending on
# the style
def create_tag_default(self, index):
def create_tag_default(self, indices):
"""Highlight the single paren that matches"""
self.text.tag_add("paren", index)
self.text.tag_add("paren", indices[0])
self.text.tag_config("paren", self.HILITE_CONFIG)
def create_tag_expression(self, index):
def create_tag_expression(self, indices):
"""Highlight the entire expression"""
self.text.tag_add("paren", index, "insert")
if self.text.get(indices[1]) in (')', ']', '}'):
rightindex = indices[1]+"+1c"
else:
rightindex = indices[1]
self.text.tag_add("paren", indices[0], rightindex)
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
"""Highlight will remain until user input turns it off
or the insert has moved"""
# After CHECK_DELAY, call a function which disables the "paren" tag
# if the event is for the most recent timer and the insert has changed,
# or schedules another call for itself.
self.counter += 1
def callme(callme, self=self, c=self.counter,
index=self.text.index("insert")):
if index != self.text.index("insert"):
self.handle_restore_timer(c)
else:
self.editwin.text_frame.after(CHECK_DELAY, callme, callme)
self.editwin.text_frame.after(CHECK_DELAY, callme, callme)
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.counter += 1
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 = EditorWindow.num_context_lines
indentwidth = EditorWindow.indentwidth
tabwidth = EditorWindow.tabwidth
context_use_ps1 = EditorWindow.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 = 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"
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 \
or 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