mirror of
https://github.com/python/cpython.git
synced 2025-08-31 05:58:33 +00:00
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:
parent
c85c74cd08
commit
b17544551f
13 changed files with 1593 additions and 202 deletions
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue