mirror of
				https://github.com/python/cpython.git
				synced 2025-11-03 19:34:08 +00:00 
			
		
		
		
	paren matching extension. warning: in current version of IDLE, can
not run this extension and CallTips extension at the same time.
This commit is contained in:
		
							parent
							
								
									98b286c217
								
							
						
					
					
						commit
						63c2b250ef
					
				
					 1 changed files with 193 additions and 0 deletions
				
			
		
							
								
								
									
										193
									
								
								Tools/idle/ParenMatch.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										193
									
								
								Tools/idle/ParenMatch.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,193 @@
 | 
				
			||||||
 | 
					"""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 string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import PyParse
 | 
				
			||||||
 | 
					from AutoIndent import AutoIndent, index2line
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 = []
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    keydefs = {
 | 
				
			||||||
 | 
					        '<<flash-open-paren>>' : ('<KeyRelease-parenright>',
 | 
				
			||||||
 | 
					                                  '<KeyRelease-bracketright>',
 | 
				
			||||||
 | 
					                                  '<KeyRelease-braceright>'),
 | 
				
			||||||
 | 
					        '<<check-restore>>' : ('<KeyPress>',),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    windows_keydefs = {}
 | 
				
			||||||
 | 
					    unix_keydefs = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    STYLE = "default" # or "expression"
 | 
				
			||||||
 | 
					    FLASH_DELAY = 500
 | 
				
			||||||
 | 
					    HILITE_CONFIG = {"foreground": "black",
 | 
				
			||||||
 | 
					                     "background": "#43cd80", # SeaGreen3
 | 
				
			||||||
 | 
					                     }
 | 
				
			||||||
 | 
					    BELL = 1 # set to false for no bell
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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 = AutoIndent.num_context_lines
 | 
				
			||||||
 | 
					    indentwidth = AutoIndent.indentwidth
 | 
				
			||||||
 | 
					    tabwidth = AutoIndent.tabwidth
 | 
				
			||||||
 | 
					    context_use_ps1 = AutoIndent.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.  also need to
 | 
				
			||||||
 | 
					            # append "\n" to please the parser, which seems to expect
 | 
				
			||||||
 | 
					            # a complete line
 | 
				
			||||||
 | 
					            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:
 | 
				
			||||||
 | 
					            return i
 | 
				
			||||||
 | 
					        if 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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue