def update_colors(self): """Update the sidebar text colors, usually after config changes.""" linenumbers_colors = idleConf.GetHighlight(idleConf.CurrentTheme(), 'linenumber') prompt_colors = idleConf.GetHighlight(idleConf.CurrentTheme(), 'console') self._update_colors(foreground=prompt_colors['foreground'], background=linenumbers_colors['background'])
def update_highlight_colors(self): if self.context is not None: colors = idleConf.GetHighlight(idleConf.CurrentTheme(), 'context') self.context['background'] = colors['background'] self.context['foreground'] = colors['foreground'] if self.cell00 is not None: line_number_colors = idleConf.GetHighlight(idleConf.CurrentTheme(), 'linenumber') self.cell00.config(bg=line_number_colors['background'])
def update_colors(self): """Update the sidebar text colors, usually after config changes.""" linenumbers_colors = idleConf.GetHighlight(idleConf.CurrentTheme(), 'linenumber') prompt_colors = idleConf.GetHighlight(idleConf.CurrentTheme(), 'console') foreground = prompt_colors['foreground'] background = linenumbers_colors['background'] self.colors = (foreground, background) self.canvas.configure(background=background) self.change_callback()
def init(self): "Create browser tkinter widgets, including the tree." global file_open root = self.master flist = (pyshell.flist if not (self._htest or self._utest) else pyshell.PyShellFileList(root)) file_open = flist.open pyclbr._modules.clear() # create top self.top = top = ListedToplevel(root) top.protocol("WM_DELETE_WINDOW", self.close) top.bind("<Escape>", self.close) if self._htest: # place dialog below parent if running htest top.geometry("+%d+%d" % (root.winfo_rootx(), root.winfo_rooty() + 200)) self.settitle() top.focus_set() # create scrolled canvas theme = idleConf.CurrentTheme() background = idleConf.GetHighlight(theme, 'normal')['background'] sc = ScrolledCanvas(top, bg=background, highlightthickness=0, takefocus=1) sc.frame.pack(expand=1, fill="both") item = self.rootnode() self.node = node = TreeNode(sc.canvas, None, item) if not self._utest: node.update() node.expand()
def init(self, flist): self.flist = flist # reset pyclbr pyclbr._modules.clear() # create top self.top = top = ListedToplevel(flist.root) top.protocol("WM_DELETE_WINDOW", self.close) top.bind("<Escape>", self.close) if self._htest: # place dialog below parent if running htest top.geometry( "+%d+%d" % (flist.root.winfo_rootx(), flist.root.winfo_rooty() + 200)) self.settitle() top.focus_set() # create scrolled canvas theme = idleConf.CurrentTheme() background = idleConf.GetHighlight(theme, 'normal')['background'] sc = ScrolledCanvas(top, bg=background, highlightthickness=0, takefocus=1) sc.frame.pack(expand=1, fill="both") item = self.rootnode() self.node = node = TreeNode(sc.canvas, None, item) node.update() node.expand()
def LoadTagDefs(self): "Create dictionary of tag names to text colors." theme = idleConf.CurrentTheme() self.tagdefs = { "COMMENT": idleConf.GetHighlight(theme, "comment"), "KEYWORD": idleConf.GetHighlight(theme, "keyword"), "BUILTIN": idleConf.GetHighlight(theme, "builtin"), "STRING": idleConf.GetHighlight(theme, "string"), "DEFINITION": idleConf.GetHighlight(theme, "definition"), "SYNC": { 'background': None, 'foreground': None }, "TODO": { 'background': None, 'foreground': None }, "ERROR": idleConf.GetHighlight(theme, "error"), # "hit" is used by ReplaceDialog to mark matches. It shouldn't be changed by Colorizer, but # that currently isn't technically possible. This should be moved elsewhere in the future # when fixing the "hit" tag's visibility, or when the replace dialog is replaced with a # non-modal alternative. "hit": idleConf.GetHighlight(theme, "hit"), } if DEBUG: print('tagdefs', self.tagdefs)
def drawtext(self): textx = self.x+20-1 texty = self.y-4 labeltext = self.item.GetLabelText() if labeltext: id = self.canvas.create_text(textx, texty, anchor="nw", text=labeltext) self.canvas.tag_bind(id, "<1>", self.select) self.canvas.tag_bind(id, "<Double-1>", self.flip) x0, y0, x1, y1 = self.canvas.bbox(id) textx = max(x1, 200) + 10 text = self.item.GetText() or "<no text>" try: self.entry except AttributeError: pass else: self.edit_finish() try: self.label except AttributeError: # padding carefully selected (on Windows) to match Entry widget: self.label = Label(self.canvas, text=text, bd=0, padx=2, pady=2) theme = idleConf.CurrentTheme() if self.selected: self.label.configure(idleConf.GetHighlight(theme, 'hilite')) else: self.label.configure(idleConf.GetHighlight(theme, 'normal')) id = self.canvas.create_window(textx, texty, anchor="nw", window=self.label) self.label.bind("<1>", self.select_or_edit) self.label.bind("<Double-1>", self.flip) self.label.bind("<Control-1>", self.select_more) self.label.bind("<3>", self.execute_file) self.text_id = id
def __init__(self, master=None, **kwargs): """ Initializer. Get default settings from user's IDLE configuration. """ currentTheme = idleConf.CurrentTheme() textcf = { 'padx': 5, 'wrap': 'word', 'undo': 'True', 'foreground': idleConf.GetHighlight( currentTheme, 'normal', fgBg='fg'), 'background': idleConf.GetHighlight( currentTheme, 'normal', fgBg='bg'), 'highlightcolor': idleConf.GetHighlight( currentTheme, 'hilite', fgBg='fg'), 'highlightbackground': idleConf.GetHighlight( currentTheme, 'hilite', fgBg='bg'), 'insertbackground': idleConf.GetHighlight( currentTheme, 'cursor', fgBg='fg'), 'width': idleConf.GetOption('main', 'EditorWindow', 'width'), 'height': idleConf.GetOption('main', 'EditorWindow', 'height'), } fontWeight = 'normal' if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'): fontWeight = 'bold' textcf['font'] = (idleConf.GetOption('main', 'EditorWindow', 'font'), idleConf.GetOption('main', 'EditorWindow', 'font-size'), fontWeight) # override defaults with any user-specified settings textcf.update(kwargs) ScrolledText.__init__(self, master, **textcf)
def reload(cls): "Load class variables from config." cls.context_depth = idleConf.GetOption("extensions", "CodeContext", "maxlines", type="int", default=15) cls.colors = idleConf.GetHighlight(idleConf.CurrentTheme(), 'context')
def toggle_code_context_event(self, event=None): """Toggle code context display. If self.context doesn't exist, create it to match the size of the editor window text (toggle on). If it does exist, destroy it (toggle off). Return 'break' to complete the processing of the binding. """ if self.context is None: # Calculate the border width and horizontal padding required to # align the context with the text in the main Text widget. # # All values are passed through getint(), since some # values may be pixel objects, which can't simply be added to ints. widgets = self.editwin.text, self.editwin.text_frame # Calculate the required horizontal padding and border width. padx = 0 border = 0 for widget in widgets: info = (widget.grid_info() if widget is self.editwin.text else widget.pack_info()) padx += widget.tk.getint(info['padx']) padx += widget.tk.getint(widget.cget('padx')) border += widget.tk.getint(widget.cget('border')) context = self.context = Text( self.editwin.text_frame, height=1, width=1, # Don't request more than we get. highlightthickness=0, padx=padx, border=border, relief=SUNKEN, state='disabled') self.update_font() self.update_highlight_colors() context.bind('<ButtonRelease-1>', self.jumptoline) # Get the current context and initiate the recurring update event. self.timer_event() # Grid the context widget above the text widget. context.grid(row=0, column=1, sticky=NSEW) line_number_colors = idleConf.GetHighlight(idleConf.CurrentTheme(), 'linenumber') self.cell00 = Frame(self.editwin.text_frame, bg=line_number_colors['background']) self.cell00.grid(row=0, column=0, sticky=NSEW) menu_status = 'Hide' else: self.context.destroy() self.context = None self.cell00.destroy() self.cell00 = None self.text.after_cancel(self.t1) self._reset() menu_status = 'Show' self.editwin.update_menu_label(menu='options', index='*ode*ontext', label=f'{menu_status} Code Context') return "break"
def reload(cls): cls.STYLE = idleConf.GetOption( 'extensions','ParenMatch','style', default='opener') cls.FLASH_DELAY = idleConf.GetOption( 'extensions','ParenMatch','flash-delay', type='int',default=500) cls.BELL = idleConf.GetOption( 'extensions','ParenMatch','bell', type='bool', default=1) cls.HILITE_CONFIG = idleConf.GetHighlight(idleConf.CurrentTheme(), 'hilite')
def color_breakpoint_text(self, color=True): """Turn colorizing of breakpoint text on or off""" if self.io is None: return if color: theme = idleConf.CurrentTheme() cfg = idleConf.GetHighlight(theme, 'break') else: cfg = {'foreground': '', 'background': ''} self.text.tag_config('BREAK', cfg)
def show_find_marks(self): # Get the highlight colors for 'hit' # Do this here (and not in __init__) for color config changes to take # effect immediately currentTheme = idleConf.CurrentTheme() mark_fg = idleConf.GetHighlight(currentTheme, 'hit', fgBg='fg') mark_bg = idleConf.GetHighlight(currentTheme, 'hit', fgBg='bg') self.text.tag_configure("findmark", foreground=mark_fg, background=mark_bg)
def update_colors(self): """Update the sidebar text colors, usually after config changes.""" colors = idleConf.GetHighlight(idleConf.CurrentTheme(), 'linenumber') foreground = colors['foreground'] background = colors['background'] self.sidebar_text.config( fg=foreground, bg=background, selectforeground=foreground, selectbackground=background, inactiveselectbackground=background, )
def LoadTagDefs(self): ColorDelegator.LoadTagDefs(self) theme = idleConf.CurrentTheme() self.tagdefs.update({ 'stdin': { 'background': None, 'foreground': None }, 'stdout': idleConf.GetHighlight(theme, 'stdout'), 'stderr': idleConf.GetHighlight(theme, 'stderr'), 'console': idleConf.GetHighlight(theme, 'console') })
def create_linenumbers(self): """ Create the widget for displaying line numbers. """ colors = idleConf.GetHighlight(idleConf.CurrentTheme(), 'stdout') editwin = self.editwin text = self.text text_frame = editwin.text_frame self.textln = textln = Text(text_frame, bg=colors['background'], fg=colors['foreground'], width=self.width, height=1, wrap=NONE, highlightthickness=0) # adjust font textln.config( font=(idleConf.GetOption('main', 'EditorWindow', 'font'), idleConf.GetOption('main', 'EditorWindow', 'font-size'))) textln.bind("<FocusIn>", self.focus_in_event) textln.bind('<Button-1>', self.button_ignore) textln.bind('<Button-2>', self.button_ignore) textln.bind('<Button-3>', self.button_ignore) textln.bind('<B1-Motion>', self.button_ignore) textln.bind('<B2-Motion>', self.button_ignore) textln.bind('<B3-Motion>', self.button_ignore) textln.bind("<Button-4>", self.button4) textln.bind("<Button-5>", self.button5) textln.tag_config('LINE', justify=RIGHT) textln.insert(END, '1') textln.tag_add('LINE', '1.0', END) # start the line numbers self.per = per = Percolator(textln) self.line_delegator = LineDelegator() per.insertfilter(self.line_delegator) textln._insert = self.line_delegator.delegate.insert textln._delete = self.line_delegator.delegate.delete lines = LineNumberDelegator(self) if _AFTER_UNDO: # idlelib.percolator's .insertfilter should have an # "after=" argument lines.setdelegate(editwin.undo.delegate) editwin.undo.setdelegate(lines) else: editwin.per.insertfilter(lines) editwin.vbar['command'] = self.vbar_split editwin.text['yscrollcommand'] = self.yscroll_split
def remote_stack_viewer(self): from idlelib import debugobj_r oid = self.rpcclt.remotequeue('exec', 'stackviewer', ('flist', ), {}) if oid is None: self.tkconsole.root.bell() return item = debugobj_r.StubObjectTreeItem(self.rpcclt, oid) from idlelib.tree import ScrolledCanvas, TreeNode top = Toplevel(self.tkconsole.root) theme = idleConf.CurrentTheme() background = idleConf.GetHighlight(theme, 'normal')['background'] sc = ScrolledCanvas(top, bg=background, highlightthickness=0) sc.frame.pack(expand=1, fill='both') node = TreeNode(sc.canvas, None, item) node.expand()
def color_config(text): """Set color opitons of Text widget. Should be called whenever ColorDelegator is called. """ theme = idleConf.CurrentTheme() normal_colors = idleConf.GetHighlight(theme, 'normal') cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg') select_colors = idleConf.GetHighlight(theme, 'hilite') text.config(foreground=normal_colors['foreground'], background=normal_colors['background'], insertbackground=cursor_color, selectforeground=select_colors['foreground'], selectbackground=select_colors['background'], inactiveselectbackground=select_colors['background'])
def LoadTagDefs(self): theme = idleConf.CurrentTheme() self.tagdefs = { "COMMENT": idleConf.GetHighlight(theme, "comment"), "KEYWORD": idleConf.GetHighlight(theme, "keyword"), "BUILTIN": idleConf.GetHighlight(theme, "builtin"), "STRING": idleConf.GetHighlight(theme, "string"), "DEFINITION": idleConf.GetHighlight(theme, "definition"), "SYNC": {'background':None,'foreground':None}, "TODO": {'background':None,'foreground':None}, "ERROR": idleConf.GetHighlight(theme, "error"), # The following is used by ReplaceDialog: "hit": idleConf.GetHighlight(theme, "hit"), } if DEBUG: print('tagdefs',self.tagdefs)
def color_config(text): # Called from htest, Editor, and Turtle Demo. '''Set color opitons of Text widget. Should be called whenever ColorDelegator is called. ''' # Not automatic because ColorDelegator does not know 'text'. theme = idleConf.CurrentTheme() normal_colors = idleConf.GetHighlight(theme, 'normal') cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg') select_colors = idleConf.GetHighlight(theme, 'hilite') text.config( foreground=normal_colors['foreground'], background=normal_colors['background'], insertbackground=cursor_color, selectforeground=select_colors['foreground'], selectbackground=select_colors['background'], inactiveselectbackground=select_colors['background'], # new in 8.5 )
def color_config(text): """Set color options of Text widget. If ColorDelegator is used, this should be called first. """ # Called from htest, TextFrame, Editor, and Turtledemo. # Not automatic because ColorDelegator does not know 'text'. theme = idleConf.CurrentTheme() normal_colors = idleConf.GetHighlight(theme, 'normal') cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg') select_colors = idleConf.GetHighlight(theme, 'hilite') text.config( foreground=normal_colors['foreground'], background=normal_colors['background'], insertbackground=cursor_color, selectforeground=select_colors['foreground'], selectbackground=select_colors['background'], inactiveselectbackground=select_colors['background'], # new in 8.5 )
def __init__(self, master=None, **kwargs): """ Initializer. Get default settings from user's IDLE configuration. """ textcf = self._initialize_config(idleConf.CurrentTheme()) if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'): font_weight = 'bold' else: font_weight = 'normal' textcf['font'] = (idleConf.GetOption('main', 'EditorWindow', 'font'), idleConf.GetOption('main', 'EditorWindow', 'font-size'), font_weight) # override defaults with any user-specified settings textcf.update(kwargs) super().__init__(master, **textcf)
def LoadTagDefs(self): theme = idleConf.CurrentTheme() self.tagdefs = { 'COMMENT': idleConf.GetHighlight(theme, 'comment'), 'KEYWORD': idleConf.GetHighlight(theme, 'keyword'), 'BUILTIN': idleConf.GetHighlight(theme, 'builtin'), 'STRING': idleConf.GetHighlight(theme, 'string'), 'DEFINITION': idleConf.GetHighlight(theme, 'definition'), 'SYNC': { 'background': None, 'foreground': None }, 'TODO': { 'background': None, 'foreground': None }, 'ERROR': idleConf.GetHighlight(theme, 'error'), 'hit': idleConf.GetHighlight(theme, 'hit') } if DEBUG: print('tagdefs', self.tagdefs)
def init(self, flist): self.flist = flist pyclbr._modules.clear() self.top = top = ListedToplevel(flist.root) top.protocol('WM_DELETE_WINDOW', self.close) top.bind('<Escape>', self.close) if self._htest: top.geometry( '+%d+%d' % (flist.root.winfo_rootx(), flist.root.winfo_rooty() + 200)) self.settitle() top.focus_set() theme = idleConf.CurrentTheme() background = idleConf.GetHighlight(theme, 'normal')['background'] sc = ScrolledCanvas(top, bg=background, highlightthickness=0, takefocus=1) sc.frame.pack(expand=1, fill='both') item = self.rootnode() self.node = node = TreeNode(sc.canvas, None, item) node.update() node.expand()
def drawtext(self): textx = self.x + 20 - 1 texty = self.y - 4 labeltext = self.item.GetLabelText() if labeltext: id = self.canvas.create_text(textx, texty, anchor='nw', text=labeltext) self.canvas.tag_bind(id, '<1>', self.select) self.canvas.tag_bind(id, '<Double-1>', self.flip) x0, y0, x1, y1 = self.canvas.bbox(id) textx = max(x1, 200) + 10 text = self.item.GetText() or '<no text>' try: self.entry except AttributeError: pass else: self.edit_finish() try: self.label except AttributeError: self.label = Label(self.canvas, text=text, bd=0, padx=2, pady=2) theme = idleConf.CurrentTheme() if self.selected: self.label.configure(idleConf.GetHighlight(theme, 'hilite')) else: self.label.configure(idleConf.GetHighlight(theme, 'normal')) id = self.canvas.create_window(textx, texty, anchor='nw', window=self.label) self.label.bind('<1>', self.select_or_edit) self.label.bind('<Double-1>', self.flip) self.text_id = id
def update_colors(self): """Update the sidebar text colors, usually after config changes.""" colors = idleConf.GetHighlight(idleConf.CurrentTheme(), 'normal') self._update_colors(foreground=colors['foreground'], background=colors['background'])
class ParenMatch: """Highlight matching openers and closers, (), [], and {}. There are three supported styles of paren matching. When a right paren (opener) is typed: opener -- highlight the matching left paren (closer); parens -- highlight the left and right parens (opener and closer); expression -- highlight the entire expression from opener to closer. (For back compatibility, 'default' is a synonym for 'opener'). Flash-delay is the maximum milliseconds the highlighting remains. Any cursor movement (key press or click) before that removes the highlight. If flash-delay is 0, there is no maximum. TODO: - Augment bell() with mismatch warning in status window. - Highlight when cursor is moved to the right of a closer. This might be too expensive to check. """ menudefs = [('edit', [ ("Show surrounding parens", "<<flash-paren>>"), ])] STYLE = idleConf.GetOption('extensions', 'ParenMatch', 'style', default='expression') FLASH_DELAY = idleConf.GetOption('extensions', 'ParenMatch', 'flash-delay', type='int', default=500) BELL = idleConf.GetOption('extensions', 'ParenMatch', 'bell', type='bool', default=1) HILITE_CONFIG = idleConf.GetHighlight(idleConf.CurrentTheme(), 'hilite') RESTORE_VIRTUAL_EVENT_NAME = "<<parenmatch-check-restore>>" # We want the restore event be called before the usual return and # backspace events. RESTORE_SEQUENCES = ("<KeyPress>", "<ButtonPress>", "<Key-Return>", "<Key-BackSpace>") def __init__(self, editwin): self.editwin = editwin self.text = editwin.text # Bind the check-restore event to the function restore_event, # so that we can then use activate_restore (which calls event_add) # and deactivate_restore (which calls event_delete). editwin.text.bind(self.RESTORE_VIRTUAL_EVENT_NAME, self.restore_event) self.bell = self.text.bell if self.BELL else lambda: None self.counter = 0 self.is_restore_active = 0 self.set_style(self.STYLE) def activate_restore(self): "Activate mechanism to restore text from highlighting." if not self.is_restore_active: for seq in self.RESTORE_SEQUENCES: self.text.event_add(self.RESTORE_VIRTUAL_EVENT_NAME, seq) self.is_restore_active = True def deactivate_restore(self): "Remove restore event bindings." if self.is_restore_active: for seq in self.RESTORE_SEQUENCES: self.text.event_delete(self.RESTORE_VIRTUAL_EVENT_NAME, seq) self.is_restore_active = False def set_style(self, style): "Set tag and timeout functions." self.STYLE = style self.create_tag = (self.create_tag_opener if style in {"opener", "default"} else self.create_tag_parens if style == "parens" else self.create_tag_expression ) # "expression" or unknown self.set_timeout = (self.set_timeout_last if self.FLASH_DELAY else self.set_timeout_none) def flash_paren_event(self, event): "Handle editor 'show surrounding parens' event (menu or shortcut)." indices = (HyperParser(self.editwin, "insert").get_surrounding_brackets()) if indices is None: self.bell() return "break" self.activate_restore() self.create_tag(indices) self.set_timeout() return "break" def paren_closed_event(self, event): "Handle user input of closer." # If user bound non-closer to <<paren-closed>>, quit. closer = self.text.get("insert-1c") if closer not in _openers: return hp = HyperParser(self.editwin, "insert-1c") if not hp.is_in_code(): return indices = hp.get_surrounding_brackets(_openers[closer], True) if indices is None: self.bell() return self.activate_restore() self.create_tag(indices) self.set_timeout() return def restore_event(self, event=None): "Remove effect of doing match." self.text.tag_delete("paren") self.deactivate_restore() self.counter += 1 # disable the last timer, if there is one. def handle_restore_timer(self, timer_count): if timer_count == self.counter: self.restore_event() # any one of the create_tag_XXX methods can be used depending on # the style def create_tag_opener(self, indices): """Highlight the single paren that matches""" self.text.tag_add("paren", indices[0]) self.text.tag_config("paren", self.HILITE_CONFIG) def create_tag_parens(self, indices): """Highlight the left and right parens""" if self.text.get(indices[1]) in (')', ']', '}'): rightindex = indices[1] + "+1c" else: rightindex = indices[1] self.text.tag_add("paren", indices[0], indices[0] + "+1c", rightindex + "-1c", rightindex) self.text.tag_config("paren", self.HILITE_CONFIG) def create_tag_expression(self, indices): """Highlight the entire expression""" if self.text.get(indices[1]) in (')', ']', '}'): rightindex = indices[1] + "+1c" else: rightindex = indices[1] self.text.tag_add("paren", indices[0], rightindex) 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 or the insert has moved""" # After CHECK_DELAY, call a function which disables the "paren" tag # if the event is for the most recent timer and the insert has changed, # or schedules another call for itself. self.counter += 1 def callme(callme, self=self, c=self.counter, index=self.text.index("insert")): if index != self.text.index("insert"): self.handle_restore_timer(c) else: self.editwin.text_frame.after(CHECK_DELAY, callme, callme) self.editwin.text_frame.after(CHECK_DELAY, callme, callme) def set_timeout_last(self): """The last highlight created will be removed after FLASH_DELAY millisecs""" # associate a counter with an event; only disable the "paren" # tag if the event is for the most recent timer. self.counter += 1 self.editwin.text_frame.after( self.FLASH_DELAY, lambda self=self, c=self.counter: self.handle_restore_timer(c))
def update_highlight_colors(self): if self.context is not None: colors = idleConf.GetHighlight(idleConf.CurrentTheme(), 'context') self.context['background'] = colors['background'] self.context['foreground'] = colors['foreground']
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: - 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 = [('edit', [('Show surrounding parens', '<<flash-paren>>')])] 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) RESTORE_VIRTUAL_EVENT_NAME = '<<parenmatch-check-restore>>' RESTORE_SEQUENCES = ('<KeyPress>', '<ButtonPress>', '<Key-Return>', '<Key-BackSpace>') def __init__(self, editwin): self.editwin = editwin self.text = editwin.text editwin.text.bind(self.RESTORE_VIRTUAL_EVENT_NAME, self.restore_event) self.bell = self.text.bell if self.BELL else lambda: None self.counter = 0 self.is_restore_active = 0 self.set_style(self.STYLE) def activate_restore(self): if not self.is_restore_active: for seq in self.RESTORE_SEQUENCES: self.text.event_add(self.RESTORE_VIRTUAL_EVENT_NAME, seq) self.is_restore_active = True def deactivate_restore(self): if self.is_restore_active: for seq in self.RESTORE_SEQUENCES: self.text.event_delete(self.RESTORE_VIRTUAL_EVENT_NAME, seq) self.is_restore_active = False 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_paren_event(self, event): indices = HyperParser(self.editwin, 'insert').get_surrounding_brackets() if indices is None: self.bell() return self.activate_restore() self.create_tag(indices) self.set_timeout_last() def paren_closed_event(self, event): closer = self.text.get('insert-1c') if closer not in _openers: return hp = HyperParser(self.editwin, 'insert-1c') if not hp.is_in_code(): return indices = hp.get_surrounding_brackets(_openers[closer], True) if indices is None: self.bell() return self.activate_restore() self.create_tag(indices) self.set_timeout() def restore_event(self, event=None): self.text.tag_delete('paren') self.deactivate_restore() self.counter += 1 def handle_restore_timer(self, timer_count): if timer_count == self.counter: self.restore_event() def create_tag_default(self, indices): """Highlight the single paren that matches""" self.text.tag_add('paren', indices[0]) self.text.tag_config('paren', self.HILITE_CONFIG) def create_tag_expression(self, indices): """Highlight the entire expression""" if self.text.get(indices[1]) in (')', ']', '}'): rightindex = indices[1] + '+1c' else: rightindex = indices[1] self.text.tag_add('paren', indices[0], rightindex) self.text.tag_config('paren', self.HILITE_CONFIG) def set_timeout_none(self): """Highlight will remain until user input turns it off or the insert has moved""" self.counter += 1 def callme(callme, self=self, c=self.counter, index=self.text.index('insert')): if index != self.text.index('insert'): self.handle_restore_timer(c) else: self.editwin.text_frame.after(CHECK_DELAY, callme, callme) self.editwin.text_frame.after(CHECK_DELAY, callme, callme) def set_timeout_last(self): """The last highlight created will be removed after .5 sec""" self.counter += 1 self.editwin.text_frame.after( self.FLASH_DELAY, lambda self=self, c=self.counter: self.handle_restore_timer(c))
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: - 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 = [('edit', [ ("Show surrounding parens", "<<flash-paren>>"), ])] 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) RESTORE_VIRTUAL_EVENT_NAME = "<<parenmatch-check-restore>>" # We want the restore event be called before the usual return and # backspace events. RESTORE_SEQUENCES = ("<KeyPress>", "<ButtonPress>", "<Key-Return>", "<Key-BackSpace>") def __init__(self, editwin): self.editwin = editwin self.text = editwin.text # Bind the check-restore event to the function restore_event, # so that we can then use activate_restore (which calls event_add) # and deactivate_restore (which calls event_delete). editwin.text.bind(self.RESTORE_VIRTUAL_EVENT_NAME, self.restore_event) self.bell = self.text.bell if self.BELL else lambda: None self.counter = 0 self.is_restore_active = 0 self.set_style(self.STYLE) def activate_restore(self): if not self.is_restore_active: for seq in self.RESTORE_SEQUENCES: self.text.event_add(self.RESTORE_VIRTUAL_EVENT_NAME, seq) self.is_restore_active = True def deactivate_restore(self): if self.is_restore_active: for seq in self.RESTORE_SEQUENCES: self.text.event_delete(self.RESTORE_VIRTUAL_EVENT_NAME, seq) self.is_restore_active = False 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_paren_event(self, event): indices = (HyperParser(self.editwin, "insert").get_surrounding_brackets()) if indices is None: self.bell() return self.activate_restore() self.create_tag(indices) self.set_timeout_last() def paren_closed_event(self, event): # If it was a shortcut and not really a closing paren, quit. closer = self.text.get("insert-1c") if closer not in _openers: return hp = HyperParser(self.editwin, "insert-1c") if not hp.is_in_code(): return indices = hp.get_surrounding_brackets(_openers[closer], True) if indices is None: self.bell() return self.activate_restore() self.create_tag(indices) self.set_timeout() def restore_event(self, event=None): self.text.tag_delete("paren") self.deactivate_restore() self.counter += 1 # disable the last timer, if there is one. def handle_restore_timer(self, timer_count): if timer_count == self.counter: self.restore_event() # any one of the create_tag_XXX methods can be used depending on # the style def create_tag_default(self, indices): """Highlight the single paren that matches""" self.text.tag_add("paren", indices[0]) self.text.tag_config("paren", self.HILITE_CONFIG) def create_tag_expression(self, indices): """Highlight the entire expression""" if self.text.get(indices[1]) in (')', ']', '}'): rightindex = indices[1] + "+1c" else: rightindex = indices[1] self.text.tag_add("paren", indices[0], rightindex) 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 or the insert has moved""" # After CHECK_DELAY, call a function which disables the "paren" tag # if the event is for the most recent timer and the insert has changed, # or schedules another call for itself. self.counter += 1 def callme(callme, self=self, c=self.counter, index=self.text.index("insert")): if index != self.text.index("insert"): self.handle_restore_timer(c) else: self.editwin.text_frame.after(CHECK_DELAY, callme, callme) self.editwin.text_frame.after(CHECK_DELAY, callme, callme) 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.counter += 1 self.editwin.text_frame.after( self.FLASH_DELAY, lambda self=self, c=self.counter: self.handle_restore_timer(c))