mirror of
				https://github.com/python/cpython.git
				synced 2025-11-03 19:34:08 +00:00 
			
		
		
		
	[ 735527 ] Re Bug [ 678325 ] ParenMatching Missing AutoIndent AutoIndent was merged with EditorWindow, this patch corrects the references in ParenMatch.
		
			
				
	
	
		
			178 lines
		
	
	
	
		
			6.5 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			178 lines
		
	
	
	
		
			6.5 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
"""ParenMatch -- An IDLE extension for parenthesis matching.
 | 
						|
 | 
						|
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 configHandler import idleConf
 | 
						|
 | 
						|
class ParenMatch:
 | 
						|
    """Highlight matching parentheses
 | 
						|
 | 
						|
    There are three supported style of paren matching, based loosely
 | 
						|
    on the Emacs options.  The style is select based on the
 | 
						|
    HILITE_STYLE attribute; it can be changed used the set_style
 | 
						|
    method.
 | 
						|
 | 
						|
    The supported styles are:
 | 
						|
 | 
						|
    default -- When a right paren is typed, highlight the matching
 | 
						|
        left paren for 1/2 sec.
 | 
						|
 | 
						|
    expression -- When a right paren is typed, highlight the entire
 | 
						|
        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
 | 
						|
 | 
						|
    Note: In Emacs, there are several styles of highlight where the
 | 
						|
    matching paren is highlighted whenever the cursor is immediately
 | 
						|
    to the right of a right paren.  I don't know how to do that in Tk,
 | 
						|
    so I haven't bothered.
 | 
						|
    """
 | 
						|
    menudefs = []
 | 
						|
    STYLE = idleConf.GetOption('extensions','ParenMatch','style',
 | 
						|
            default='expression')
 | 
						|
    FLASH_DELAY = idleConf.GetOption('extensions','ParenMatch','flash-delay',
 | 
						|
            type='int',default=500)
 | 
						|
    HILITE_CONFIG = idleConf.GetHighlight(idleConf.CurrentTheme(),'hilite')
 | 
						|
    BELL = idleConf.GetOption('extensions','ParenMatch','bell',
 | 
						|
            type='bool',default=1)
 | 
						|
 | 
						|
    def __init__(self, editwin):
 | 
						|
        self.editwin = editwin
 | 
						|
        self.text = editwin.text
 | 
						|
        self.finder = LastOpenBracketFinder(editwin)
 | 
						|
        self.counter = 0
 | 
						|
        self._restore = None
 | 
						|
        self.set_style(self.STYLE)
 | 
						|
 | 
						|
    def set_style(self, style):
 | 
						|
        self.STYLE = style
 | 
						|
        if style == "default":
 | 
						|
            self.create_tag = self.create_tag_default
 | 
						|
            self.set_timeout = self.set_timeout_last
 | 
						|
        elif style == "expression":
 | 
						|
            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:
 | 
						|
            self.warn_mismatched()
 | 
						|
            return
 | 
						|
        self._restore = 1
 | 
						|
        self.create_tag(index)
 | 
						|
        self.set_timeout()
 | 
						|
 | 
						|
    def check_restore_event(self, event=None):
 | 
						|
        if self._restore:
 | 
						|
            self.text.tag_delete("paren")
 | 
						|
            self._restore = None
 | 
						|
 | 
						|
    def handle_restore_timer(self, timer_count):
 | 
						|
        if timer_count + 1 == self.counter:
 | 
						|
            self.check_restore_event()
 | 
						|
 | 
						|
    def warn_mismatched(self):
 | 
						|
        if self.BELL:
 | 
						|
            self.text.bell()
 | 
						|
 | 
						|
    # any one of the create_tag_XXX methods can be used depending on
 | 
						|
    # the style
 | 
						|
 | 
						|
    def create_tag_default(self, index):
 | 
						|
        """Highlight the single paren that matches"""
 | 
						|
        self.text.tag_add("paren", index)
 | 
						|
        self.text.tag_config("paren", self.HILITE_CONFIG)
 | 
						|
 | 
						|
    def create_tag_expression(self, index):
 | 
						|
        """Highlight the entire expression"""
 | 
						|
        self.text.tag_add("paren", index, "insert")
 | 
						|
        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
 | 
						|
 | 
						|
    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.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 = `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
 |