diff options
author | Kurt B. Kaiser <kbk@shore.net> | 2005-11-18 22:05:48 (GMT) |
---|---|---|
committer | Kurt B. Kaiser <kbk@shore.net> | 2005-11-18 22:05:48 (GMT) |
commit | b17544551fc8dfd1304d5679c6e444cad4d34d97 (patch) | |
tree | 75cb5f0b7551a755354fc8fff5ae65449a3442ba /Lib/idlelib/ParenMatch.py | |
parent | c85c74cd08f619b69a61a0290c660d642a15e9d3 (diff) | |
download | cpython-b17544551fc8dfd1304d5679c6e444cad4d34d97.zip cpython-b17544551fc8dfd1304d5679c6e444cad4d34d97.tar.gz cpython-b17544551fc8dfd1304d5679c6e444cad4d34d97.tar.bz2 |
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
Diffstat (limited to 'Lib/idlelib/ParenMatch.py')
-rw-r--r-- | Lib/idlelib/ParenMatch.py | 169 |
1 files changed, 81 insertions, 88 deletions
diff --git a/Lib/idlelib/ParenMatch.py b/Lib/idlelib/ParenMatch.py index 407f468..673aee2 100644 --- a/Lib/idlelib/ParenMatch.py +++ b/Lib/idlelib/ParenMatch.py @@ -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 |