Author: Matti Picus <[email protected]>
Branch: py3.6
Changeset: r95830:33fe3b2cf186
Date: 2019-02-05 14:44 +0100
http://bitbucket.org/pypy/pypy/changeset/33fe3b2cf186/
Log: merge py3.5 into py3.6
diff too long, truncating to 2000 out of 22336 lines
diff --git a/lib-python/3/collections/__main__.py
b/lib-python/3/collections/__main__.py
new file mode 100644
--- /dev/null
+++ b/lib-python/3/collections/__main__.py
@@ -0,0 +1,38 @@
+################################################################################
+### Simple tests
+################################################################################
+
+# verify that instances can be pickled
+from collections import namedtuple
+from pickle import loads, dumps
+Point = namedtuple('Point', 'x, y', True)
+p = Point(x=10, y=20)
+assert p == loads(dumps(p))
+
+# test and demonstrate ability to override methods
+class Point(namedtuple('Point', 'x y')):
+ __slots__ = ()
+ @property
+ def hypot(self):
+ return (self.x ** 2 + self.y ** 2) ** 0.5
+ def __str__(self):
+ return 'Point: x=%6.3f y=%6.3f hypot=%6.3f' % (self.x, self.y,
self.hypot)
+
+for p in Point(3, 4), Point(14, 5/7.):
+ print (p)
+
+class Point(namedtuple('Point', 'x y')):
+ 'Point class with optimized _make() and _replace() without error-checking'
+ __slots__ = ()
+ _make = classmethod(tuple.__new__)
+ def _replace(self, _map=map, **kwds):
+ return self._make(_map(kwds.get, ('x', 'y'), self))
+
+print(Point(11, 22)._replace(x=100))
+
+Point3D = namedtuple('Point3D', Point._fields + ('z',))
+print(Point3D.__doc__)
+
+import doctest, collections
+TestResults = namedtuple('TestResults', 'failed attempted')
+print(TestResults(*doctest.testmod(collections)))
diff --git a/lib-python/3/idlelib/AutoCompleteWindow.py
b/lib-python/3/idlelib/AutoCompleteWindow.py
new file mode 100644
--- /dev/null
+++ b/lib-python/3/idlelib/AutoCompleteWindow.py
@@ -0,0 +1,416 @@
+"""
+An auto-completion window for IDLE, used by the AutoComplete extension
+"""
+from tkinter import *
+from idlelib.MultiCall import MC_SHIFT
+from idlelib.AutoComplete import COMPLETE_FILES, COMPLETE_ATTRIBUTES
+
+HIDE_VIRTUAL_EVENT_NAME = "<<autocompletewindow-hide>>"
+HIDE_SEQUENCES = ("<FocusOut>", "<ButtonPress>")
+KEYPRESS_VIRTUAL_EVENT_NAME = "<<autocompletewindow-keypress>>"
+# We need to bind event beyond <Key> so that the function will be called
+# before the default specific IDLE function
+KEYPRESS_SEQUENCES = ("<Key>", "<Key-BackSpace>", "<Key-Return>", "<Key-Tab>",
+ "<Key-Up>", "<Key-Down>", "<Key-Home>", "<Key-End>",
+ "<Key-Prior>", "<Key-Next>")
+KEYRELEASE_VIRTUAL_EVENT_NAME = "<<autocompletewindow-keyrelease>>"
+KEYRELEASE_SEQUENCE = "<KeyRelease>"
+LISTUPDATE_SEQUENCE = "<B1-ButtonRelease>"
+WINCONFIG_SEQUENCE = "<Configure>"
+DOUBLECLICK_SEQUENCE = "<B1-Double-ButtonRelease>"
+
+class AutoCompleteWindow:
+
+ def __init__(self, widget):
+ # The widget (Text) on which we place the AutoCompleteWindow
+ self.widget = widget
+ # The widgets we create
+ self.autocompletewindow = self.listbox = self.scrollbar = None
+ # The default foreground and background of a selection. Saved because
+ # they are changed to the regular colors of list items when the
+ # completion start is not a prefix of the selected completion
+ self.origselforeground = self.origselbackground = None
+ # The list of completions
+ self.completions = None
+ # A list with more completions, or None
+ self.morecompletions = None
+ # The completion mode. Either AutoComplete.COMPLETE_ATTRIBUTES or
+ # AutoComplete.COMPLETE_FILES
+ self.mode = None
+ # The current completion start, on the text box (a string)
+ self.start = None
+ # The index of the start of the completion
+ self.startindex = None
+ # The last typed start, used so that when the selection changes,
+ # the new start will be as close as possible to the last typed one.
+ self.lasttypedstart = None
+ # Do we have an indication that the user wants the completion window
+ # (for example, he clicked the list)
+ self.userwantswindow = None
+ # event ids
+ self.hideid = self.keypressid = self.listupdateid = self.winconfigid \
+ = self.keyreleaseid = self.doubleclickid = None
+ # Flag set if last keypress was a tab
+ self.lastkey_was_tab = False
+
+ def _change_start(self, newstart):
+ min_len = min(len(self.start), len(newstart))
+ i = 0
+ while i < min_len and self.start[i] == newstart[i]:
+ i += 1
+ if i < len(self.start):
+ self.widget.delete("%s+%dc" % (self.startindex, i),
+ "%s+%dc" % (self.startindex, len(self.start)))
+ if i < len(newstart):
+ self.widget.insert("%s+%dc" % (self.startindex, i),
+ newstart[i:])
+ self.start = newstart
+
+ def _binary_search(self, s):
+ """Find the first index in self.completions where completions[i] is
+ greater or equal to s, or the last index if there is no such
+ one."""
+ i = 0; j = len(self.completions)
+ while j > i:
+ m = (i + j) // 2
+ if self.completions[m] >= s:
+ j = m
+ else:
+ i = m + 1
+ return min(i, len(self.completions)-1)
+
+ def _complete_string(self, s):
+ """Assuming that s is the prefix of a string in self.completions,
+ return the longest string which is a prefix of all the strings which
+ s is a prefix of them. If s is not a prefix of a string, return s."""
+ first = self._binary_search(s)
+ if self.completions[first][:len(s)] != s:
+ # There is not even one completion which s is a prefix of.
+ return s
+ # Find the end of the range of completions where s is a prefix of.
+ i = first + 1
+ j = len(self.completions)
+ while j > i:
+ m = (i + j) // 2
+ if self.completions[m][:len(s)] != s:
+ j = m
+ else:
+ i = m + 1
+ last = i-1
+
+ if first == last: # only one possible completion
+ return self.completions[first]
+
+ # We should return the maximum prefix of first and last
+ first_comp = self.completions[first]
+ last_comp = self.completions[last]
+ min_len = min(len(first_comp), len(last_comp))
+ i = len(s)
+ while i < min_len and first_comp[i] == last_comp[i]:
+ i += 1
+ return first_comp[:i]
+
+ def _selection_changed(self):
+ """Should be called when the selection of the Listbox has changed.
+ Updates the Listbox display and calls _change_start."""
+ cursel = int(self.listbox.curselection()[0])
+
+ self.listbox.see(cursel)
+
+ lts = self.lasttypedstart
+ selstart = self.completions[cursel]
+ if self._binary_search(lts) == cursel:
+ newstart = lts
+ else:
+ min_len = min(len(lts), len(selstart))
+ i = 0
+ while i < min_len and lts[i] == selstart[i]:
+ i += 1
+ newstart = selstart[:i]
+ self._change_start(newstart)
+
+ if self.completions[cursel][:len(self.start)] == self.start:
+ # start is a prefix of the selected completion
+ self.listbox.configure(selectbackground=self.origselbackground,
+ selectforeground=self.origselforeground)
+ else:
+ self.listbox.configure(selectbackground=self.listbox.cget("bg"),
+ selectforeground=self.listbox.cget("fg"))
+ # If there are more completions, show them, and call me again.
+ if self.morecompletions:
+ self.completions = self.morecompletions
+ self.morecompletions = None
+ self.listbox.delete(0, END)
+ for item in self.completions:
+ self.listbox.insert(END, item)
+ self.listbox.select_set(self._binary_search(self.start))
+ self._selection_changed()
+
+ def show_window(self, comp_lists, index, complete, mode, userWantsWin):
+ """Show the autocomplete list, bind events.
+ If complete is True, complete the text, and if there is exactly one
+ matching completion, don't open a list."""
+ # Handle the start we already have
+ self.completions, self.morecompletions = comp_lists
+ self.mode = mode
+ self.startindex = self.widget.index(index)
+ self.start = self.widget.get(self.startindex, "insert")
+ if complete:
+ completed = self._complete_string(self.start)
+ start = self.start
+ self._change_start(completed)
+ i = self._binary_search(completed)
+ if self.completions[i] == completed and \
+ (i == len(self.completions)-1 or
+ self.completions[i+1][:len(completed)] != completed):
+ # There is exactly one matching completion
+ return completed == start
+ self.userwantswindow = userWantsWin
+ self.lasttypedstart = self.start
+
+ # Put widgets in place
+ self.autocompletewindow = acw = Toplevel(self.widget)
+ # Put it in a position so that it is not seen.
+ acw.wm_geometry("+10000+10000")
+ # Make it float
+ acw.wm_overrideredirect(1)
+ try:
+ # This command is only needed and available on Tk >= 8.4.0 for OSX
+ # Without it, call tips intrude on the typing process by grabbing
+ # the focus.
+ acw.tk.call("::tk::unsupported::MacWindowStyle", "style", acw._w,
+ "help", "noActivates")
+ except TclError:
+ pass
+ self.scrollbar = scrollbar = Scrollbar(acw, orient=VERTICAL)
+ self.listbox = listbox = Listbox(acw, yscrollcommand=scrollbar.set,
+ exportselection=False, bg="white")
+ for item in self.completions:
+ listbox.insert(END, item)
+ self.origselforeground = listbox.cget("selectforeground")
+ self.origselbackground = listbox.cget("selectbackground")
+ scrollbar.config(command=listbox.yview)
+ scrollbar.pack(side=RIGHT, fill=Y)
+ listbox.pack(side=LEFT, fill=BOTH, expand=True)
+ acw.lift() # work around bug in Tk 8.5.18+ (issue #24570)
+
+ # Initialize the listbox selection
+ self.listbox.select_set(self._binary_search(self.start))
+ self._selection_changed()
+
+ # bind events
+ self.hideid = self.widget.bind(HIDE_VIRTUAL_EVENT_NAME,
+ self.hide_event)
+ for seq in HIDE_SEQUENCES:
+ self.widget.event_add(HIDE_VIRTUAL_EVENT_NAME, seq)
+ self.keypressid = self.widget.bind(KEYPRESS_VIRTUAL_EVENT_NAME,
+ self.keypress_event)
+ for seq in KEYPRESS_SEQUENCES:
+ self.widget.event_add(KEYPRESS_VIRTUAL_EVENT_NAME, seq)
+ self.keyreleaseid = self.widget.bind(KEYRELEASE_VIRTUAL_EVENT_NAME,
+ self.keyrelease_event)
+
self.widget.event_add(KEYRELEASE_VIRTUAL_EVENT_NAME,KEYRELEASE_SEQUENCE)
+ self.listupdateid = listbox.bind(LISTUPDATE_SEQUENCE,
+ self.listselect_event)
+ self.winconfigid = acw.bind(WINCONFIG_SEQUENCE, self.winconfig_event)
+ self.doubleclickid = listbox.bind(DOUBLECLICK_SEQUENCE,
+ self.doubleclick_event)
+
+ def winconfig_event(self, event):
+ if not self.is_active():
+ return
+ # Position the completion list window
+ text = self.widget
+ text.see(self.startindex)
+ x, y, cx, cy = text.bbox(self.startindex)
+ acw = self.autocompletewindow
+ acw_width, acw_height = acw.winfo_width(), acw.winfo_height()
+ text_width, text_height = text.winfo_width(), text.winfo_height()
+ new_x = text.winfo_rootx() + min(x, max(0, text_width - acw_width))
+ new_y = text.winfo_rooty() + y
+ if (text_height - (y + cy) >= acw_height # enough height below
+ or y < acw_height): # not enough height above
+ # place acw below current line
+ new_y += cy
+ else:
+ # place acw above current line
+ new_y -= acw_height
+ acw.wm_geometry("+%d+%d" % (new_x, new_y))
+
+ def hide_event(self, event):
+ if not self.is_active():
+ return
+ self.hide_window()
+
+ def listselect_event(self, event):
+ if not self.is_active():
+ return
+ self.userwantswindow = True
+ cursel = int(self.listbox.curselection()[0])
+ self._change_start(self.completions[cursel])
+
+ def doubleclick_event(self, event):
+ # Put the selected completion in the text, and close the list
+ cursel = int(self.listbox.curselection()[0])
+ self._change_start(self.completions[cursel])
+ self.hide_window()
+
+ def keypress_event(self, event):
+ if not self.is_active():
+ return
+ keysym = event.keysym
+ if hasattr(event, "mc_state"):
+ state = event.mc_state
+ else:
+ state = 0
+ if keysym != "Tab":
+ self.lastkey_was_tab = False
+ if (len(keysym) == 1 or keysym in ("underscore", "BackSpace")
+ or (self.mode == COMPLETE_FILES and keysym in
+ ("period", "minus"))) \
+ and not (state & ~MC_SHIFT):
+ # Normal editing of text
+ if len(keysym) == 1:
+ self._change_start(self.start + keysym)
+ elif keysym == "underscore":
+ self._change_start(self.start + '_')
+ elif keysym == "period":
+ self._change_start(self.start + '.')
+ elif keysym == "minus":
+ self._change_start(self.start + '-')
+ else:
+ # keysym == "BackSpace"
+ if len(self.start) == 0:
+ self.hide_window()
+ return
+ self._change_start(self.start[:-1])
+ self.lasttypedstart = self.start
+ self.listbox.select_clear(0, int(self.listbox.curselection()[0]))
+ self.listbox.select_set(self._binary_search(self.start))
+ self._selection_changed()
+ return "break"
+
+ elif keysym == "Return":
+ self.hide_window()
+ return
+
+ elif (self.mode == COMPLETE_ATTRIBUTES and keysym in
+ ("period", "space", "parenleft", "parenright", "bracketleft",
+ "bracketright")) or \
+ (self.mode == COMPLETE_FILES and keysym in
+ ("slash", "backslash", "quotedbl", "apostrophe")) \
+ and not (state & ~MC_SHIFT):
+ # If start is a prefix of the selection, but is not '' when
+ # completing file names, put the whole
+ # selected completion. Anyway, close the list.
+ cursel = int(self.listbox.curselection()[0])
+ if self.completions[cursel][:len(self.start)] == self.start \
+ and (self.mode == COMPLETE_ATTRIBUTES or self.start):
+ self._change_start(self.completions[cursel])
+ self.hide_window()
+ return
+
+ elif keysym in ("Home", "End", "Prior", "Next", "Up", "Down") and \
+ not state:
+ # Move the selection in the listbox
+ self.userwantswindow = True
+ cursel = int(self.listbox.curselection()[0])
+ if keysym == "Home":
+ newsel = 0
+ elif keysym == "End":
+ newsel = len(self.completions)-1
+ elif keysym in ("Prior", "Next"):
+ jump = self.listbox.nearest(self.listbox.winfo_height()) - \
+ self.listbox.nearest(0)
+ if keysym == "Prior":
+ newsel = max(0, cursel-jump)
+ else:
+ assert keysym == "Next"
+ newsel = min(len(self.completions)-1, cursel+jump)
+ elif keysym == "Up":
+ newsel = max(0, cursel-1)
+ else:
+ assert keysym == "Down"
+ newsel = min(len(self.completions)-1, cursel+1)
+ self.listbox.select_clear(cursel)
+ self.listbox.select_set(newsel)
+ self._selection_changed()
+ self._change_start(self.completions[newsel])
+ return "break"
+
+ elif (keysym == "Tab" and not state):
+ if self.lastkey_was_tab:
+ # two tabs in a row; insert current selection and close acw
+ cursel = int(self.listbox.curselection()[0])
+ self._change_start(self.completions[cursel])
+ self.hide_window()
+ return "break"
+ else:
+ # first tab; let AutoComplete handle the completion
+ self.userwantswindow = True
+ self.lastkey_was_tab = True
+ return
+
+ elif any(s in keysym for s in ("Shift", "Control", "Alt",
+ "Meta", "Command", "Option")):
+ # A modifier key, so ignore
+ return
+
+ elif event.char and event.char >= ' ':
+ # Regular character with a non-length-1 keycode
+ self._change_start(self.start + event.char)
+ self.lasttypedstart = self.start
+ self.listbox.select_clear(0, int(self.listbox.curselection()[0]))
+ self.listbox.select_set(self._binary_search(self.start))
+ self._selection_changed()
+ return "break"
+
+ else:
+ # Unknown event, close the window and let it through.
+ self.hide_window()
+ return
+
+ def keyrelease_event(self, event):
+ if not self.is_active():
+ return
+ if self.widget.index("insert") != \
+ self.widget.index("%s+%dc" % (self.startindex, len(self.start))):
+ # If we didn't catch an event which moved the insert, close window
+ self.hide_window()
+
+ def is_active(self):
+ return self.autocompletewindow is not None
+
+ def complete(self):
+ self._change_start(self._complete_string(self.start))
+ # The selection doesn't change.
+
+ def hide_window(self):
+ if not self.is_active():
+ return
+
+ # unbind events
+ for seq in HIDE_SEQUENCES:
+ self.widget.event_delete(HIDE_VIRTUAL_EVENT_NAME, seq)
+ self.widget.unbind(HIDE_VIRTUAL_EVENT_NAME, self.hideid)
+ self.hideid = None
+ for seq in KEYPRESS_SEQUENCES:
+ self.widget.event_delete(KEYPRESS_VIRTUAL_EVENT_NAME, seq)
+ self.widget.unbind(KEYPRESS_VIRTUAL_EVENT_NAME, self.keypressid)
+ self.keypressid = None
+ self.widget.event_delete(KEYRELEASE_VIRTUAL_EVENT_NAME,
+ KEYRELEASE_SEQUENCE)
+ self.widget.unbind(KEYRELEASE_VIRTUAL_EVENT_NAME, self.keyreleaseid)
+ self.keyreleaseid = None
+ self.listbox.unbind(LISTUPDATE_SEQUENCE, self.listupdateid)
+ self.listupdateid = None
+ self.autocompletewindow.unbind(WINCONFIG_SEQUENCE, self.winconfigid)
+ self.winconfigid = None
+
+ # destroy widgets
+ self.scrollbar.destroy()
+ self.scrollbar = None
+ self.listbox.destroy()
+ self.listbox = None
+ self.autocompletewindow.destroy()
+ self.autocompletewindow = None
diff --git a/lib-python/3/idlelib/Bindings.py b/lib-python/3/idlelib/Bindings.py
new file mode 100644
--- /dev/null
+++ b/lib-python/3/idlelib/Bindings.py
@@ -0,0 +1,96 @@
+"""Define the menu contents, hotkeys, and event bindings.
+
+There is additional configuration information in the EditorWindow class (and
+subclasses): the menus are created there based on the menu_specs (class)
+variable, and menus not created are silently skipped in the code here. This
+makes it possible, for example, to define a Debug menu which is only present in
+the PythonShell window, and a Format menu which is only present in the Editor
+windows.
+
+"""
+from importlib.util import find_spec
+
+from idlelib.configHandler import idleConf
+
+# Warning: menudefs is altered in macosxSupport.overrideRootMenu()
+# after it is determined that an OS X Aqua Tk is in use,
+# which cannot be done until after Tk() is first called.
+# Do not alter the 'file', 'options', or 'help' cascades here
+# without altering overrideRootMenu() as well.
+# TODO: Make this more robust
+
+menudefs = [
+ # underscore prefixes character to underscore
+ ('file', [
+ ('_New File', '<<open-new-window>>'),
+ ('_Open...', '<<open-window-from-file>>'),
+ ('Open _Module...', '<<open-module>>'),
+ ('Class _Browser', '<<open-class-browser>>'),
+ ('_Path Browser', '<<open-path-browser>>'),
+ None,
+ ('_Save', '<<save-window>>'),
+ ('Save _As...', '<<save-window-as-file>>'),
+ ('Save Cop_y As...', '<<save-copy-of-window-as-file>>'),
+ None,
+ ('Prin_t Window', '<<print-window>>'),
+ None,
+ ('_Close', '<<close-window>>'),
+ ('E_xit', '<<close-all-windows>>'),
+ ]),
+ ('edit', [
+ ('_Undo', '<<undo>>'),
+ ('_Redo', '<<redo>>'),
+ None,
+ ('Cu_t', '<<cut>>'),
+ ('_Copy', '<<copy>>'),
+ ('_Paste', '<<paste>>'),
+ ('Select _All', '<<select-all>>'),
+ None,
+ ('_Find...', '<<find>>'),
+ ('Find A_gain', '<<find-again>>'),
+ ('Find _Selection', '<<find-selection>>'),
+ ('Find in Files...', '<<find-in-files>>'),
+ ('R_eplace...', '<<replace>>'),
+ ('Go to _Line', '<<goto-line>>'),
+ ]),
+('format', [
+ ('_Indent Region', '<<indent-region>>'),
+ ('_Dedent Region', '<<dedent-region>>'),
+ ('Comment _Out Region', '<<comment-region>>'),
+ ('U_ncomment Region', '<<uncomment-region>>'),
+ ('Tabify Region', '<<tabify-region>>'),
+ ('Untabify Region', '<<untabify-region>>'),
+ ('Toggle Tabs', '<<toggle-tabs>>'),
+ ('New Indent Width', '<<change-indentwidth>>'),
+ ]),
+ ('run', [
+ ('Python Shell', '<<open-python-shell>>'),
+ ]),
+ ('shell', [
+ ('_View Last Restart', '<<view-restart>>'),
+ ('_Restart Shell', '<<restart-shell>>'),
+ None,
+ ('_Interrupt Execution', '<<interrupt-execution>>'),
+ ]),
+ ('debug', [
+ ('_Go to File/Line', '<<goto-file-line>>'),
+ ('!_Debugger', '<<toggle-debugger>>'),
+ ('_Stack Viewer', '<<open-stack-viewer>>'),
+ ('!_Auto-open Stack Viewer', '<<toggle-jit-stack-viewer>>'),
+ ]),
+ ('options', [
+ ('Configure _IDLE', '<<open-config-dialog>>'),
+ None,
+ ]),
+ ('help', [
+ ('_About IDLE', '<<about-idle>>'),
+ None,
+ ('_IDLE Help', '<<help>>'),
+ ('Python _Docs', '<<python-docs>>'),
+ ]),
+]
+
+if find_spec('turtledemo'):
+ menudefs[-1][1].append(('Turtle Demo', '<<open-turtle-demo>>'))
+
+default_keydefs = idleConf.GetCurrentKeySet()
diff --git a/lib-python/3/idlelib/CallTipWindow.py
b/lib-python/3/idlelib/CallTipWindow.py
new file mode 100644
--- /dev/null
+++ b/lib-python/3/idlelib/CallTipWindow.py
@@ -0,0 +1,161 @@
+"""A CallTip window class for Tkinter/IDLE.
+
+After ToolTip.py, which uses ideas gleaned from PySol
+Used by the CallTips IDLE extension.
+"""
+from tkinter import Toplevel, Label, LEFT, SOLID, TclError
+
+HIDE_VIRTUAL_EVENT_NAME = "<<calltipwindow-hide>>"
+HIDE_SEQUENCES = ("<Key-Escape>", "<FocusOut>")
+CHECKHIDE_VIRTUAL_EVENT_NAME = "<<calltipwindow-checkhide>>"
+CHECKHIDE_SEQUENCES = ("<KeyRelease>", "<ButtonRelease>")
+CHECKHIDE_TIME = 100 # milliseconds
+
+MARK_RIGHT = "calltipwindowregion_right"
+
+class CallTip:
+
+ def __init__(self, widget):
+ self.widget = widget
+ self.tipwindow = self.label = None
+ self.parenline = self.parencol = None
+ self.lastline = None
+ self.hideid = self.checkhideid = None
+ self.checkhide_after_id = None
+
+ def position_window(self):
+ """Check if needs to reposition the window, and if so - do it."""
+ curline = int(self.widget.index("insert").split('.')[0])
+ if curline == self.lastline:
+ return
+ self.lastline = curline
+ self.widget.see("insert")
+ if curline == self.parenline:
+ box = self.widget.bbox("%d.%d" % (self.parenline,
+ self.parencol))
+ else:
+ box = self.widget.bbox("%d.0" % curline)
+ if not box:
+ box = list(self.widget.bbox("insert"))
+ # align to left of window
+ box[0] = 0
+ box[2] = 0
+ x = box[0] + self.widget.winfo_rootx() + 2
+ y = box[1] + box[3] + self.widget.winfo_rooty()
+ self.tipwindow.wm_geometry("+%d+%d" % (x, y))
+
+ def showtip(self, text, parenleft, parenright):
+ """Show the calltip, bind events which will close it and reposition it.
+ """
+ # Only called in CallTips, where lines are truncated
+ self.text = text
+ if self.tipwindow or not self.text:
+ return
+
+ self.widget.mark_set(MARK_RIGHT, parenright)
+ self.parenline, self.parencol = map(
+ int, self.widget.index(parenleft).split("."))
+
+ self.tipwindow = tw = Toplevel(self.widget)
+ self.position_window()
+ # remove border on calltip window
+ tw.wm_overrideredirect(1)
+ try:
+ # This command is only needed and available on Tk >= 8.4.0 for OSX
+ # Without it, call tips intrude on the typing process by grabbing
+ # the focus.
+ tw.tk.call("::tk::unsupported::MacWindowStyle", "style", tw._w,
+ "help", "noActivates")
+ except TclError:
+ pass
+ self.label = Label(tw, text=self.text, justify=LEFT,
+ background="#ffffe0", relief=SOLID, borderwidth=1,
+ font = self.widget['font'])
+ self.label.pack()
+ tw.lift() # work around bug in Tk 8.5.18+ (issue #24570)
+
+ self.checkhideid = self.widget.bind(CHECKHIDE_VIRTUAL_EVENT_NAME,
+ self.checkhide_event)
+ for seq in CHECKHIDE_SEQUENCES:
+ self.widget.event_add(CHECKHIDE_VIRTUAL_EVENT_NAME, seq)
+ self.widget.after(CHECKHIDE_TIME, self.checkhide_event)
+ self.hideid = self.widget.bind(HIDE_VIRTUAL_EVENT_NAME,
+ self.hide_event)
+ for seq in HIDE_SEQUENCES:
+ self.widget.event_add(HIDE_VIRTUAL_EVENT_NAME, seq)
+
+ def checkhide_event(self, event=None):
+ if not self.tipwindow:
+ # If the event was triggered by the same event that unbinded
+ # this function, the function will be called nevertheless,
+ # so do nothing in this case.
+ return
+ curline, curcol = map(int, self.widget.index("insert").split('.'))
+ if curline < self.parenline or \
+ (curline == self.parenline and curcol <= self.parencol) or \
+ self.widget.compare("insert", ">", MARK_RIGHT):
+ self.hidetip()
+ else:
+ self.position_window()
+ if self.checkhide_after_id is not None:
+ self.widget.after_cancel(self.checkhide_after_id)
+ self.checkhide_after_id = \
+ self.widget.after(CHECKHIDE_TIME, self.checkhide_event)
+
+ def hide_event(self, event):
+ if not self.tipwindow:
+ # See the explanation in checkhide_event.
+ return
+ self.hidetip()
+
+ def hidetip(self):
+ if not self.tipwindow:
+ return
+
+ for seq in CHECKHIDE_SEQUENCES:
+ self.widget.event_delete(CHECKHIDE_VIRTUAL_EVENT_NAME, seq)
+ self.widget.unbind(CHECKHIDE_VIRTUAL_EVENT_NAME, self.checkhideid)
+ self.checkhideid = None
+ for seq in HIDE_SEQUENCES:
+ self.widget.event_delete(HIDE_VIRTUAL_EVENT_NAME, seq)
+ self.widget.unbind(HIDE_VIRTUAL_EVENT_NAME, self.hideid)
+ self.hideid = None
+
+ self.label.destroy()
+ self.label = None
+ self.tipwindow.destroy()
+ self.tipwindow = None
+
+ self.widget.mark_unset(MARK_RIGHT)
+ self.parenline = self.parencol = self.lastline = None
+
+ def is_active(self):
+ return bool(self.tipwindow)
+
+
+def _calltip_window(parent): # htest #
+ from tkinter import Toplevel, Text, LEFT, BOTH
+
+ top = Toplevel(parent)
+ top.title("Test calltips")
+ top.geometry("200x100+%d+%d" % (parent.winfo_rootx() + 200,
+ parent.winfo_rooty() + 150))
+ text = Text(top)
+ text.pack(side=LEFT, fill=BOTH, expand=1)
+ text.insert("insert", "string.split")
+ top.update()
+ calltip = CallTip(text)
+
+ def calltip_show(event):
+ calltip.showtip("(s=Hello world)", "insert", "end")
+ def calltip_hide(event):
+ calltip.hidetip()
+ text.event_add("<<calltip-show>>", "(")
+ text.event_add("<<calltip-hide>>", ")")
+ text.bind("<<calltip-show>>", calltip_show)
+ text.bind("<<calltip-hide>>", calltip_hide)
+ text.focus_set()
+
+if __name__=='__main__':
+ from idlelib.idle_test.htest import run
+ run(_calltip_window)
diff --git a/lib-python/3/idlelib/ClassBrowser.py
b/lib-python/3/idlelib/ClassBrowser.py
new file mode 100644
--- /dev/null
+++ b/lib-python/3/idlelib/ClassBrowser.py
@@ -0,0 +1,236 @@
+"""Class browser.
+
+XXX TO DO:
+
+- reparse when source changed (maybe just a button would be OK?)
+ (or recheck on window popup)
+- add popup menu with more options (e.g. doc strings, base classes, imports)
+- show function argument list? (have to do pattern matching on source)
+- should the classes and methods lists also be in the module's menu bar?
+- add base classes to class browser tree
+"""
+
+import os
+import sys
+import pyclbr
+
+from idlelib import PyShell
+from idlelib.WindowList import ListedToplevel
+from idlelib.TreeWidget import TreeNode, TreeItem, ScrolledCanvas
+from idlelib.configHandler import idleConf
+
+file_open = None # Method...Item and Class...Item use this.
+# Normally PyShell.flist.open, but there is no PyShell.flist for htest.
+
+class ClassBrowser:
+
+ def __init__(self, flist, name, path, _htest=False):
+ # XXX This API should change, if the file doesn't end in ".py"
+ # XXX the code here is bogus!
+ """
+ _htest - bool, change box when location running htest.
+ """
+ global file_open
+ if not _htest:
+ file_open = PyShell.flist.open
+ self.name = name
+ self.file = os.path.join(path[0], self.name + ".py")
+ self._htest = _htest
+ self.init(flist)
+
+ def close(self, event=None):
+ self.top.destroy()
+ self.node.destroy()
+
+ 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 settitle(self):
+ self.top.wm_title("Class Browser - " + self.name)
+ self.top.wm_iconname("Class Browser")
+
+ def rootnode(self):
+ return ModuleBrowserTreeItem(self.file)
+
+class ModuleBrowserTreeItem(TreeItem):
+
+ def __init__(self, file):
+ self.file = file
+
+ def GetText(self):
+ return os.path.basename(self.file)
+
+ def GetIconName(self):
+ return "python"
+
+ def GetSubList(self):
+ sublist = []
+ for name in self.listclasses():
+ item = ClassBrowserTreeItem(name, self.classes, self.file)
+ sublist.append(item)
+ return sublist
+
+ def OnDoubleClick(self):
+ if os.path.normcase(self.file[-3:]) != ".py":
+ return
+ if not os.path.exists(self.file):
+ return
+ PyShell.flist.open(self.file)
+
+ def IsExpandable(self):
+ return os.path.normcase(self.file[-3:]) == ".py"
+
+ def listclasses(self):
+ dir, file = os.path.split(self.file)
+ name, ext = os.path.splitext(file)
+ if os.path.normcase(ext) != ".py":
+ return []
+ try:
+ dict = pyclbr.readmodule_ex(name, [dir] + sys.path)
+ except ImportError:
+ return []
+ items = []
+ self.classes = {}
+ for key, cl in dict.items():
+ if cl.module == name:
+ s = key
+ if hasattr(cl, 'super') and cl.super:
+ supers = []
+ for sup in cl.super:
+ if type(sup) is type(''):
+ sname = sup
+ else:
+ sname = sup.name
+ if sup.module != cl.module:
+ sname = "%s.%s" % (sup.module, sname)
+ supers.append(sname)
+ s = s + "(%s)" % ", ".join(supers)
+ items.append((cl.lineno, s))
+ self.classes[s] = cl
+ items.sort()
+ list = []
+ for item, s in items:
+ list.append(s)
+ return list
+
+class ClassBrowserTreeItem(TreeItem):
+
+ def __init__(self, name, classes, file):
+ self.name = name
+ self.classes = classes
+ self.file = file
+ try:
+ self.cl = self.classes[self.name]
+ except (IndexError, KeyError):
+ self.cl = None
+ self.isfunction = isinstance(self.cl, pyclbr.Function)
+
+ def GetText(self):
+ if self.isfunction:
+ return "def " + self.name + "(...)"
+ else:
+ return "class " + self.name
+
+ def GetIconName(self):
+ if self.isfunction:
+ return "python"
+ else:
+ return "folder"
+
+ def IsExpandable(self):
+ if self.cl:
+ try:
+ return not not self.cl.methods
+ except AttributeError:
+ return False
+
+ def GetSubList(self):
+ if not self.cl:
+ return []
+ sublist = []
+ for name in self.listmethods():
+ item = MethodBrowserTreeItem(name, self.cl, self.file)
+ sublist.append(item)
+ return sublist
+
+ def OnDoubleClick(self):
+ if not os.path.exists(self.file):
+ return
+ edit = file_open(self.file)
+ if hasattr(self.cl, 'lineno'):
+ lineno = self.cl.lineno
+ edit.gotoline(lineno)
+
+ def listmethods(self):
+ if not self.cl:
+ return []
+ items = []
+ for name, lineno in self.cl.methods.items():
+ items.append((lineno, name))
+ items.sort()
+ list = []
+ for item, name in items:
+ list.append(name)
+ return list
+
+class MethodBrowserTreeItem(TreeItem):
+
+ def __init__(self, name, cl, file):
+ self.name = name
+ self.cl = cl
+ self.file = file
+
+ def GetText(self):
+ return "def " + self.name + "(...)"
+
+ def GetIconName(self):
+ return "python" # XXX
+
+ def IsExpandable(self):
+ return 0
+
+ def OnDoubleClick(self):
+ if not os.path.exists(self.file):
+ return
+ edit = file_open(self.file)
+ edit.gotoline(self.cl.methods[self.name])
+
+def _class_browser(parent): #Wrapper for htest
+ try:
+ file = __file__
+ except NameError:
+ file = sys.argv[0]
+ if sys.argv[1:]:
+ file = sys.argv[1]
+ else:
+ file = sys.argv[0]
+ dir, file = os.path.split(file)
+ name = os.path.splitext(file)[0]
+ flist = PyShell.PyShellFileList(parent)
+ global file_open
+ file_open = flist.open
+ ClassBrowser(flist, name, [dir], _htest=True)
+
+if __name__ == "__main__":
+ from idlelib.idle_test.htest import run
+ run(_class_browser)
diff --git a/lib-python/3/idlelib/ColorDelegator.py
b/lib-python/3/idlelib/ColorDelegator.py
new file mode 100644
--- /dev/null
+++ b/lib-python/3/idlelib/ColorDelegator.py
@@ -0,0 +1,281 @@
+import time
+import re
+import keyword
+import builtins
+from tkinter import TkVersion
+from idlelib.Delegator import Delegator
+from idlelib.configHandler import idleConf
+
+DEBUG = False
+
+def any(name, alternates):
+ "Return a named group pattern matching list of alternates."
+ return "(?P<%s>" % name + "|".join(alternates) + ")"
+
+def make_pat():
+ kw = r"\b" + any("KEYWORD", keyword.kwlist) + r"\b"
+ builtinlist = [str(name) for name in dir(builtins)
+ if not name.startswith('_') and \
+ name not in keyword.kwlist]
+ # self.file = open("file") :
+ # 1st 'file' colorized normal, 2nd as builtin, 3rd as string
+ builtin = r"([^.'\"\\#]\b|^)" + any("BUILTIN", builtinlist) + r"\b"
+ comment = any("COMMENT", [r"#[^\n]*"])
+ stringprefix = r"(\br|u|ur|R|U|UR|Ur|uR|b|B|br|Br|bR|BR|rb|rB|Rb|RB)?"
+ sqstring = stringprefix + r"'[^'\\\n]*(\\.[^'\\\n]*)*'?"
+ dqstring = stringprefix + r'"[^"\\\n]*(\\.[^"\\\n]*)*"?'
+ sq3string = stringprefix + r"'''[^'\\]*((\\.|'(?!''))[^'\\]*)*(''')?"
+ dq3string = stringprefix + r'"""[^"\\]*((\\.|"(?!""))[^"\\]*)*(""")?'
+ string = any("STRING", [sq3string, dq3string, sqstring, dqstring])
+ return kw + "|" + builtin + "|" + comment + "|" + string +\
+ "|" + any("SYNC", [r"\n"])
+
+prog = re.compile(make_pat(), re.S)
+idprog = re.compile(r"\s+(\w+)", re.S)
+
+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'],
+ )
+ if TkVersion >= 8.5:
+ text.config(
+ inactiveselectbackground=select_colors['background'])
+
+
+class ColorDelegator(Delegator):
+
+ def __init__(self):
+ Delegator.__init__(self)
+ self.prog = prog
+ self.idprog = idprog
+ self.LoadTagDefs()
+
+ def setdelegate(self, delegate):
+ if self.delegate is not None:
+ self.unbind("<<toggle-auto-coloring>>")
+ Delegator.setdelegate(self, delegate)
+ if delegate is not None:
+ self.config_colors()
+ self.bind("<<toggle-auto-coloring>>", self.toggle_colorize_event)
+ self.notify_range("1.0", "end")
+ else:
+ # No delegate - stop any colorizing
+ self.stop_colorizing = True
+ self.allow_colorizing = False
+
+ def config_colors(self):
+ for tag, cnf in self.tagdefs.items():
+ if cnf:
+ self.tag_configure(tag, **cnf)
+ self.tag_raise('sel')
+
+ 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 insert(self, index, chars, tags=None):
+ index = self.index(index)
+ self.delegate.insert(index, chars, tags)
+ self.notify_range(index, index + "+%dc" % len(chars))
+
+ def delete(self, index1, index2=None):
+ index1 = self.index(index1)
+ self.delegate.delete(index1, index2)
+ self.notify_range(index1)
+
+ after_id = None
+ allow_colorizing = True
+ colorizing = False
+
+ def notify_range(self, index1, index2=None):
+ self.tag_add("TODO", index1, index2)
+ if self.after_id:
+ if DEBUG: print("colorizing already scheduled")
+ return
+ if self.colorizing:
+ self.stop_colorizing = True
+ if DEBUG: print("stop colorizing")
+ if self.allow_colorizing:
+ if DEBUG: print("schedule colorizing")
+ self.after_id = self.after(1, self.recolorize)
+
+ close_when_done = None # Window to be closed when done colorizing
+
+ def close(self, close_when_done=None):
+ if self.after_id:
+ after_id = self.after_id
+ self.after_id = None
+ if DEBUG: print("cancel scheduled recolorizer")
+ self.after_cancel(after_id)
+ self.allow_colorizing = False
+ self.stop_colorizing = True
+ if close_when_done:
+ if not self.colorizing:
+ close_when_done.destroy()
+ else:
+ self.close_when_done = close_when_done
+
+ def toggle_colorize_event(self, event):
+ if self.after_id:
+ after_id = self.after_id
+ self.after_id = None
+ if DEBUG: print("cancel scheduled recolorizer")
+ self.after_cancel(after_id)
+ if self.allow_colorizing and self.colorizing:
+ if DEBUG: print("stop colorizing")
+ self.stop_colorizing = True
+ self.allow_colorizing = not self.allow_colorizing
+ if self.allow_colorizing and not self.colorizing:
+ self.after_id = self.after(1, self.recolorize)
+ if DEBUG:
+ print("auto colorizing turned",\
+ self.allow_colorizing and "on" or "off")
+ return "break"
+
+ def recolorize(self):
+ self.after_id = None
+ if not self.delegate:
+ if DEBUG: print("no delegate")
+ return
+ if not self.allow_colorizing:
+ if DEBUG: print("auto colorizing is off")
+ return
+ if self.colorizing:
+ if DEBUG: print("already colorizing")
+ return
+ try:
+ self.stop_colorizing = False
+ self.colorizing = True
+ if DEBUG: print("colorizing...")
+ t0 = time.perf_counter()
+ self.recolorize_main()
+ t1 = time.perf_counter()
+ if DEBUG: print("%.3f seconds" % (t1-t0))
+ finally:
+ self.colorizing = False
+ if self.allow_colorizing and self.tag_nextrange("TODO", "1.0"):
+ if DEBUG: print("reschedule colorizing")
+ self.after_id = self.after(1, self.recolorize)
+ if self.close_when_done:
+ top = self.close_when_done
+ self.close_when_done = None
+ top.destroy()
+
+ def recolorize_main(self):
+ next = "1.0"
+ while True:
+ item = self.tag_nextrange("TODO", next)
+ if not item:
+ break
+ head, tail = item
+ self.tag_remove("SYNC", head, tail)
+ item = self.tag_prevrange("SYNC", head)
+ if item:
+ head = item[1]
+ else:
+ head = "1.0"
+
+ chars = ""
+ next = head
+ lines_to_get = 1
+ ok = False
+ while not ok:
+ mark = next
+ next = self.index(mark + "+%d lines linestart" %
+ lines_to_get)
+ lines_to_get = min(lines_to_get * 2, 100)
+ ok = "SYNC" in self.tag_names(next + "-1c")
+ line = self.get(mark, next)
+ ##print head, "get", mark, next, "->", repr(line)
+ if not line:
+ return
+ for tag in self.tagdefs:
+ self.tag_remove(tag, mark, next)
+ chars = chars + line
+ m = self.prog.search(chars)
+ while m:
+ for key, value in m.groupdict().items():
+ if value:
+ a, b = m.span(key)
+ self.tag_add(key,
+ head + "+%dc" % a,
+ head + "+%dc" % b)
+ if value in ("def", "class"):
+ m1 = self.idprog.match(chars, b)
+ if m1:
+ a, b = m1.span(1)
+ self.tag_add("DEFINITION",
+ head + "+%dc" % a,
+ head + "+%dc" % b)
+ m = self.prog.search(chars, m.end())
+ if "SYNC" in self.tag_names(next + "-1c"):
+ head = next
+ chars = ""
+ else:
+ ok = False
+ if not ok:
+ # We're in an inconsistent state, and the call to
+ # update may tell us to stop. It may also change
+ # the correct value for "next" (since this is a
+ # line.col string, not a true mark). So leave a
+ # crumb telling the next invocation to resume here
+ # in case update tells us to leave.
+ self.tag_add("TODO", next)
+ self.update()
+ if self.stop_colorizing:
+ if DEBUG: print("colorizing stopped")
+ return
+
+ def removecolors(self):
+ for tag in self.tagdefs:
+ self.tag_remove(tag, "1.0", "end")
+
+
+def _color_delegator(parent): # htest #
+ from tkinter import Toplevel, Text
+ from idlelib.Percolator import Percolator
+
+ top = Toplevel(parent)
+ top.title("Test ColorDelegator")
+ top.geometry("200x100+%d+%d" % (parent.winfo_rootx() + 200,
+ parent.winfo_rooty() + 150))
+ source = "if somename: x = 'abc' # comment\nprint\n"
+ text = Text(top, background="white")
+ text.pack(expand=1, fill="both")
+ text.insert("insert", source)
+ text.focus_set()
+
+ color_config(text)
+ p = Percolator(text)
+ d = ColorDelegator()
+ p.insertfilter(d)
+
+if __name__ == "__main__":
+ from idlelib.idle_test.htest import run
+ run(_color_delegator)
diff --git a/lib-python/3/idlelib/EditorWindow.py
b/lib-python/3/idlelib/EditorWindow.py
new file mode 100644
--- /dev/null
+++ b/lib-python/3/idlelib/EditorWindow.py
@@ -0,0 +1,1690 @@
+import importlib
+import importlib.abc
+import importlib.util
+import os
+import platform
+import re
+import string
+import sys
+from tkinter import *
+import tkinter.simpledialog as tkSimpleDialog
+import tkinter.messagebox as tkMessageBox
+import traceback
+import webbrowser
+
+from idlelib.MultiCall import MultiCallCreator
+from idlelib import WindowList
+from idlelib import SearchDialog
+from idlelib import GrepDialog
+from idlelib import ReplaceDialog
+from idlelib import PyParse
+from idlelib.configHandler import idleConf
+from idlelib import aboutDialog, textView, configDialog
+from idlelib import macosxSupport
+from idlelib import help
+
+# The default tab setting for a Text widget, in average-width characters.
+TK_TABWIDTH_DEFAULT = 8
+
+_py_version = ' (%s)' % platform.python_version()
+
+def _sphinx_version():
+ "Format sys.version_info to produce the Sphinx version string used to
install the chm docs"
+ major, minor, micro, level, serial = sys.version_info
+ release = '%s%s' % (major, minor)
+ release += '%s' % (micro,)
+ if level == 'candidate':
+ release += 'rc%s' % (serial,)
+ elif level != 'final':
+ release += '%s%s' % (level[0], serial)
+ return release
+
+
+class HelpDialog(object):
+
+ def __init__(self):
+ self.parent = None # parent of help window
+ self.dlg = None # the help window iteself
+
+ def display(self, parent, near=None):
+ """ Display the help dialog.
+
+ parent - parent widget for the help window
+
+ near - a Toplevel widget (e.g. EditorWindow or PyShell)
+ to use as a reference for placing the help window
+ """
+ import warnings as w
+ w.warn("EditorWindow.HelpDialog is no longer used by Idle.\n"
+ "It will be removed in 3.6 or later.\n"
+ "It has been replaced by private help.HelpWindow\n",
+ DeprecationWarning, stacklevel=2)
+ if self.dlg is None:
+ self.show_dialog(parent)
+ if near:
+ self.nearwindow(near)
+
+ def show_dialog(self, parent):
+ self.parent = parent
+ fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
+ self.dlg = dlg = textView.view_file(parent,'Help',fn, modal=False)
+ dlg.bind('<Destroy>', self.destroy, '+')
+
+ def nearwindow(self, near):
+ # Place the help dialog near the window specified by parent.
+ # Note - this may not reposition the window in Metacity
+ # if "/apps/metacity/general/disable_workarounds" is enabled
+ dlg = self.dlg
+ geom = (near.winfo_rootx() + 10, near.winfo_rooty() + 10)
+ dlg.withdraw()
+ dlg.geometry("=+%d+%d" % geom)
+ dlg.deiconify()
+ dlg.lift()
+
+ def destroy(self, ev=None):
+ self.dlg = None
+ self.parent = None
+
+helpDialog = HelpDialog() # singleton instance, no longer used
+
+
+class EditorWindow(object):
+ from idlelib.Percolator import Percolator
+ from idlelib.ColorDelegator import ColorDelegator, color_config
+ from idlelib.UndoDelegator import UndoDelegator
+ from idlelib.IOBinding import IOBinding, filesystemencoding, encoding
+ from idlelib import Bindings
+ from tkinter import Toplevel
+ from idlelib.MultiStatusBar import MultiStatusBar
+
+ help_url = None
+
+ def __init__(self, flist=None, filename=None, key=None, root=None):
+ if EditorWindow.help_url is None:
+ dochome = os.path.join(sys.base_prefix, 'Doc', 'index.html')
+ if sys.platform.count('linux'):
+ # look for html docs in a couple of standard places
+ pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
+ if os.path.isdir('/var/www/html/python/'): # "python2" rpm
+ dochome = '/var/www/html/python/index.html'
+ else:
+ basepath = '/usr/share/doc/' # standard location
+ dochome = os.path.join(basepath, pyver,
+ 'Doc', 'index.html')
+ elif sys.platform[:3] == 'win':
+ chmfile = os.path.join(sys.base_prefix, 'Doc',
+ 'Python%s.chm' % _sphinx_version())
+ if os.path.isfile(chmfile):
+ dochome = chmfile
+ elif sys.platform == 'darwin':
+ # documentation may be stored inside a python framework
+ dochome = os.path.join(sys.base_prefix,
+ 'Resources/English.lproj/Documentation/index.html')
+ dochome = os.path.normpath(dochome)
+ if os.path.isfile(dochome):
+ EditorWindow.help_url = dochome
+ if sys.platform == 'darwin':
+ # Safari requires real file:-URLs
+ EditorWindow.help_url = 'file://' + EditorWindow.help_url
+ else:
+ EditorWindow.help_url = "https://docs.python.org/%d.%d/" %
sys.version_info[:2]
+ self.flist = flist
+ root = root or flist.root
+ self.root = root
+ try:
+ sys.ps1
+ except AttributeError:
+ sys.ps1 = '>>> '
+ self.menubar = Menu(root)
+ self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
+ if flist:
+ self.tkinter_vars = flist.vars
+ #self.top.instance_dict makes flist.inversedict available to
+ #configDialog.py so it can access all EditorWindow instances
+ self.top.instance_dict = flist.inversedict
+ else:
+ self.tkinter_vars = {} # keys: Tkinter event names
+ # values: Tkinter variable instances
+ self.top.instance_dict = {}
+ self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
+ 'recent-files.lst')
+ self.text_frame = text_frame = Frame(top)
+ self.vbar = vbar = Scrollbar(text_frame, name='vbar')
+ self.width = idleConf.GetOption('main', 'EditorWindow',
+ 'width', type='int')
+ text_options = {
+ 'name': 'text',
+ 'padx': 5,
+ 'wrap': 'none',
+ 'highlightthickness': 0,
+ 'width': self.width,
+ 'height': idleConf.GetOption('main', 'EditorWindow',
+ 'height', type='int')}
+ if TkVersion >= 8.5:
+ # Starting with tk 8.5 we have to set the new tabstyle option
+ # to 'wordprocessor' to achieve the same display of tabs as in
+ # older tk versions.
+ text_options['tabstyle'] = 'wordprocessor'
+ self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
+ self.top.focused_widget = self.text
+
+ self.createmenubar()
+ self.apply_bindings()
+
+ self.top.protocol("WM_DELETE_WINDOW", self.close)
+ self.top.bind("<<close-window>>", self.close_event)
+ if macosxSupport.isAquaTk():
+ # Command-W on editorwindows doesn't work without this.
+ text.bind('<<close-window>>', self.close_event)
+ # Some OS X systems have only one mouse button, so use
+ # control-click for popup context menus there. For two
+ # buttons, AquaTk defines <2> as the right button, not <3>.
+ text.bind("<Control-Button-1>",self.right_menu_event)
+ text.bind("<2>", self.right_menu_event)
+ else:
+ # Elsewhere, use right-click for popup menus.
+ text.bind("<3>",self.right_menu_event)
+ text.bind("<<cut>>", self.cut)
+ text.bind("<<copy>>", self.copy)
+ text.bind("<<paste>>", self.paste)
+ text.bind("<<center-insert>>", self.center_insert_event)
+ text.bind("<<help>>", self.help_dialog)
+ text.bind("<<python-docs>>", self.python_docs)
+ text.bind("<<about-idle>>", self.about_dialog)
+ text.bind("<<open-config-dialog>>", self.config_dialog)
+ text.bind("<<open-module>>", self.open_module)
+ text.bind("<<do-nothing>>", lambda event: "break")
+ text.bind("<<select-all>>", self.select_all)
+ text.bind("<<remove-selection>>", self.remove_selection)
+ text.bind("<<find>>", self.find_event)
+ text.bind("<<find-again>>", self.find_again_event)
+ text.bind("<<find-in-files>>", self.find_in_files_event)
+ text.bind("<<find-selection>>", self.find_selection_event)
+ text.bind("<<replace>>", self.replace_event)
+ text.bind("<<goto-line>>", self.goto_line_event)
+ text.bind("<<smart-backspace>>",self.smart_backspace_event)
+ text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
+ text.bind("<<smart-indent>>",self.smart_indent_event)
+ text.bind("<<indent-region>>",self.indent_region_event)
+ text.bind("<<dedent-region>>",self.dedent_region_event)
+ text.bind("<<comment-region>>",self.comment_region_event)
+ text.bind("<<uncomment-region>>",self.uncomment_region_event)
+ text.bind("<<tabify-region>>",self.tabify_region_event)
+ text.bind("<<untabify-region>>",self.untabify_region_event)
+ text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
+ text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
+ text.bind("<Left>", self.move_at_edge_if_selection(0))
+ text.bind("<Right>", self.move_at_edge_if_selection(1))
+ text.bind("<<del-word-left>>", self.del_word_left)
+ text.bind("<<del-word-right>>", self.del_word_right)
+ text.bind("<<beginning-of-line>>", self.home_callback)
+
+ if flist:
+ flist.inversedict[self] = key
+ if key:
+ flist.dict[key] = self
+ text.bind("<<open-new-window>>", self.new_callback)
+ text.bind("<<close-all-windows>>", self.flist.close_all_callback)
+ text.bind("<<open-class-browser>>", self.open_class_browser)
+ text.bind("<<open-path-browser>>", self.open_path_browser)
+ text.bind("<<open-turtle-demo>>", self.open_turtle_demo)
+
+ self.set_status_bar()
+ vbar['command'] = text.yview
+ vbar.pack(side=RIGHT, fill=Y)
+ text['yscrollcommand'] = vbar.set
+ text['font'] = idleConf.GetFont(self.root, 'main', 'EditorWindow')
+ text_frame.pack(side=LEFT, fill=BOTH, expand=1)
+ text.pack(side=TOP, fill=BOTH, expand=1)
+ text.focus_set()
+
+ # usetabs true -> literal tab characters are used by indent and
+ # dedent cmds, possibly mixed with spaces if
+ # indentwidth is not a multiple of tabwidth,
+ # which will cause Tabnanny to nag!
+ # false -> tab characters are converted to spaces by indent
+ # and dedent cmds, and ditto TAB keystrokes
+ # Although use-spaces=0 can be configured manually in config-main.def,
+ # configuration of tabs v. spaces is not supported in the configuration
+ # dialog. IDLE promotes the preferred Python indentation: use spaces!
+ usespaces = idleConf.GetOption('main', 'Indent',
+ 'use-spaces', type='bool')
+ self.usetabs = not usespaces
+
+ # tabwidth is the display width of a literal tab character.
+ # CAUTION: telling Tk to use anything other than its default
+ # tab setting causes it to use an entirely different tabbing algorithm,
+ # treating tab stops as fixed distances from the left margin.
+ # Nobody expects this, so for now tabwidth should never be changed.
+ self.tabwidth = 8 # must remain 8 until Tk is fixed.
+
+ # indentwidth is the number of screen characters per indent level.
+ # The recommended Python indentation is four spaces.
+ self.indentwidth = self.tabwidth
+ self.set_notabs_indentwidth()
+
+ # If context_use_ps1 is true, parsing searches back for a ps1 line;
+ # else searches for a popular (if, def, ...) Python stmt.
+ self.context_use_ps1 = False
+
+ # When searching backwards for a reliable place to begin parsing,
+ # first start num_context_lines[0] lines back, then
+ # num_context_lines[1] lines back if that didn't work, and so on.
+ # The last value should be huge (larger than the # of lines in a
+ # conceivable file).
+ # Making the initial values larger slows things down more often.
+ self.num_context_lines = 50, 500, 5000000
+ self.per = per = self.Percolator(text)
+ self.undo = undo = self.UndoDelegator()
+ per.insertfilter(undo)
+ text.undo_block_start = undo.undo_block_start
+ text.undo_block_stop = undo.undo_block_stop
+ undo.set_saved_change_hook(self.saved_change_hook)
+ # IOBinding implements file I/O and printing functionality
+ self.io = io = self.IOBinding(self)
+ io.set_filename_change_hook(self.filename_change_hook)
+ self.good_load = False
+ self.set_indentation_params(False)
+ self.color = None # initialized below in self.ResetColorizer
+ if filename:
+ if os.path.exists(filename) and not os.path.isdir(filename):
+ if io.loadfile(filename):
+ self.good_load = True
+ is_py_src = self.ispythonsource(filename)
+ self.set_indentation_params(is_py_src)
+ else:
+ io.set_filename(filename)
+ self.good_load = True
+
+ self.ResetColorizer()
+ self.saved_change_hook()
+ self.update_recent_files_list()
+ self.load_extensions()
+ menu = self.menudict.get('windows')
+ if menu:
+ end = menu.index("end")
+ if end is None:
+ end = -1
+ if end >= 0:
+ menu.add_separator()
+ end = end + 1
+ self.wmenu_end = end
+ WindowList.register_callback(self.postwindowsmenu)
+
+ # Some abstractions so IDLE extensions are cross-IDE
+ self.askyesno = tkMessageBox.askyesno
+ self.askinteger = tkSimpleDialog.askinteger
+ self.showerror = tkMessageBox.showerror
+
+ def _filename_to_unicode(self, filename):
+ """Return filename as BMP unicode so diplayable in Tk."""
+ # Decode bytes to unicode.
+ if isinstance(filename, bytes):
+ try:
+ filename = filename.decode(self.filesystemencoding)
+ except UnicodeDecodeError:
+ try:
+ filename = filename.decode(self.encoding)
+ except UnicodeDecodeError:
+ # byte-to-byte conversion
+ filename = filename.decode('iso8859-1')
+ # Replace non-BMP char with diamond questionmark.
+ return re.sub('[\U00010000-\U0010FFFF]', '\ufffd', filename)
+
+ def new_callback(self, event):
+ dirname, basename = self.io.defaultfilename()
+ self.flist.new(dirname)
+ return "break"
+
+ def home_callback(self, event):
+ if (event.state & 4) != 0 and event.keysym == "Home":
+ # state&4==Control. If <Control-Home>, use the Tk binding.
+ return
+ if self.text.index("iomark") and \
+ self.text.compare("iomark", "<=", "insert lineend") and \
+ self.text.compare("insert linestart", "<=", "iomark"):
+ # In Shell on input line, go to just after prompt
+ insertpt = int(self.text.index("iomark").split(".")[1])
+ else:
+ line = self.text.get("insert linestart", "insert lineend")
+ for insertpt in range(len(line)):
+ if line[insertpt] not in (' ','\t'):
+ break
+ else:
+ insertpt=len(line)
+ lineat = int(self.text.index("insert").split('.')[1])
+ if insertpt == lineat:
+ insertpt = 0
+ dest = "insert linestart+"+str(insertpt)+"c"
+ if (event.state&1) == 0:
+ # shift was not pressed
+ self.text.tag_remove("sel", "1.0", "end")
+ else:
+ if not self.text.index("sel.first"):
+ # there was no previous selection
+ self.text.mark_set("my_anchor", "insert")
+ else:
+ if self.text.compare(self.text.index("sel.first"), "<",
+ self.text.index("insert")):
+ self.text.mark_set("my_anchor", "sel.first") # extend back
+ else:
+ self.text.mark_set("my_anchor", "sel.last") # extend
forward
+ first = self.text.index(dest)
+ last = self.text.index("my_anchor")
+ if self.text.compare(first,">",last):
+ first,last = last,first
+ self.text.tag_remove("sel", "1.0", "end")
+ self.text.tag_add("sel", first, last)
+ self.text.mark_set("insert", dest)
+ self.text.see("insert")
+ return "break"
+
+ def set_status_bar(self):
+ self.status_bar = self.MultiStatusBar(self.top)
+ sep = Frame(self.top, height=1, borderwidth=1, background='grey75')
+ if sys.platform == "darwin":
+ # Insert some padding to avoid obscuring some of the statusbar
+ # by the resize widget.
+ self.status_bar.set_label('_padding1', ' ', side=RIGHT)
+ self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
+ self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
+ self.status_bar.pack(side=BOTTOM, fill=X)
+ sep.pack(side=BOTTOM, fill=X)
+ self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
+ self.text.event_add("<<set-line-and-column>>",
+ "<KeyRelease>", "<ButtonRelease>")
+ self.text.after_idle(self.set_line_and_column)
+
+ def set_line_and_column(self, event=None):
+ line, column = self.text.index(INSERT).split('.')
+ self.status_bar.set_label('column', 'Col: %s' % column)
+ self.status_bar.set_label('line', 'Ln: %s' % line)
+
+ menu_specs = [
+ ("file", "_File"),
+ ("edit", "_Edit"),
+ ("format", "F_ormat"),
+ ("run", "_Run"),
+ ("options", "_Options"),
+ ("windows", "_Window"),
+ ("help", "_Help"),
+ ]
+
+
+ def createmenubar(self):
+ mbar = self.menubar
+ self.menudict = menudict = {}
+ for name, label in self.menu_specs:
+ underline, label = prepstr(label)
+ menudict[name] = menu = Menu(mbar, name=name, tearoff=0)
+ mbar.add_cascade(label=label, menu=menu, underline=underline)
+ if macosxSupport.isCarbonTk():
+ # Insert the application menu
+ menudict['application'] = menu = Menu(mbar, name='apple',
+ tearoff=0)
+ mbar.add_cascade(label='IDLE', menu=menu)
+ self.fill_menus()
+ self.recent_files_menu = Menu(self.menubar, tearoff=0)
+ self.menudict['file'].insert_cascade(3, label='Recent Files',
+ underline=0,
+ menu=self.recent_files_menu)
+ self.base_helpmenu_length = self.menudict['help'].index(END)
+ self.reset_help_menu_entries()
+
+ def postwindowsmenu(self):
+ # Only called when Windows menu exists
+ menu = self.menudict['windows']
+ end = menu.index("end")
+ if end is None:
+ end = -1
+ if end > self.wmenu_end:
+ menu.delete(self.wmenu_end+1, end)
+ WindowList.add_windows_to_menu(menu)
+
+ rmenu = None
+
+ def right_menu_event(self, event):
+ self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
+ if not self.rmenu:
+ self.make_rmenu()
+ rmenu = self.rmenu
+ self.event = event
+ iswin = sys.platform[:3] == 'win'
+ if iswin:
+ self.text.config(cursor="arrow")
+
+ for item in self.rmenu_specs:
+ try:
+ label, eventname, verify_state = item
+ except ValueError: # see issue1207589
+ continue
+
+ if verify_state is None:
+ continue
+ state = getattr(self, verify_state)()
+ rmenu.entryconfigure(label, state=state)
+
+
+ rmenu.tk_popup(event.x_root, event.y_root)
+ if iswin:
+ self.text.config(cursor="ibeam")
+
+ rmenu_specs = [
+ # ("Label", "<<virtual-event>>", "statefuncname"), ...
+ ("Close", "<<close-window>>", None), # Example
+ ]
+
+ def make_rmenu(self):
+ rmenu = Menu(self.text, tearoff=0)
+ for item in self.rmenu_specs:
+ label, eventname = item[0], item[1]
+ if label is not None:
+ def command(text=self.text, eventname=eventname):
+ text.event_generate(eventname)
+ rmenu.add_command(label=label, command=command)
+ else:
+ rmenu.add_separator()
+ self.rmenu = rmenu
+
+ def rmenu_check_cut(self):
+ return self.rmenu_check_copy()
+
+ def rmenu_check_copy(self):
+ try:
+ indx = self.text.index('sel.first')
+ except TclError:
+ return 'disabled'
+ else:
+ return 'normal' if indx else 'disabled'
+
+ def rmenu_check_paste(self):
+ try:
+ self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
+ except TclError:
+ return 'disabled'
+ else:
+ return 'normal'
+
+ def about_dialog(self, event=None):
+ "Handle Help 'About IDLE' event."
+ # Synchronize with macosxSupport.overrideRootMenu.about_dialog.
+ aboutDialog.AboutDialog(self.top,'About IDLE')
+
+ def config_dialog(self, event=None):
+ "Handle Options 'Configure IDLE' event."
+ # Synchronize with macosxSupport.overrideRootMenu.config_dialog.
+ configDialog.ConfigDialog(self.top,'Settings')
+
+ def help_dialog(self, event=None):
+ "Handle Help 'IDLE Help' event."
+ # Synchronize with macosxSupport.overrideRootMenu.help_dialog.
+ if self.root:
+ parent = self.root
+ else:
+ parent = self.top
+ help.show_idlehelp(parent)
+
+ def python_docs(self, event=None):
+ if sys.platform[:3] == 'win':
+ try:
+ os.startfile(self.help_url)
+ except OSError as why:
+ tkMessageBox.showerror(title='Document Start Failure',
+ message=str(why), parent=self.text)
+ else:
+ webbrowser.open(self.help_url)
+ return "break"
+
+ def cut(self,event):
+ self.text.event_generate("<<Cut>>")
+ return "break"
+
+ def copy(self,event):
+ if not self.text.tag_ranges("sel"):
+ # There is no selection, so do nothing and maybe interrupt.
+ return
+ self.text.event_generate("<<Copy>>")
+ return "break"
+
+ def paste(self,event):
+ self.text.event_generate("<<Paste>>")
+ self.text.see("insert")
+ return "break"
+
+ def select_all(self, event=None):
+ self.text.tag_add("sel", "1.0", "end-1c")
+ self.text.mark_set("insert", "1.0")
+ self.text.see("insert")
+ return "break"
+
+ def remove_selection(self, event=None):
+ self.text.tag_remove("sel", "1.0", "end")
+ self.text.see("insert")
+
+ def move_at_edge_if_selection(self, edge_index):
+ """Cursor move begins at start or end of selection
+
+ When a left/right cursor key is pressed create and return to Tkinter a
+ function which causes a cursor move from the associated edge of the
+ selection.
+
+ """
+ self_text_index = self.text.index
+ self_text_mark_set = self.text.mark_set
+ edges_table = ("sel.first+1c", "sel.last-1c")
+ def move_at_edge(event):
+ if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
+ try:
+ self_text_index("sel.first")
+ self_text_mark_set("insert", edges_table[edge_index])
+ except TclError:
+ pass
+ return move_at_edge
+
+ def del_word_left(self, event):
+ self.text.event_generate('<Meta-Delete>')
+ return "break"
+
+ def del_word_right(self, event):
+ self.text.event_generate('<Meta-d>')
+ return "break"
+
+ def find_event(self, event):
+ SearchDialog.find(self.text)
+ return "break"
+
+ def find_again_event(self, event):
+ SearchDialog.find_again(self.text)
+ return "break"
+
+ def find_selection_event(self, event):
+ SearchDialog.find_selection(self.text)
+ return "break"
+
+ def find_in_files_event(self, event):
+ GrepDialog.grep(self.text, self.io, self.flist)
+ return "break"
+
+ def replace_event(self, event):
+ ReplaceDialog.replace(self.text)
+ return "break"
+
+ def goto_line_event(self, event):
+ text = self.text
+ lineno = tkSimpleDialog.askinteger("Goto",
+ "Go to line number:",parent=text)
+ if lineno is None:
+ return "break"
+ if lineno <= 0:
+ text.bell()
+ return "break"
+ text.mark_set("insert", "%d.0" % lineno)
+ text.see("insert")
+
+ def open_module(self, event=None):
+ # XXX Shouldn't this be in IOBinding?
+ try:
+ name = self.text.get("sel.first", "sel.last")
+ except TclError:
+ name = ""
+ else:
+ name = name.strip()
+ name = tkSimpleDialog.askstring("Module",
+ "Enter the name of a Python module\n"
+ "to search on sys.path and open:",
+ parent=self.text, initialvalue=name)
+ if name:
+ name = name.strip()
+ if not name:
+ return
+ # XXX Ought to insert current file's directory in front of path
+ try:
+ spec = importlib.util.find_spec(name)
+ except (ValueError, ImportError) as msg:
+ tkMessageBox.showerror("Import error", str(msg), parent=self.text)
+ return
+ if spec is None:
+ tkMessageBox.showerror("Import error", "module not found",
+ parent=self.text)
+ return
+ if not isinstance(spec.loader, importlib.abc.SourceLoader):
+ tkMessageBox.showerror("Import error", "not a source-based module",
+ parent=self.text)
+ return
+ try:
+ file_path = spec.loader.get_filename(name)
+ except AttributeError:
+ tkMessageBox.showerror("Import error",
+ "loader does not support get_filename",
+ parent=self.text)
+ return
+ if self.flist:
+ self.flist.open(file_path)
+ else:
+ self.io.loadfile(file_path)
+ return file_path
+
+ def open_class_browser(self, event=None):
+ filename = self.io.filename
+ if not (self.__class__.__name__ == 'PyShellEditorWindow'
+ and filename):
+ filename = self.open_module()
+ if filename is None:
+ return
+ head, tail = os.path.split(filename)
+ base, ext = os.path.splitext(tail)
+ from idlelib import ClassBrowser
+ ClassBrowser.ClassBrowser(self.flist, base, [head])
+
+ def open_path_browser(self, event=None):
+ from idlelib import PathBrowser
+ PathBrowser.PathBrowser(self.flist)
+
+ def open_turtle_demo(self, event = None):
+ import subprocess
+
+ cmd = [sys.executable,
+ '-c',
+ 'from turtledemo.__main__ import main; main()']
+ subprocess.Popen(cmd, shell=False)
+
+ def gotoline(self, lineno):
+ if lineno is not None and lineno > 0:
+ self.text.mark_set("insert", "%d.0" % lineno)
+ self.text.tag_remove("sel", "1.0", "end")
+ self.text.tag_add("sel", "insert", "insert +1l")
+ self.center()
+
+ def ispythonsource(self, filename):
+ if not filename or os.path.isdir(filename):
+ return True
+ base, ext = os.path.splitext(os.path.basename(filename))
+ if os.path.normcase(ext) in (".py", ".pyw"):
+ return True
+ line = self.text.get('1.0', '1.0 lineend')
+ return line.startswith('#!') and 'python' in line
+
+ def close_hook(self):
+ if self.flist:
+ self.flist.unregister_maybe_terminate(self)
+ self.flist = None
+
+ def set_close_hook(self, close_hook):
+ self.close_hook = close_hook
+
+ def filename_change_hook(self):
+ if self.flist:
+ self.flist.filename_changed_edit(self)
+ self.saved_change_hook()
+ self.top.update_windowlist_registry(self)
+ self.ResetColorizer()
+
+ def _addcolorizer(self):
+ if self.color:
+ return
+ if self.ispythonsource(self.io.filename):
+ self.color = self.ColorDelegator()
+ # can add more colorizers here...
+ if self.color:
+ self.per.removefilter(self.undo)
+ self.per.insertfilter(self.color)
+ self.per.insertfilter(self.undo)
+
+ def _rmcolorizer(self):
+ if not self.color:
+ return
+ self.color.removecolors()
+ self.per.removefilter(self.color)
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit