--- W:\Download\Squeezer.py Sun Jul 30 11:58:12 2006 +++ C:\Python24\Lib\idlelib\Squeezer.py Sun Jul 30 11:15:08 2006 @@ -6,6 +6,7 @@ from PyShell import PyShell from configHandler import idleConf import Tkinter +import tkFont import os @@ -22,43 +23,48 @@ # 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): +_tab_table_cache = {} + +def _countlines(s, linewidth=_LINEWIDTH, tabwidth=_TABWIDTH): + if (tabwidth, linewidth) not in _tab_table_cache: + _tab_table_cache[(tabwidth, linewidth)] = \ + [ncols+tabwidth-(ncols%tabwidth) for ncols in range(linewidth)] + tab_table = _tab_table_cache[(tabwidth, linewidth)] + pos = 0 linecount = 1 - colcount = 0 + current_column = 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 + for m in _tab_newline_re.finditer(s): + # process the normal chars up to tab or newline + numchars = m.start() - pos + if numchars > 0: # must special-case, otherwise divmod(-1, linewidth) + # If the length was exactly linewidth, divmod would give + # (1,0), even though a new line hadn't yet been started. + # Therefore subtract 1 before doing divmod, and later add + # 1 to the column to compensate. + lines, column = divmod(current_column + numchars - 1, linewidth) + linecount += lines + current_column = column + 1 + pos += numchars + # deal with tab or newline if s[pos] == '\n': linecount += 1 - colcount = 0 + current_column = 0 else: assert s[pos] == '\t' - colcount = _tab_table[colcount] - pos += 1 + current_column = tab_table[current_column] + + pos += 1 # after the tab or newline + # process remaining chars (no more tabs or newlines) + numchars = len(s) - pos + if numchars > 0: # must special-case, otherwise divmod(-1, linewidth) + linecount += (current_column + numchars - 1) // linewidth return linecount @@ -117,7 +123,7 @@ _PREVIEW_COMMAND = idleConf.GetOption( "extensions", "Squeezer", "preview-command-"+{"nt":"win"}.get(os.name, os.name), - default="") + default="", raw=True) menudefs = [ ('edit', [ @@ -141,7 +147,7 @@ if tags != "stdout": return write(s, tags) else: - numoflines = _countlines(s) + numoflines = self.count_lines(s) if numoflines < self._MAX_NUM_OF_LINES: return write(s, tags) else: @@ -162,6 +168,30 @@ _add_to_rmenu(editwin, [("Squeeze current text", "<>")]) + def count_lines(self, s): + "Calculate number of lines in given text.\n\n" \ + "Before calculation, the tab width and line length of the text are" \ + "fetched, so that up-to-date values are used." + # Tab width is configurable + tabwidth = self.editwin.get_tabwidth() + + # Get the Text widget's size + linewidth = self.editwin.text.winfo_width() + # Deduct the border and padding + linewidth -= 2*sum([int(self.editwin.text.cget(opt)) + for opt in ('border','padx')]) + + # Get the Text widget's font + font = tkFont.Font(self.editwin.text, + name=self.editwin.text.cget('font')) + # Divide the size of the Text widget by the font's width. + # According to Tk8.4 docs, the Text widget's width is set + # according to the width of its font's '0' (zero) character, + # so we will use this as an approximation. + linewidth //= font.measure('0') + + return _countlines(s, linewidth, tabwidth) + def expand_last_squeezed_event(self, event): if len(self.expandingbuttons) > 0: self.expandingbuttons[-1].expand(event) @@ -177,29 +207,35 @@ 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) + tag_names = self.text.tag_names(Tkinter.INSERT) + for tag_name in ("stdout","stderr"): + if tag_name in tag_names: + break else: self.text.bell() + return "break" + + # find the range to squeeze + start, end = self.text.tag_prevrange(tag_name, Tkinter.INSERT+"+1c") + 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 = self.count_lines(s) + expandingbutton = ExpandingButton(s, tag_name, 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) + return "break"