| 
									
										
										
										
											2018-09-25 15:10:14 +03:00
										 |  |  | """An IDLE extension to avoid having very long texts printed in the shell.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | A common problem in IDLE's interactive shell is printing of large amounts of | 
					
						
							|  |  |  | text into the shell. This makes looking at the previous history difficult. | 
					
						
							|  |  |  | Worse, this can cause IDLE to become very slow, even to the point of being | 
					
						
							|  |  |  | completely unusable. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | This extension will automatically replace long texts with a small button. | 
					
						
							| 
									
										
										
										
											2019-06-03 09:51:15 +05:30
										 |  |  | Double-clicking this button will remove it and insert the original text instead. | 
					
						
							| 
									
										
										
										
											2018-09-25 15:10:14 +03:00
										 |  |  | Middle-clicking will copy the text to the clipboard. Right-clicking will open | 
					
						
							|  |  |  | the text in a separate viewing window. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Additionally, any output can be manually "squeezed" by the user. This includes | 
					
						
							|  |  |  | output written to the standard error stream ("stderr"), such as exception | 
					
						
							|  |  |  | messages and their tracebacks. | 
					
						
							|  |  |  | """
 | 
					
						
							|  |  |  | import re | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import tkinter as tk | 
					
						
							| 
									
										
										
										
											2021-01-25 06:33:18 -05:00
										 |  |  | from tkinter import messagebox | 
					
						
							| 
									
										
										
										
											2018-09-25 15:10:14 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | from idlelib.config import idleConf | 
					
						
							|  |  |  | from idlelib.textview import view_text | 
					
						
							|  |  |  | from idlelib.tooltip import Hovertip | 
					
						
							|  |  |  | from idlelib import macosx | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-13 17:01:50 +02:00
										 |  |  | def count_lines_with_wrapping(s, linewidth=80): | 
					
						
							| 
									
										
										
										
											2018-09-25 15:10:14 +03:00
										 |  |  |     """Count the number of lines in a given string.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Lines are counted as if the string was wrapped so that lines are never over | 
					
						
							|  |  |  |     linewidth characters long. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Tabs are considered tabwidth characters long. | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2019-01-13 17:01:50 +02:00
										 |  |  |     tabwidth = 8  # Currently always true in Shell. | 
					
						
							| 
									
										
										
										
											2018-09-25 15:10:14 +03:00
										 |  |  |     pos = 0 | 
					
						
							|  |  |  |     linecount = 1 | 
					
						
							|  |  |  |     current_column = 0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for m in re.finditer(r"[\t\n]", s): | 
					
						
							| 
									
										
										
										
											2019-01-13 17:01:50 +02:00
										 |  |  |         # Process the normal chars up to tab or newline. | 
					
						
							| 
									
										
										
										
											2018-09-25 15:10:14 +03:00
										 |  |  |         numchars = m.start() - pos | 
					
						
							|  |  |  |         pos += numchars | 
					
						
							|  |  |  |         current_column += numchars | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-13 17:01:50 +02:00
										 |  |  |         # Deal with tab or newline. | 
					
						
							| 
									
										
										
										
											2018-09-25 15:10:14 +03:00
										 |  |  |         if s[pos] == '\n': | 
					
						
							| 
									
										
										
										
											2019-01-13 17:01:50 +02:00
										 |  |  |             # Avoid the `current_column == 0` edge-case, and while we're | 
					
						
							|  |  |  |             # at it, don't bother adding 0. | 
					
						
							| 
									
										
										
										
											2018-12-24 14:05:51 +02:00
										 |  |  |             if current_column > linewidth: | 
					
						
							| 
									
										
										
										
											2019-01-13 17:01:50 +02:00
										 |  |  |                 # If the current column was exactly linewidth, divmod | 
					
						
							|  |  |  |                 # would give (1,0), even though a new line hadn't yet | 
					
						
							|  |  |  |                 # been started. The same is true if length is any exact | 
					
						
							|  |  |  |                 # multiple of linewidth. Therefore, subtract 1 before | 
					
						
							|  |  |  |                 # dividing a non-empty line. | 
					
						
							| 
									
										
										
										
											2018-12-24 14:05:51 +02:00
										 |  |  |                 linecount += (current_column - 1) // linewidth | 
					
						
							| 
									
										
										
										
											2018-09-25 15:10:14 +03:00
										 |  |  |             linecount += 1 | 
					
						
							|  |  |  |             current_column = 0 | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             assert s[pos] == '\t' | 
					
						
							|  |  |  |             current_column += tabwidth - (current_column % tabwidth) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-13 17:01:50 +02:00
										 |  |  |             # If a tab passes the end of the line, consider the entire | 
					
						
							|  |  |  |             # tab as being on the next line. | 
					
						
							| 
									
										
										
										
											2018-09-25 15:10:14 +03:00
										 |  |  |             if current_column > linewidth: | 
					
						
							|  |  |  |                 linecount += 1 | 
					
						
							|  |  |  |                 current_column = tabwidth | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-13 17:01:50 +02:00
										 |  |  |         pos += 1 # After the tab or newline. | 
					
						
							| 
									
										
										
										
											2018-09-25 15:10:14 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-13 17:01:50 +02:00
										 |  |  |     # Process remaining chars (no more tabs or newlines). | 
					
						
							| 
									
										
										
										
											2018-09-25 15:10:14 +03:00
										 |  |  |     current_column += len(s) - pos | 
					
						
							| 
									
										
										
										
											2019-01-13 17:01:50 +02:00
										 |  |  |     # Avoid divmod(-1, linewidth). | 
					
						
							| 
									
										
										
										
											2018-09-25 15:10:14 +03:00
										 |  |  |     if current_column > 0: | 
					
						
							|  |  |  |         linecount += (current_column - 1) // linewidth | 
					
						
							|  |  |  |     else: | 
					
						
							| 
									
										
										
										
											2019-01-13 17:01:50 +02:00
										 |  |  |         # Text ended with newline; don't count an extra line after it. | 
					
						
							| 
									
										
										
										
											2018-09-25 15:10:14 +03:00
										 |  |  |         linecount -= 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return linecount | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class ExpandingButton(tk.Button): | 
					
						
							|  |  |  |     """Class for the "squeezed" text buttons used by Squeezer
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     These buttons are displayed inside a Tk Text widget in place of text. A | 
					
						
							|  |  |  |     user can then use the button to replace it with the original text, copy | 
					
						
							|  |  |  |     the original text to the clipboard or view the original text in a separate | 
					
						
							|  |  |  |     window. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Each button is tied to a Squeezer instance, and it knows to update the | 
					
						
							|  |  |  |     Squeezer instance when it is expanded (and therefore removed). | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     def __init__(self, s, tags, numoflines, squeezer): | 
					
						
							|  |  |  |         self.s = s | 
					
						
							|  |  |  |         self.tags = tags | 
					
						
							|  |  |  |         self.numoflines = numoflines | 
					
						
							|  |  |  |         self.squeezer = squeezer | 
					
						
							|  |  |  |         self.editwin = editwin = squeezer.editwin | 
					
						
							|  |  |  |         self.text = text = editwin.text | 
					
						
							| 
									
										
										
										
											2019-01-13 17:01:50 +02:00
										 |  |  |         # The base Text widget is needed to change text before iomark. | 
					
						
							| 
									
										
										
										
											2018-09-25 15:10:14 +03:00
										 |  |  |         self.base_text = editwin.per.bottom | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-24 14:05:51 +02:00
										 |  |  |         line_plurality = "lines" if numoflines != 1 else "line" | 
					
						
							|  |  |  |         button_text = f"Squeezed text ({numoflines} {line_plurality})." | 
					
						
							| 
									
										
										
										
											2018-09-25 15:10:14 +03:00
										 |  |  |         tk.Button.__init__(self, text, text=button_text, | 
					
						
							|  |  |  |                            background="#FFFFC0", activebackground="#FFFFE0") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         button_tooltip_text = ( | 
					
						
							|  |  |  |             "Double-click to expand, right-click for more options." | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         Hovertip(self, button_tooltip_text, hover_delay=80) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.bind("<Double-Button-1>", self.expand) | 
					
						
							|  |  |  |         if macosx.isAquaTk(): | 
					
						
							|  |  |  |             # AquaTk defines <2> as the right button, not <3>. | 
					
						
							|  |  |  |             self.bind("<Button-2>", self.context_menu_event) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self.bind("<Button-3>", self.context_menu_event) | 
					
						
							| 
									
										
										
										
											2019-01-13 17:01:50 +02:00
										 |  |  |         self.selection_handle(  # X windows only. | 
					
						
							| 
									
										
										
										
											2018-09-25 15:10:14 +03:00
										 |  |  |             lambda offset, length: s[int(offset):int(offset) + int(length)]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.is_dangerous = None | 
					
						
							|  |  |  |         self.after_idle(self.set_is_dangerous) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def set_is_dangerous(self): | 
					
						
							|  |  |  |         dangerous_line_len = 50 * self.text.winfo_width() | 
					
						
							|  |  |  |         self.is_dangerous = ( | 
					
						
							|  |  |  |             self.numoflines > 1000 or | 
					
						
							|  |  |  |             len(self.s) > 50000 or | 
					
						
							|  |  |  |             any( | 
					
						
							|  |  |  |                 len(line_match.group(0)) >= dangerous_line_len | 
					
						
							|  |  |  |                 for line_match in re.finditer(r'[^\n]+', self.s) | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def expand(self, event=None): | 
					
						
							|  |  |  |         """expand event handler
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         This inserts the original text in place of the button in the Text | 
					
						
							|  |  |  |         widget, removes the button and updates the Squeezer instance. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         If the original text is dangerously long, i.e. expanding it could | 
					
						
							|  |  |  |         cause a performance degradation, ask the user for confirmation. | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         if self.is_dangerous is None: | 
					
						
							|  |  |  |             self.set_is_dangerous() | 
					
						
							|  |  |  |         if self.is_dangerous: | 
					
						
							| 
									
										
										
										
											2021-01-25 06:33:18 -05:00
										 |  |  |             confirm = messagebox.askokcancel( | 
					
						
							| 
									
										
										
										
											2018-09-25 15:10:14 +03:00
										 |  |  |                 title="Expand huge output?", | 
					
						
							|  |  |  |                 message="\n\n".join([ | 
					
						
							|  |  |  |                     "The squeezed output is very long: %d lines, %d chars.", | 
					
						
							|  |  |  |                     "Expanding it could make IDLE slow or unresponsive.", | 
					
						
							|  |  |  |                     "It is recommended to view or copy the output instead.", | 
					
						
							|  |  |  |                     "Really expand?" | 
					
						
							|  |  |  |                 ]) % (self.numoflines, len(self.s)), | 
					
						
							| 
									
										
										
										
											2021-01-25 06:33:18 -05:00
										 |  |  |                 default=messagebox.CANCEL, | 
					
						
							| 
									
										
										
										
											2018-09-25 15:10:14 +03:00
										 |  |  |                 parent=self.text) | 
					
						
							|  |  |  |             if not confirm: | 
					
						
							|  |  |  |                 return "break" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-29 01:27:55 +03:00
										 |  |  |         index = self.text.index(self) | 
					
						
							|  |  |  |         self.base_text.insert(index, self.s, self.tags) | 
					
						
							| 
									
										
										
										
											2018-09-25 15:10:14 +03:00
										 |  |  |         self.base_text.delete(self) | 
					
						
							| 
									
										
										
										
											2021-04-29 01:27:55 +03:00
										 |  |  |         self.editwin.on_squeezed_expand(index, self.s, self.tags) | 
					
						
							| 
									
										
										
										
											2018-09-25 15:10:14 +03:00
										 |  |  |         self.squeezer.expandingbuttons.remove(self) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def copy(self, event=None): | 
					
						
							|  |  |  |         """copy event handler
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         Copy the original text to the clipboard. | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         self.clipboard_clear() | 
					
						
							|  |  |  |         self.clipboard_append(self.s) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def view(self, event=None): | 
					
						
							|  |  |  |         """view event handler
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         View the original text in a separate text viewer window. | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         view_text(self.text, "Squeezed Output Viewer", self.s, | 
					
						
							|  |  |  |                   modal=False, wrap='none') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     rmenu_specs = ( | 
					
						
							| 
									
										
										
										
											2019-01-13 17:01:50 +02:00
										 |  |  |         # Item structure: (label, method_name). | 
					
						
							| 
									
										
										
										
											2018-09-25 15:10:14 +03:00
										 |  |  |         ('copy', 'copy'), | 
					
						
							|  |  |  |         ('view', 'view'), | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def context_menu_event(self, event): | 
					
						
							|  |  |  |         self.text.mark_set("insert", "@%d,%d" % (event.x, event.y)) | 
					
						
							|  |  |  |         rmenu = tk.Menu(self.text, tearoff=0) | 
					
						
							|  |  |  |         for label, method_name in self.rmenu_specs: | 
					
						
							|  |  |  |             rmenu.add_command(label=label, command=getattr(self, method_name)) | 
					
						
							|  |  |  |         rmenu.tk_popup(event.x_root, event.y_root) | 
					
						
							|  |  |  |         return "break" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class Squeezer: | 
					
						
							|  |  |  |     """Replace long outputs in the shell with a simple button.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     This avoids IDLE's shell slowing down considerably, and even becoming | 
					
						
							|  |  |  |     completely unresponsive, when very long outputs are written. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def reload(cls): | 
					
						
							|  |  |  |         """Load class variables from config.""" | 
					
						
							|  |  |  |         cls.auto_squeeze_min_lines = idleConf.GetOption( | 
					
						
							|  |  |  |             "main", "PyShell", "auto-squeeze-min-lines", | 
					
						
							|  |  |  |             type="int", default=50, | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self, editwin): | 
					
						
							|  |  |  |         """Initialize settings for Squeezer.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         editwin is the shell's Editor window. | 
					
						
							|  |  |  |         self.text is the editor window text widget. | 
					
						
							|  |  |  |         self.base_test is the actual editor window Tk text widget, rather than | 
					
						
							|  |  |  |             EditorWindow's wrapper. | 
					
						
							|  |  |  |         self.expandingbuttons is the list of all buttons representing | 
					
						
							|  |  |  |             "squeezed" output. | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         self.editwin = editwin | 
					
						
							|  |  |  |         self.text = text = editwin.text | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-13 17:01:50 +02:00
										 |  |  |         # Get the base Text widget of the PyShell object, used to change | 
					
						
							|  |  |  |         # text before the iomark. PyShell deliberately disables changing | 
					
						
							|  |  |  |         # text before the iomark via its 'text' attribute, which is | 
					
						
							|  |  |  |         # actually a wrapper for the actual Text widget. Squeezer, | 
					
						
							|  |  |  |         # however, needs to make such changes. | 
					
						
							| 
									
										
										
										
											2018-09-25 15:10:14 +03:00
										 |  |  |         self.base_text = editwin.per.bottom | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-13 17:01:50 +02:00
										 |  |  |         # Twice the text widget's border width and internal padding; | 
					
						
							|  |  |  |         # pre-calculated here for the get_line_width() method. | 
					
						
							|  |  |  |         self.window_width_delta = 2 * ( | 
					
						
							|  |  |  |             int(text.cget('border')) + | 
					
						
							|  |  |  |             int(text.cget('padx')) | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-25 15:10:14 +03:00
										 |  |  |         self.expandingbuttons = [] | 
					
						
							| 
									
										
										
										
											2019-01-13 17:01:50 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # Replace the PyShell instance's write method with a wrapper, | 
					
						
							|  |  |  |         # which inserts an ExpandingButton instead of a long text. | 
					
						
							|  |  |  |         def mywrite(s, tags=(), write=editwin.write): | 
					
						
							|  |  |  |             # Only auto-squeeze text which has just the "stdout" tag. | 
					
						
							|  |  |  |             if tags != "stdout": | 
					
						
							|  |  |  |                 return write(s, tags) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # Only auto-squeeze text with at least the minimum | 
					
						
							|  |  |  |             # configured number of lines. | 
					
						
							|  |  |  |             auto_squeeze_min_lines = self.auto_squeeze_min_lines | 
					
						
							|  |  |  |             # First, a very quick check to skip very short texts. | 
					
						
							|  |  |  |             if len(s) < auto_squeeze_min_lines: | 
					
						
							|  |  |  |                 return write(s, tags) | 
					
						
							|  |  |  |             # Now the full line-count check. | 
					
						
							|  |  |  |             numoflines = self.count_lines(s) | 
					
						
							|  |  |  |             if numoflines < auto_squeeze_min_lines: | 
					
						
							|  |  |  |                 return write(s, tags) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # Create an ExpandingButton instance. | 
					
						
							|  |  |  |             expandingbutton = ExpandingButton(s, tags, numoflines, self) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # Insert the ExpandingButton into the Text widget. | 
					
						
							|  |  |  |             text.mark_gravity("iomark", tk.RIGHT) | 
					
						
							|  |  |  |             text.window_create("iomark", window=expandingbutton, | 
					
						
							|  |  |  |                                padx=3, pady=5) | 
					
						
							|  |  |  |             text.see("iomark") | 
					
						
							|  |  |  |             text.update() | 
					
						
							|  |  |  |             text.mark_gravity("iomark", tk.LEFT) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # Add the ExpandingButton to the Squeezer's list. | 
					
						
							|  |  |  |             self.expandingbuttons.append(expandingbutton) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         editwin.write = mywrite | 
					
						
							| 
									
										
										
										
											2018-09-25 15:10:14 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def count_lines(self, s): | 
					
						
							|  |  |  |         """Count the number of lines in a given text.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         Before calculation, the tab width and line length of the text are | 
					
						
							|  |  |  |         fetched, so that up-to-date values are used. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         Lines are counted as if the string was wrapped so that lines are never | 
					
						
							|  |  |  |         over linewidth characters long. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         Tabs are considered tabwidth characters long. | 
					
						
							|  |  |  |         """
 | 
					
						
							| 
									
										
										
										
											2019-08-25 08:52:58 +03:00
										 |  |  |         return count_lines_with_wrapping(s, self.editwin.width) | 
					
						
							| 
									
										
										
										
											2018-09-25 15:10:14 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-29 01:27:55 +03:00
										 |  |  |     def squeeze_current_text(self): | 
					
						
							|  |  |  |         """Squeeze the text block where the insertion cursor is.
 | 
					
						
							| 
									
										
										
										
											2018-09-25 15:10:14 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-29 01:27:55 +03:00
										 |  |  |         If the cursor is not in a squeezable block of text, give the | 
					
						
							| 
									
										
										
										
											2018-09-25 15:10:14 +03:00
										 |  |  |         user a small warning and do nothing. | 
					
						
							|  |  |  |         """
 | 
					
						
							| 
									
										
										
										
											2019-01-13 17:01:50 +02:00
										 |  |  |         # Set tag_name to the first valid tag found on the "insert" cursor. | 
					
						
							| 
									
										
										
										
											2018-09-25 15:10:14 +03:00
										 |  |  |         tag_names = self.text.tag_names(tk.INSERT) | 
					
						
							|  |  |  |         for tag_name in ("stdout", "stderr"): | 
					
						
							|  |  |  |             if tag_name in tag_names: | 
					
						
							|  |  |  |                 break | 
					
						
							|  |  |  |         else: | 
					
						
							| 
									
										
										
										
											2019-01-13 17:01:50 +02:00
										 |  |  |             # The insert cursor doesn't have a "stdout" or "stderr" tag. | 
					
						
							| 
									
										
										
										
											2018-09-25 15:10:14 +03:00
										 |  |  |             self.text.bell() | 
					
						
							|  |  |  |             return "break" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-13 17:01:50 +02:00
										 |  |  |         # Find the range to squeeze. | 
					
						
							| 
									
										
										
										
											2018-09-25 15:10:14 +03:00
										 |  |  |         start, end = self.text.tag_prevrange(tag_name, tk.INSERT + "+1c") | 
					
						
							|  |  |  |         s = self.text.get(start, end) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-13 17:01:50 +02:00
										 |  |  |         # If the last char is a newline, remove it from the range. | 
					
						
							| 
									
										
										
										
											2018-09-25 15:10:14 +03:00
										 |  |  |         if len(s) > 0 and s[-1] == '\n': | 
					
						
							|  |  |  |             end = self.text.index("%s-1c" % end) | 
					
						
							|  |  |  |             s = s[:-1] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-13 17:01:50 +02:00
										 |  |  |         # Delete the text. | 
					
						
							| 
									
										
										
										
											2018-09-25 15:10:14 +03:00
										 |  |  |         self.base_text.delete(start, end) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-13 17:01:50 +02:00
										 |  |  |         # Prepare an ExpandingButton. | 
					
						
							| 
									
										
										
										
											2018-09-25 15:10:14 +03:00
										 |  |  |         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) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-13 17:01:50 +02:00
										 |  |  |         # Insert the ExpandingButton to the list of ExpandingButtons, | 
					
						
							|  |  |  |         # while keeping the list ordered according to the position of | 
					
						
							|  |  |  |         # the buttons in the Text widget. | 
					
						
							| 
									
										
										
										
											2018-09-25 15:10:14 +03:00
										 |  |  |         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" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Squeezer.reload() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | if __name__ == "__main__": | 
					
						
							|  |  |  |     from unittest import main | 
					
						
							|  |  |  |     main('idlelib.idle_test.test_squeezer', verbosity=2, exit=False) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Add htest. |