PageRenderTime 400ms CodeModel.GetById 156ms app.highlight 13ms RepoModel.GetById 228ms app.codeStats 0ms

/Lib/idlelib/ParenMatch.py

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