""" Squeezer - using this extension will make long texts become a small button. """ import re from PyShell import PyShell from configHandler import idleConf import Tkinter import os # define IDLEfork-infrastructure specific functions def _get_base_text(editwin): "Return the base Text widget of an editwin, which can be changed before "\ "the iomark." return editwin.per.bottom def _add_to_rmenu(editwin, specs): "Add specs to the right-click menu of the given editwin." editwin.rmenu_specs = editwin.rmenu_specs + specs # define a function to count the number of lines in a given string _TABWIDTH = 8 _LINEWIDTH = 80 _tab_newline_re = re.compile(r"[\t\n]") _tab_table = map(lambda ncols: ncols+_TABWIDTH-(ncols%_TABWIDTH), range(_LINEWIDTH)) def _countlines(s): pos = 0 linecount = 1 colcount = 0 while 1: # find the next tab or newline m = _tab_newline_re.search(s, pos) if m: numchars = m.start() - pos else: numchars = len(s) - pos # process the normal chars up to it div = (colcount + numchars) // _LINEWIDTH linecount += div colcount = colcount + numchars - div*_LINEWIDTH pos += numchars # if there's no tab of newline, just end of string, quit if pos == len(s): break if s[pos] == '\n': linecount += 1 colcount = 0 else: assert s[pos] == '\t' colcount = _tab_table[colcount] pos += 1 return linecount # define the extension's classes class ExpandingButton(Tkinter.Button): def __init__(self, s, tags, numoflines, squeezer): self.s = s self.tags = tags self.squeezer = squeezer self.editwin = editwin = squeezer.editwin self.text = text = editwin.text caption = "Squeezed text (about %d lines). "\ "Double-click to expand, middle-click to copy" % numoflines if squeezer._PREVIEW_COMMAND: caption += ", right-click to preview." else: caption += "." Tkinter.Button.__init__(self, text, text=caption, background="#FFFFC0", activebackground="#FFFFE0") self.bind("", self.expand) self.bind("", self.copy) if squeezer._PREVIEW_COMMAND: self.bind("", self.preview) self.selection_handle(lambda offset,length: s[int(offset):int(offset)+int(length)]) def expand(self, event): # We must use the original insert and delete methods of the Text widget, # to be able to change text before the iomark. basetext = _get_base_text(self.editwin) basetext.insert(self.text.index(self), self.s, self.tags) basetext.delete(self) self.squeezer.expandingbuttons.remove(self) def copy(self, event): self.selection_own() def preview(self, event): from tempfile import mktemp fn = mktemp("longidletext") f = open(fn, "w") f.write(self.s) f.close() os.system(self.squeezer._PREVIEW_COMMAND % {"fn":fn}) class Squeezer: _MAX_NUM_OF_LINES = idleConf.GetOption("extensions", "Squeezer", "max-num-of-lines", type="int", default=30) _PREVIEW_COMMAND = idleConf.GetOption( "extensions", "Squeezer", "preview-command-"+{"nt":"win"}.get(os.name, os.name), default="") menudefs = [ ('edit', [ None, # Separator ("Expand last squeezed text", "<>"), ]) ] if _PREVIEW_COMMAND: menudefs[0][1].append(("Preview last squeezed text", "<>")) def __init__(self, editwin): self.editwin = editwin self.text = text = editwin.text self.expandingbuttons = [] if isinstance(editwin, PyShell): # If we get a PyShell instance, replace its write method with a # wrapper, which inserts an ExpandingButton instead of a long text. def mywrite(s, tags=(), write=editwin.write): if tags != "stdout": return write(s, tags) else: numoflines = _countlines(s) if numoflines < self._MAX_NUM_OF_LINES: return write(s, tags) else: expandingbutton = ExpandingButton(s, tags, numoflines, self) text.mark_gravity("iomark", Tkinter.RIGHT) text.window_create("iomark",window=expandingbutton, padx=3, pady=5) text.see("iomark") text.update() text.mark_gravity("iomark", Tkinter.LEFT) self.expandingbuttons.append(expandingbutton) editwin.write = mywrite # Add squeeze-current-text to the right-click menu text.bind("<>", self.squeeze_current_text_event) _add_to_rmenu(editwin, [("Squeeze current text", "<>")]) def expand_last_squeezed_event(self, event): if len(self.expandingbuttons) > 0: self.expandingbuttons[-1].expand(event) else: self.text.bell() return "break" def preview_last_squeezed_event(self, event): if self._PREVIEW_COMMAND and len(self.expandingbuttons) > 0: self.expandingbuttons[-1].preview(event) else: self.text.bell() return "break" def squeeze_current_text_event(self, event): if "stdout" in self.text.tag_names(Tkinter.INSERT): # find the range to squeeze start, end = self.text.tag_prevrange("stdout", Tkinter.INSERT) s = self.text.get(start, end) # if the last char is a newline, remove it from the range if len(s) > 0 and s[-1] == '\n': end = self.text.index("%s-1c" % end) s = s[:-1] # delete the text _get_base_text(self.editwin).delete(start, end) # prepare an ExpandingButton numoflines = _countlines(s) expandingbutton = ExpandingButton(s, "stdout", numoflines, self) # insert the ExpandingButton to the Text self.text.window_create(start, window=expandingbutton, padx=3, pady=5) # insert the ExpandingButton to the list of ExpandingButtons i = len(self.expandingbuttons) while i > 0 and self.text.compare(self.expandingbuttons[i-1], ">", expandingbutton): i -= 1 self.expandingbuttons.insert(i, expandingbutton) else: self.text.bell() return "break"