1ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot"""ParenMatch -- An IDLE extension for parenthesis matching. 2ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot 3ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team RobotWhen you hit a right paren, the cursor should move briefly to the left 4ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robotparen. Paren here is used generically; the matching applies to 5ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robotparentheses, square brackets, and curly braces. 6ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot""" 7ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot 8ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robotfrom idlelib.HyperParser import HyperParser 9ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robotfrom idlelib.configHandler import idleConf 10ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot 11ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot_openers = {')':'(',']':'[','}':'{'} 12ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team RobotCHECK_DELAY = 100 # miliseconds 13ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot 14ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robotclass ParenMatch: 15ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot """Highlight matching parentheses 16ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot 17ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot There are three supported style of paren matching, based loosely 18ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot on the Emacs options. The style is select based on the 19ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot HILITE_STYLE attribute; it can be changed used the set_style 20ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot method. 21ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot 22ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot The supported styles are: 23ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot 24ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot default -- When a right paren is typed, highlight the matching 25ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot left paren for 1/2 sec. 26ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot 27ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot expression -- When a right paren is typed, highlight the entire 28ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot expression from the left paren to the right paren. 29ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot 30ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot TODO: 31ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot - extend IDLE with configuration dialog to change options 32ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot - implement rest of Emacs highlight styles (see below) 33ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot - print mismatch warning in IDLE status window 34ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot 35ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot Note: In Emacs, there are several styles of highlight where the 36ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot matching paren is highlighted whenever the cursor is immediately 37ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot to the right of a right paren. I don't know how to do that in Tk, 38ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot so I haven't bothered. 39ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot """ 40ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot menudefs = [ 41ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot ('edit', [ 42ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot ("Show surrounding parens", "<<flash-paren>>"), 43ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot ]) 44ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot ] 45ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot STYLE = idleConf.GetOption('extensions','ParenMatch','style', 46ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot default='expression') 47ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot FLASH_DELAY = idleConf.GetOption('extensions','ParenMatch','flash-delay', 48ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot type='int',default=500) 49ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot HILITE_CONFIG = idleConf.GetHighlight(idleConf.CurrentTheme(),'hilite') 50ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot BELL = idleConf.GetOption('extensions','ParenMatch','bell', 51ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot type='bool',default=1) 52ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot 53ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot RESTORE_VIRTUAL_EVENT_NAME = "<<parenmatch-check-restore>>" 54ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot # We want the restore event be called before the usual return and 55ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot # backspace events. 56ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot RESTORE_SEQUENCES = ("<KeyPress>", "<ButtonPress>", 57ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot "<Key-Return>", "<Key-BackSpace>") 58ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot 59ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot def __init__(self, editwin): 60ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot self.editwin = editwin 61ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot self.text = editwin.text 62ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot # Bind the check-restore event to the function restore_event, 63ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot # so that we can then use activate_restore (which calls event_add) 64ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot # and deactivate_restore (which calls event_delete). 65ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot editwin.text.bind(self.RESTORE_VIRTUAL_EVENT_NAME, 66ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot self.restore_event) 67ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot self.counter = 0 68ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot self.is_restore_active = 0 69ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot self.set_style(self.STYLE) 70ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot 71ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot def activate_restore(self): 72ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot if not self.is_restore_active: 73ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot for seq in self.RESTORE_SEQUENCES: 74ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot self.text.event_add(self.RESTORE_VIRTUAL_EVENT_NAME, seq) 75ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot self.is_restore_active = True 76ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot 77ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot def deactivate_restore(self): 78ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot if self.is_restore_active: 79ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot for seq in self.RESTORE_SEQUENCES: 80ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot self.text.event_delete(self.RESTORE_VIRTUAL_EVENT_NAME, seq) 81ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot self.is_restore_active = False 82ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot 83ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot def set_style(self, style): 84ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot self.STYLE = style 85ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot if style == "default": 86ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot self.create_tag = self.create_tag_default 87ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot self.set_timeout = self.set_timeout_last 88ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot elif style == "expression": 89ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot self.create_tag = self.create_tag_expression 90ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot self.set_timeout = self.set_timeout_none 91ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot 92ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot def flash_paren_event(self, event): 93ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot indices = HyperParser(self.editwin, "insert").get_surrounding_brackets() 94ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot if indices is None: 95ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot self.warn_mismatched() 96ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot return 97ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot self.activate_restore() 98ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot self.create_tag(indices) 99ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot self.set_timeout_last() 100ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot 101ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot def paren_closed_event(self, event): 102ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot # If it was a shortcut and not really a closing paren, quit. 103ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot closer = self.text.get("insert-1c") 104ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot if closer not in _openers: 105ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot return 106ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot hp = HyperParser(self.editwin, "insert-1c") 107ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot if not hp.is_in_code(): 108ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot return 109ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot indices = hp.get_surrounding_brackets(_openers[closer], True) 110ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot if indices is None: 111ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot self.warn_mismatched() 112ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot return 113ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot self.activate_restore() 114ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot self.create_tag(indices) 115ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot self.set_timeout() 116ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot 117ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot def restore_event(self, event=None): 118ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot self.text.tag_delete("paren") 119ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot self.deactivate_restore() 120ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot self.counter += 1 # disable the last timer, if there is one. 121ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot 122ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot def handle_restore_timer(self, timer_count): 123ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot if timer_count == self.counter: 124ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot self.restore_event() 125ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot 126ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot def warn_mismatched(self): 127ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot if self.BELL: 128ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot self.text.bell() 129ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot 130ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot # any one of the create_tag_XXX methods can be used depending on 131ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot # the style 132ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot 133ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot def create_tag_default(self, indices): 134ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot """Highlight the single paren that matches""" 135ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot self.text.tag_add("paren", indices[0]) 136ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot self.text.tag_config("paren", self.HILITE_CONFIG) 137ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot 138ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot def create_tag_expression(self, indices): 139ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot """Highlight the entire expression""" 140ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot if self.text.get(indices[1]) in (')', ']', '}'): 141ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot rightindex = indices[1]+"+1c" 142ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot else: 143ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot rightindex = indices[1] 144ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot self.text.tag_add("paren", indices[0], rightindex) 145ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot self.text.tag_config("paren", self.HILITE_CONFIG) 146ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot 147ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot # any one of the set_timeout_XXX methods can be used depending on 148ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot # the style 149ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot 150ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot def set_timeout_none(self): 151ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot """Highlight will remain until user input turns it off 152ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot or the insert has moved""" 153ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot # After CHECK_DELAY, call a function which disables the "paren" tag 154ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot # if the event is for the most recent timer and the insert has changed, 155ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot # or schedules another call for itself. 156ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot self.counter += 1 157ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot def callme(callme, self=self, c=self.counter, 158ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot index=self.text.index("insert")): 159ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot if index != self.text.index("insert"): 160ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot self.handle_restore_timer(c) 161ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot else: 162ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot self.editwin.text_frame.after(CHECK_DELAY, callme, callme) 163ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot self.editwin.text_frame.after(CHECK_DELAY, callme, callme) 164ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot 165ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot def set_timeout_last(self): 166ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot """The last highlight created will be removed after .5 sec""" 167ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot # associate a counter with an event; only disable the "paren" 168ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot # tag if the event is for the most recent timer. 169ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot self.counter += 1 170ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot self.editwin.text_frame.after(self.FLASH_DELAY, 171ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot lambda self=self, c=self.counter: \ 172ec1a0b3abe08fb9a3952e8f48231cda1f6d9b1fandroid-build-team Robot self.handle_restore_timer(c)) 173