0001 """ParenMatch -- An IDLE extension for parenthesis matching. 0002 0003 When you hit a right paren, the cursor should move briefly to the left 0004 paren. Paren here is used generically; the matching applies to 0005 parentheses, square brackets, and curly braces. 0006 0007 WARNING: This extension will fight with the CallTips extension, 0008 because they both are interested in the KeyRelease-parenright event. 0009 We'll have to fix IDLE to do something reasonable when two or more 0010 extensions what to capture the same event. 0011 """ 0012 0013 import PyParse 0014 from EditorWindow import EditorWindow, index2line 0015 from configHandler import idleConf 0016 0017 class ParenMatch: 0018 """Highlight matching parentheses 0019 0020 There are three supported style of paren matching, based loosely 0021 on the Emacs options. The style is select based on the 0022 HILITE_STYLE attribute; it can be changed used the set_style 0023 method. 0024 0025 The supported styles are: 0026 0027 default -- When a right paren is typed, highlight the matching 0028 left paren for 1/2 sec. 0029 0030 expression -- When a right paren is typed, highlight the entire 0031 expression from the left paren to the right paren. 0032 0033 TODO: 0034 - fix interaction with CallTips 0035 - extend IDLE with configuration dialog to change options 0036 - implement rest of Emacs highlight styles (see below) 0037 - print mismatch warning in IDLE status window 0038 0039 Note: In Emacs, there are several styles of highlight where the 0040 matching paren is highlighted whenever the cursor is immediately 0041 to the right of a right paren. I don't know how to do that in Tk, 0042 so I haven't bothered. 0043 """ 0044 menudefs = [] 0045 STYLE = idleConf.GetOption('extensions','ParenMatch','style', 0046 default='expression') 0047 FLASH_DELAY = idleConf.GetOption('extensions','ParenMatch','flash-delay', 0048 type='int',default=500) 0049 HILITE_CONFIG = idleConf.GetHighlight(idleConf.CurrentTheme(),'hilite') 0050 BELL = idleConf.GetOption('extensions','ParenMatch','bell', 0051 type='bool',default=1) 0052 0053 def __init__(self, editwin): 0054 self.editwin = editwin 0055 self.text = editwin.text 0056 self.finder = LastOpenBracketFinder(editwin) 0057 self.counter = 0 0058 self._restore = None 0059 self.set_style(self.STYLE) 0060 0061 def set_style(self, style): 0062 self.STYLE = style 0063 if style == "default": 0064 self.create_tag = self.create_tag_default 0065 self.set_timeout = self.set_timeout_last 0066 elif style == "expression": 0067 self.create_tag = self.create_tag_expression 0068 self.set_timeout = self.set_timeout_none 0069 0070 def flash_open_paren_event(self, event): 0071 index = self.finder.find(keysym_type(event.keysym)) 0072 if index is None: 0073 self.warn_mismatched() 0074 return 0075 self._restore = 1 0076 self.create_tag(index) 0077 self.set_timeout() 0078 0079 def check_restore_event(self, event=None): 0080 if self._restore: 0081 self.text.tag_delete("paren") 0082 self._restore = None 0083 0084 def handle_restore_timer(self, timer_count): 0085 if timer_count + 1 == self.counter: 0086 self.check_restore_event() 0087 0088 def warn_mismatched(self): 0089 if self.BELL: 0090 self.text.bell() 0091 0092 # any one of the create_tag_XXX methods can be used depending on 0093 # the style 0094 0095 def create_tag_default(self, index): 0096 """Highlight the single paren that matches""" 0097 self.text.tag_add("paren", index) 0098 self.text.tag_config("paren", self.HILITE_CONFIG) 0099 0100 def create_tag_expression(self, index): 0101 """Highlight the entire expression""" 0102 self.text.tag_add("paren", index, "insert") 0103 self.text.tag_config("paren", self.HILITE_CONFIG) 0104 0105 # any one of the set_timeout_XXX methods can be used depending on 0106 # the style 0107 0108 def set_timeout_none(self): 0109 """Highlight will remain until user input turns it off""" 0110 pass 0111 0112 def set_timeout_last(self): 0113 """The last highlight created will be removed after .5 sec""" 0114 # associate a counter with an event; only disable the "paren" 0115 # tag if the event is for the most recent timer. 0116 self.editwin.text_frame.after(self.FLASH_DELAY, 0117 lambda self=self, c=self.counter: \ 0118 self.handle_restore_timer(c)) 0119 self.counter = self.counter + 1 0120 0121 def keysym_type(ks): 0122 # Not all possible chars or keysyms are checked because of the 0123 # limited context in which the function is used. 0124 if ks == "parenright" or ks == "(": 0125 return "paren" 0126 if ks == "bracketright" or ks == "[": 0127 return "bracket" 0128 if ks == "braceright" or ks == "{": 0129 return "brace" 0130 0131 class LastOpenBracketFinder: 0132 num_context_lines = EditorWindow.num_context_lines 0133 indentwidth = EditorWindow.indentwidth 0134 tabwidth = EditorWindow.tabwidth 0135 context_use_ps1 = EditorWindow.context_use_ps1 0136 0137 def __init__(self, editwin): 0138 self.editwin = editwin 0139 self.text = editwin.text 0140 0141 def _find_offset_in_buf(self, lno): 0142 y = PyParse.Parser(self.indentwidth, self.tabwidth) 0143 for context in self.num_context_lines: 0144 startat = max(lno - context, 1) 0145 startatindex = repr(startat) + ".0" 0146 # rawtext needs to contain everything up to the last 0147 # character, which was the close paren. the parser also 0148 # requires that the last line ends with "\n" 0149 rawtext = self.text.get(startatindex, "insert")[:-1] + "\n" 0150 y.set_str(rawtext) 0151 bod = y.find_good_parse_start( 0152 self.context_use_ps1, 0153 self._build_char_in_string_func(startatindex)) 0154 if bod is not None or startat == 1: 0155 break 0156 y.set_lo(bod or 0) 0157 i = y.get_last_open_bracket_pos() 0158 return i, y.str 0159 0160 def find(self, right_keysym_type): 0161 """Return the location of the last open paren""" 0162 lno = index2line(self.text.index("insert")) 0163 i, buf = self._find_offset_in_buf(lno) 0164 if i is None \ 0165 or keysym_type(buf[i]) != right_keysym_type: 0166 return None 0167 lines_back = buf[i:].count("\n") - 1 0168 # subtract one for the "\n" added to please the parser 0169 upto_open = buf[:i] 0170 j = upto_open.rfind("\n") + 1 # offset of column 0 of line 0171 offset = i - j 0172 return "%d.%d" % (lno - lines_back, offset) 0173 0174 def _build_char_in_string_func(self, startindex): 0175 def inner(offset, startindex=startindex, 0176 icis=self.editwin.is_char_in_string): 0177 return icis(startindex + "%dc" % offset) 0178 return inner 0179
Generated by PyXR 0.9.4