home *** CD-ROM | disk | FTP | other *** search
/ PC World 2005 June / PCWorld_2005-06_cd.bin / software / vyzkuste / firewally / firewally.exe / framework-2.3.exe / ParenMatch.py < prev    next >
Text File  |  2003-12-30  |  7KB  |  179 lines

  1. """ParenMatch -- An IDLE extension for parenthesis matching.
  2.  
  3. When you hit a right paren, the cursor should move briefly to the left
  4. paren.  Paren here is used generically; the matching applies to
  5. parentheses, square brackets, and curly braces.
  6.  
  7. WARNING: This extension will fight with the CallTips extension,
  8. because they both are interested in the KeyRelease-parenright event.
  9. We'll have to fix IDLE to do something reasonable when two or more
  10. extensions what to capture the same event.
  11. """
  12.  
  13. import PyParse
  14. from EditorWindow import EditorWindow, index2line
  15. from configHandler import idleConf
  16.  
  17. class ParenMatch:
  18.     """Highlight matching parentheses
  19.  
  20.     There are three supported style of paren matching, based loosely
  21.     on the Emacs options.  The style is select based on the
  22.     HILITE_STYLE attribute; it can be changed used the set_style
  23.     method.
  24.  
  25.     The supported styles are:
  26.  
  27.     default -- When a right paren is typed, highlight the matching
  28.         left paren for 1/2 sec.
  29.  
  30.     expression -- When a right paren is typed, highlight the entire
  31.         expression from the left paren to the right paren.
  32.  
  33.     TODO:
  34.         - fix interaction with CallTips
  35.         - extend IDLE with configuration dialog to change options
  36.         - implement rest of Emacs highlight styles (see below)
  37.         - print mismatch warning in IDLE status window
  38.  
  39.     Note: In Emacs, there are several styles of highlight where the
  40.     matching paren is highlighted whenever the cursor is immediately
  41.     to the right of a right paren.  I don't know how to do that in Tk,
  42.     so I haven't bothered.
  43.     """
  44.     menudefs = []
  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.     def __init__(self, editwin):
  54.         self.editwin = editwin
  55.         self.text = editwin.text
  56.         self.finder = LastOpenBracketFinder(editwin)
  57.         self.counter = 0
  58.         self._restore = None
  59.         self.set_style(self.STYLE)
  60.  
  61.     def set_style(self, style):
  62.         self.STYLE = style
  63.         if style == "default":
  64.             self.create_tag = self.create_tag_default
  65.             self.set_timeout = self.set_timeout_last
  66.         elif style == "expression":
  67.             self.create_tag = self.create_tag_expression
  68.             self.set_timeout = self.set_timeout_none
  69.  
  70.     def flash_open_paren_event(self, event):
  71.         index = self.finder.find(keysym_type(event.keysym))
  72.         if index is None:
  73.             self.warn_mismatched()
  74.             return
  75.         self._restore = 1
  76.         self.create_tag(index)
  77.         self.set_timeout()
  78.  
  79.     def check_restore_event(self, event=None):
  80.         if self._restore:
  81.             self.text.tag_delete("paren")
  82.             self._restore = None
  83.  
  84.     def handle_restore_timer(self, timer_count):
  85.         if timer_count + 1 == self.counter:
  86.             self.check_restore_event()
  87.  
  88.     def warn_mismatched(self):
  89.         if self.BELL:
  90.             self.text.bell()
  91.  
  92.     # any one of the create_tag_XXX methods can be used depending on
  93.     # the style
  94.  
  95.     def create_tag_default(self, index):
  96.         """Highlight the single paren that matches"""
  97.         self.text.tag_add("paren", index)
  98.         self.text.tag_config("paren", self.HILITE_CONFIG)
  99.  
  100.     def create_tag_expression(self, index):
  101.         """Highlight the entire expression"""
  102.         self.text.tag_add("paren", index, "insert")
  103.         self.text.tag_config("paren", self.HILITE_CONFIG)
  104.  
  105.     # any one of the set_timeout_XXX methods can be used depending on
  106.     # the style
  107.  
  108.     def set_timeout_none(self):
  109.         """Highlight will remain until user input turns it off"""
  110.         pass
  111.  
  112.     def set_timeout_last(self):
  113.         """The last highlight created will be removed after .5 sec"""
  114.         # associate a counter with an event; only disable the "paren"
  115.         # tag if the event is for the most recent timer.
  116.         self.editwin.text_frame.after(self.FLASH_DELAY,
  117.                                       lambda self=self, c=self.counter: \
  118.                                       self.handle_restore_timer(c))
  119.         self.counter = self.counter + 1
  120.  
  121. def keysym_type(ks):
  122.     # Not all possible chars or keysyms are checked because of the
  123.     # limited context in which the function is used.
  124.     if ks == "parenright" or ks == "(":
  125.         return "paren"
  126.     if ks == "bracketright" or ks == "[":
  127.         return "bracket"
  128.     if ks == "braceright" or ks == "{":
  129.         return "brace"
  130.  
  131. class LastOpenBracketFinder:
  132.     num_context_lines = EditorWindow.num_context_lines
  133.     indentwidth = EditorWindow.indentwidth
  134.     tabwidth = EditorWindow.tabwidth
  135.     context_use_ps1 = EditorWindow.context_use_ps1
  136.  
  137.     def __init__(self, editwin):
  138.         self.editwin = editwin
  139.         self.text = editwin.text
  140.  
  141.     def _find_offset_in_buf(self, lno):
  142.         y = PyParse.Parser(self.indentwidth, self.tabwidth)
  143.         for context in self.num_context_lines:
  144.             startat = max(lno - context, 1)
  145.             startatindex = `startat` + ".0"
  146.             # rawtext needs to contain everything up to the last
  147.             # character, which was the close paren.  the parser also
  148.             # requires that the last line ends with "\n"
  149.             rawtext = self.text.get(startatindex, "insert")[:-1] + "\n"
  150.             y.set_str(rawtext)
  151.             bod = y.find_good_parse_start(
  152.                         self.context_use_ps1,
  153.                         self._build_char_in_string_func(startatindex))
  154.             if bod is not None or startat == 1:
  155.                 break
  156.         y.set_lo(bod or 0)
  157.         i = y.get_last_open_bracket_pos()
  158.         return i, y.str
  159.  
  160.     def find(self, right_keysym_type):
  161.         """Return the location of the last open paren"""
  162.         lno = index2line(self.text.index("insert"))
  163.         i, buf = self._find_offset_in_buf(lno)
  164.         if i is None \
  165.            or keysym_type(buf[i]) != right_keysym_type:
  166.             return None
  167.         lines_back = buf[i:].count("\n") - 1
  168.         # subtract one for the "\n" added to please the parser
  169.         upto_open = buf[:i]
  170.         j = upto_open.rfind("\n") + 1 # offset of column 0 of line
  171.         offset = i - j
  172.         return "%d.%d" % (lno - lines_back, offset)
  173.  
  174.     def _build_char_in_string_func(self, startindex):
  175.         def inner(offset, startindex=startindex,
  176.                   icis=self.editwin.is_char_in_string):
  177.             return icis(startindex + "%dc" % offset)
  178.         return inner
  179.