Index: __init__.py =================================================================== --- __init__.py (revision 85696) +++ __init__.py (working copy) @@ -1 +1,4 @@ # Dummy file to make this a package. +# Imports are relative (e.g. from . import PyShell), but +# in PyShell.py there is a specific reference to idlelib (or vidle) +# in connection with using __import__ (constructed import). Index: aboutDialog.py =================================================================== --- aboutDialog.py (revision 85696) +++ aboutDialog.py (working copy) @@ -2,11 +2,11 @@ """ -from tkinter import * +from Tkinter import * import os -from idlelib import textView -from idlelib import idlever +from . import textView +from . import idlever class AboutDialog(Toplevel): """Modal about dialog for idle @@ -144,7 +144,7 @@ # test the dialog root = Tk() def run(): - from idlelib import aboutDialog + from . import aboutDialog aboutDialog.AboutDialog(root, 'About') Button(root, text='Dialog', command=run).pack() root.mainloop() Index: AutoComplete.py =================================================================== --- AutoComplete.py (revision 85696) +++ AutoComplete.py (working copy) @@ -7,7 +7,7 @@ import sys import string -from idlelib.configHandler import idleConf +from .configHandler import idleConf # This string includes all chars that may be in a file name (without a path # separator) @@ -18,8 +18,8 @@ # These constants represent the two different types of completions COMPLETE_ATTRIBUTES, COMPLETE_FILES = range(1, 2+1) -from idlelib import AutoCompleteWindow -from idlelib.HyperParser import HyperParser +from . import AutoCompleteWindow +from . HyperParser import HyperParser import __main__ Index: AutoCompleteWindow.py =================================================================== --- AutoCompleteWindow.py (revision 85696) +++ AutoCompleteWindow.py (working copy) @@ -1,9 +1,9 @@ """ 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 +from Tkinter import * +from .MultiCall import MC_SHIFT +from .AutoComplete import COMPLETE_FILES, COMPLETE_ATTRIBUTES HIDE_VIRTUAL_EVENT_NAME = "<>" HIDE_SEQUENCES = ("", "") Index: Bindings.py =================================================================== --- Bindings.py (revision 85696) +++ Bindings.py (working copy) @@ -9,8 +9,8 @@ """ import sys -from idlelib.configHandler import idleConf -from idlelib import macosxSupport +from .configHandler import idleConf +from . import macosxSupport menudefs = [ # underscore prefixes character to underscore Index: CallTips.py =================================================================== --- CallTips.py (revision 85696) +++ CallTips.py (working copy) @@ -10,8 +10,8 @@ import types import inspect -from idlelib import CallTipWindow -from idlelib.HyperParser import HyperParser +from . import CallTipWindow +from .HyperParser import HyperParser import __main__ Index: CallTipWindow.py =================================================================== --- CallTipWindow.py (revision 85696) +++ CallTipWindow.py (working copy) @@ -4,7 +4,7 @@ Used by the CallTips IDLE extension. """ -from tkinter import * +from Tkinter import * HIDE_VIRTUAL_EVENT_NAME = "<>" HIDE_SEQUENCES = ("", "") Index: ClassBrowser.py =================================================================== --- ClassBrowser.py (revision 85696) +++ ClassBrowser.py (working copy) @@ -10,15 +10,38 @@ - add base classes to class browser tree """ +__all__ = ['ClassBrowser'] + 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 +from . import PyShell +from .WindowList import ListedToplevel +from .TreeWidget import TreeNode, TreeItem, ScrolledCanvas +from .configHandler import idleConf +def collect_objects(d, name=None): + items = [] + objects = {} + for key, cl in list(d.items()): + if name is None or 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)) + objects[s] = cl + return objects, items + class ClassBrowser: def __init__(self, flist, name, path): @@ -72,8 +95,8 @@ def GetSubList(self): sublist = [] - for name in self.listclasses(): - item = ClassBrowserTreeItem(name, self.classes, self.file) + for name in self.listobjects(): + item = ObjectBrowserTreeItem(name, self.classes, self.file) sublist.append(item) return sublist @@ -87,7 +110,7 @@ def IsExpandable(self): return os.path.normcase(self.file[-3:]) == ".py" - def listclasses(self): + def listobjects(self): dir, file = os.path.split(self.file) name, ext = os.path.splitext(file) if os.path.normcase(ext) != ".py": @@ -96,31 +119,11 @@ dict = pyclbr.readmodule_ex(name, [dir] + sys.path) except ImportError as msg: 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 + self.classes, items = collect_objects(dict, name) items.sort() - list = [] - for item, s in items: - list.append(s) - return list + return [s for item, s in items] -class ClassBrowserTreeItem(TreeItem): +class ObjectBrowserTreeItem(TreeItem): def __init__(self, name, classes, file): self.name = name @@ -147,7 +150,7 @@ def IsExpandable(self): if self.cl: try: - return not not self.cl.methods + return not not self.cl.objects except AttributeError: return False @@ -155,8 +158,9 @@ if not self.cl: return [] sublist = [] - for name in self.listmethods(): - item = MethodBrowserTreeItem(name, self.cl, self.file) + for obj in self.listobjects(): + classes, item_name = obj + item = ObjectBrowserTreeItem(item_name, classes, self.file) sublist.append(item) return sublist @@ -168,40 +172,16 @@ lineno = self.cl.lineno edit.gotoline(lineno) - def listmethods(self): + def listobjects(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 + result = [] + for name, ob in list(self.cl.objects.items()): + classes, items = collect_objects({name:ob}) + result.append((ob.lineno, classes, items[0][1])) + result.sort() + return [item[1:] for item in result] -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 = PyShell.flist.open(self.file) - edit.gotoline(self.cl.methods[self.name]) - def main(): try: file = __file__ Index: CodeContext.py =================================================================== --- CodeContext.py (revision 85696) +++ CodeContext.py (working copy) @@ -9,11 +9,11 @@ not open blocks are not shown in the context hints pane. """ -import tkinter -from tkinter.constants import TOP, LEFT, X, W, SUNKEN +import Tkinter +from Tkconstants import TOP, LEFT, X, W, SUNKEN import re from sys import maxsize as INFINITY -from idlelib.configHandler import idleConf +from .configHandler import idleConf BLOCKOPENERS = set(["class", "def", "elif", "else", "except", "finally", "for", "if", "try", "while", "with"]) @@ -69,7 +69,7 @@ border = 0 for widget in widgets: border += int(str( widget.cget('border') )) - self.label = tkinter.Label(self.editwin.top, + self.label = Tkinter.Label(self.editwin.top, text="\n" * (self.context_depth - 1), anchor=W, justify=LEFT, font=self.textfont, Index: ColorDelegator.py =================================================================== --- ColorDelegator.py (revision 85696) +++ ColorDelegator.py (working copy) @@ -1,10 +1,10 @@ import time import re import keyword -import builtins -from tkinter import * -from idlelib.Delegator import Delegator -from idlelib.configHandler import idleConf +import __builtin__ +from Tkinter import * +from .Delegator import Delegator +from .configHandler import idleConf DEBUG = False @@ -14,7 +14,7 @@ def make_pat(): kw = r"\b" + any("KEYWORD", keyword.kwlist) + r"\b" - builtinlist = [str(name) for name in dir(builtins) + builtinlist = [str(name) for name in dir(__builtin__) if not name.startswith('_')] # self.file = open("file") : # 1st 'file' colorized normal, 2nd as builtin, 3rd as string @@ -248,7 +248,7 @@ self.tag_remove(tag, "1.0", "end") def main(): - from idlelib.Percolator import Percolator + from .Percolator import Percolator root = Tk() root.wm_protocol("WM_DELETE_WINDOW", root.quit) text = Text(background="white") Index: config-extensions.def =================================================================== --- config-extensions.def (revision 85696) +++ config-extensions.def (working copy) @@ -29,6 +29,12 @@ # See config-keys.def for notes on specifying keys and extend.txt for # information on creating IDLE extensions. +[FileRevert] +enable=1 +enable_shell=0 +[FileRevert_cfgBindings] +revert-file= + [FormatParagraph] enable=1 [FormatParagraph_cfgBindings] @@ -46,6 +52,7 @@ [ScriptBinding] enable=1 +enable_shell=0 [ScriptBinding_cfgBindings] run-module= check-module= Index: config-main.def =================================================================== --- config-main.def (revision 85696) +++ config-main.def (working copy) @@ -18,6 +18,9 @@ # ~/.idlerc/config-highlight.cfg the user highlighting config file # ~/.idlerc/config-keys.cfg the user keybinding config file # +# On Windows 7 the .idlerc directory is at +# C:\Users\username\.idlerc +# # On Windows2000 and Windows XP the .idlerc directory is at # Documents and Settings\\.idlerc # @@ -46,6 +49,8 @@ [General] editor-on-startup= 0 autosave= 0 +save-before-run= 1 +signal-first-error= 1 print-command-posix=lpr %s print-command-win=start /min notepad /p %s delete-exitfunc= 1 Index: configDialog.py =================================================================== --- configDialog.py (revision 85696) +++ configDialog.py (working copy) @@ -9,19 +9,18 @@ Refer to comments in EditorWindow autoindent code for details. """ -from tkinter import * -import tkinter.messagebox as tkMessageBox -import tkinter.colorchooser as tkColorChooser -import tkinter.font as tkFont -import copy +from Tkinter import * +import tkMessageBox +import tkColorChooser +import tkFont -from idlelib.configHandler import idleConf -from idlelib.dynOptionMenuWidget import DynOptionMenu -from idlelib.tabbedpages import TabbedPageSet -from idlelib.keybindingDialog import GetKeysDialog -from idlelib.configSectionNameDialog import GetCfgSectionNameDialog -from idlelib.configHelpSourceEdit import GetHelpSourceDialog -from idlelib import macosxSupport +from .configHandler import idleConf +from .dynOptionMenuWidget import DynOptionMenu +from .tabbedpages import TabbedPageSet +from .keybindingDialog import GetKeysDialog +from .configSectionNameDialog import GetCfgSectionNameDialog +from .configHelpSourceEdit import GetHelpSourceDialog +from . import macosxSupport class ConfigDialog(Toplevel): @@ -334,6 +333,8 @@ self.paraWidth=StringVar(self) self.startupEdit=IntVar(self) self.autoSave=IntVar(self) + self.saveBeforeRun=BooleanVar(self) + self.signalOnErr = BooleanVar(self) self.encoding=StringVar(self) self.userHelpBrowser=BooleanVar(self) self.helpBrowser=StringVar(self) @@ -341,26 +342,38 @@ #body frame=self.tabPages.pages['General'].frame #body section frames + frameStartup=LabelFrame(frame,borderwidth=2,relief=GROOVE, + text=' Startup Preferences ') frameRun=LabelFrame(frame,borderwidth=2,relief=GROOVE, - text=' Startup Preferences ') - frameSave=LabelFrame(frame,borderwidth=2,relief=GROOVE, - text=' Autosave Preferences ') + text=' Run (F5) Preferences ') frameWinSize=Frame(frame,borderwidth=2,relief=GROOVE) frameParaSize=Frame(frame,borderwidth=2,relief=GROOVE) frameHelp=LabelFrame(frame,borderwidth=2,relief=GROOVE, text=' Additional Help Sources ') - #frameRun - labelRunChoiceTitle=Label(frameRun,text='At Startup') - radioStartupEdit=Radiobutton(frameRun,variable=self.startupEdit, + #frameStartup + labelRunChoiceTitle=Label(frameStartup,text='At Startup') + radioStartupEdit=Radiobutton(frameStartup,variable=self.startupEdit, value=1,command=self.SetKeysType,text="Open Edit Window") - radioStartupShell=Radiobutton(frameRun,variable=self.startupEdit, + radioStartupShell=Radiobutton(frameStartup,variable=self.startupEdit, value=0,command=self.SetKeysType,text='Open Shell Window') - #frameSave - labelRunSaveTitle=Label(frameSave,text='At Start of Run (F5) ') - radioSaveAsk=Radiobutton(frameSave,variable=self.autoSave, - value=0,command=self.SetKeysType,text="Prompt to Save") - radioSaveAuto=Radiobutton(frameSave,variable=self.autoSave, - value=1,command=self.SetKeysType,text='No Prompt') + #frameRun + labelSaveBeforeRun=Label(frameRun, + text='If file has never been saved ') + radioSaveBefore=Radiobutton(frameRun,variable=self.saveBeforeRun, + value=1,text="Prompt to Save") + radioSaveToTemp=Radiobutton(frameRun,variable=self.saveBeforeRun, + value=0,text="No prompt") + labelAutoSave=Label(frameRun, text='If file has been saved before ') + radioSaveAsk=Radiobutton(frameRun,variable=self.autoSave, + value=0,text="Prompt to Save") + radioSaveAuto=Radiobutton(frameRun,variable=self.autoSave, + value=1,text="No prompt") + labelSignalOnErr = Label(frameRun, + text="On first error") + signalErr = Radiobutton(frameRun, variable=self.signalOnErr, + value=1, text="Bring shell forward") + noSignalErr = Radiobutton(frameRun, variable=self.signalOnErr, + value=0, text="Do nothing") #frameWinSize labelWinSizeTitle=Label(frameWinSize,text='Initial Window Size'+ ' (in characters)') @@ -392,19 +405,26 @@ state=DISABLED,width=8,command=self.HelpListItemRemove) #widget packing #body + frameStartup.pack(side=TOP,padx=5,pady=5,fill=X) frameRun.pack(side=TOP,padx=5,pady=5,fill=X) - frameSave.pack(side=TOP,padx=5,pady=5,fill=X) frameWinSize.pack(side=TOP,padx=5,pady=5,fill=X) frameParaSize.pack(side=TOP,padx=5,pady=5,fill=X) frameHelp.pack(side=TOP,padx=5,pady=5,expand=TRUE,fill=BOTH) - #frameRun + #frameStartup labelRunChoiceTitle.pack(side=LEFT,anchor=W,padx=5,pady=5) radioStartupShell.pack(side=RIGHT,anchor=W,padx=5,pady=5) radioStartupEdit.pack(side=RIGHT,anchor=W,padx=5,pady=5) - #frameSave - labelRunSaveTitle.pack(side=LEFT,anchor=W,padx=5,pady=5) - radioSaveAuto.pack(side=RIGHT,anchor=W,padx=5,pady=5) - radioSaveAsk.pack(side=RIGHT,anchor=W,padx=5,pady=5) + #frameRun + commonOpts = {'sticky': W, 'padx': 5, 'pady': 5} + labelSaveBeforeRun.grid(row=0, column=0, **commonOpts) + radioSaveBefore.grid(row=0, column=1, **commonOpts) + radioSaveToTemp.grid(row=0, column=2, **commonOpts) + labelAutoSave.grid(row=1, column=0, **commonOpts) + radioSaveAsk.grid(row=1, column=1, **commonOpts) + radioSaveAuto.grid(row=1, column=2, **commonOpts) + labelSignalOnErr.grid(row=2, column=0, **commonOpts) + signalErr.grid(row=2, column=1, **commonOpts) + noSignalErr.grid(row=2, column=2, **commonOpts) #frameWinSize labelWinSizeTitle.pack(side=LEFT,anchor=W,padx=5,pady=5) entryWinHeight.pack(side=RIGHT,anchor=E,padx=10,pady=5) @@ -444,6 +464,8 @@ self.startupEdit.trace_variable('w',self.VarChanged_startupEdit) self.autoSave.trace_variable('w',self.VarChanged_autoSave) self.encoding.trace_variable('w',self.VarChanged_encoding) + self.saveBeforeRun.trace_variable('w', self.VarChanged_saveBeforeRun) + self.signalOnErr.trace_variable('w', self.VarChanged_signalOnErr) def VarChanged_fontSize(self,*params): value=self.fontSize.get() @@ -541,6 +563,14 @@ value=self.encoding.get() self.AddChangedItem('main','EditorWindow','encoding',value) + def VarChanged_saveBeforeRun(self,*params): + value = self.saveBeforeRun.get() + self.AddChangedItem('main','General','save-before-run',value) + + def VarChanged_signalOnErr(self,*params): + value = self.signalOnErr.get() + self.AddChangedItem('main','General','signal-first-error',value) + def ResetChangedItems(self): #When any config item is changed in this dialog, an entry #should be made in the relevant section (config type) of this @@ -696,6 +726,12 @@ 'to delete the key set %r ?' % (keySetName), parent=self): return + #revert to default key set + self.keysAreBuiltin.set(idleConf.defaultCfg['main'].Get('Keys','default')) + self.builtinKeys.set(idleConf.defaultCfg['main'].Get('Keys','name')) + #user can't back out of these changes, they must be applied now + self.Apply() + self.SetKeysType() #remove key set from config idleConf.userCfg['keys'].remove_section(keySetName) if keySetName in self.changedItems['keys']: @@ -710,12 +746,6 @@ self.optMenuKeysCustom.SetMenu(itemList,'- no custom keys -') else: self.optMenuKeysCustom.SetMenu(itemList,itemList[0]) - #revert to default key set - self.keysAreBuiltin.set(idleConf.defaultCfg['main'].Get('Keys','default')) - self.builtinKeys.set(idleConf.defaultCfg['main'].Get('Keys','name')) - #user can't back out of these changes, they must be applied now - self.Apply() - self.SetKeysType() def DeleteCustomTheme(self): themeName=self.customTheme.get() @@ -1020,6 +1050,12 @@ #autosave state self.autoSave.set(idleConf.GetOption('main', 'General', 'autosave', default=0, type='bool')) + # save before run + self.saveBeforeRun.set(idleConf.GetOption('main', 'General', + 'save-before-run', default=1, type='bool')) + # bring shell forward on first error + self.signalOnErr.set(idleConf.GetOption('main', 'General', + 'signal-first-error', default=1, type='bool')) #initial window size self.winWidth.set(idleConf.GetOption('main','EditorWindow','width')) self.winHeight.set(idleConf.GetOption('main','EditorWindow','height')) Index: configHandler.py =================================================================== --- configHandler.py (revision 85696) +++ configHandler.py (working copy) @@ -20,8 +20,8 @@ import os import sys -from idlelib import macosxSupport -from configparser import ConfigParser, NoOptionError, NoSectionError +from . import macosxSupport +from ConfigParser import ConfigParser, NoOptionError, NoSectionError class InvalidConfigType(Exception): pass class InvalidConfigSet(Exception): pass Index: configHelpSourceEdit.py =================================================================== --- configHelpSourceEdit.py (revision 85696) +++ configHelpSourceEdit.py (working copy) @@ -3,9 +3,9 @@ import os import sys -from tkinter import * -import tkinter.messagebox as tkMessageBox -import tkinter.filedialog as tkFileDialog +from Tkinter import * +import tkMessageBox +import tkFileDialog class GetHelpSourceDialog(Toplevel): def __init__(self, parent, title, menuItem='', filePath=''): Index: configSectionNameDialog.py =================================================================== --- configSectionNameDialog.py (revision 85696) +++ configSectionNameDialog.py (working copy) @@ -2,8 +2,8 @@ Dialog that allows user to specify a new config file section name. Used to get new highlight theme and keybinding set names. """ -from tkinter import * -import tkinter.messagebox as tkMessageBox +from Tkinter import * +import tkMessageBox class GetCfgSectionNameDialog(Toplevel): def __init__(self,parent,title,message,usedNames): Index: Debugger.py =================================================================== --- Debugger.py (revision 85696) +++ Debugger.py (working copy) @@ -1,12 +1,11 @@ import os import bdb import types -from tkinter import * -from idlelib.WindowList import ListedToplevel -from idlelib.ScrolledList import ScrolledList -from idlelib import macosxSupport +from Tkinter import * +from .WindowList import ListedToplevel +from .ScrolledList import ScrolledList +from . import macosxSupport - class Idb(bdb.Bdb): def __init__(self, gui): Index: dynOptionMenuWidget.py =================================================================== --- dynOptionMenuWidget.py (revision 85696) +++ dynOptionMenuWidget.py (working copy) @@ -2,8 +2,8 @@ OptionMenu widget modified to allow dynamic menu reconfiguration and setting of highlightthickness """ -from tkinter import OptionMenu -from tkinter import _setit +from Tkinter import OptionMenu +from Tkinter import _setit import copy class DynOptionMenu(OptionMenu): Index: EditorWindow.py =================================================================== --- EditorWindow.py (revision 85696) +++ EditorWindow.py (working copy) @@ -3,22 +3,23 @@ import re import string import imp -from tkinter import * -import tkinter.simpledialog as tkSimpleDialog -import tkinter.messagebox as tkMessageBox +from itertools import count +from Tkinter import * +import tkSimpleDialog +import tkMessageBox import traceback import webbrowser -from idlelib.MultiCall import MultiCallCreator -from idlelib import idlever -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 .MultiCall import MultiCallCreator +from . import idlever +from . import WindowList +from . import SearchDialog +from . import GrepDialog +from . import ReplaceDialog +from . import PyParse +from .configHandler import idleConf +from . import aboutDialog, textView, configDialog +from . import macosxSupport # The default tab setting for a Text widget, in average-width characters. TK_TABWIDTH_DEFAULT = 8 @@ -52,14 +53,41 @@ raise ImportError('No source for module ' + module.__name__) return file, filename, descr +class _singledialog(object): + """Just a given dialog should be executing at any time. Trying to + create a new one results in bringing to front the running dialog.""" + + def __init__(self, meth): + self.meth = meth + self.dlg = None + + def __get__(self, instance, owner): + self.instance = instance + return self + + def __call__(self, *args): + if self.dlg: # dialog is already running + # bring it to front + self.dlg.withdraw() + self.dlg.deiconify() + self.dlg.lift() + + else: # dialog not running, start it and save the instance + self.dlg = self.meth(self.instance, *args) + self.dlg.bind('', self._clear_dlg) + + def _clear_dlg(self, *args): + """Dialog is being destroyed. A new dialog instance can be created.""" + self.dlg = None + class EditorWindow(object): - from idlelib.Percolator import Percolator - from idlelib.ColorDelegator import ColorDelegator - 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 + from .Percolator import Percolator + from .ColorDelegator import ColorDelegator + from .UndoDelegator import UndoDelegator + from .IOBinding import IOBinding, filesystemencoding, encoding + from . import Bindings + from Tkinter import Toplevel + from .MultiStatusBar import MultiStatusBar help_url = None @@ -103,12 +131,12 @@ self.menubar = Menu(root) self.top = top = WindowList.ListedToplevel(root, menu=self.menubar) if flist: - self.tkinter_vars = flist.vars + self.Tkinter_vars = flist.vars #self.top.instance_dict makes flist.inversedict avalable to #configDialog.py so it can access all EditorWindow instaces self.top.instance_dict = flist.inversedict else: - self.tkinter_vars = {} # keys: Tkinter event names + self.Tkinter_vars = {} # keys: Tkinter event names # values: Tkinter variable instances self.top.instance_dict = {} self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(), @@ -405,7 +433,6 @@ rmenu = None def right_menu_event(self, event): - self.text.tag_remove("sel", "1.0", "end") self.text.mark_set("insert", "@%d,%d" % (event.x, event.y)) if not self.rmenu: self.make_rmenu() @@ -414,32 +441,73 @@ iswin = sys.platform[:3] == 'win' if iswin: self.text.config(cursor="arrow") + + for label, eventname, verify_state in self.rmenu_specs: + 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", "<>"), ... - ("Close", "<>"), # Example + # ("Label", "<>", "statefuncname"), ... + ("Close", "<>", None), # Example ] def make_rmenu(self): rmenu = Menu(self.text, tearoff=0) - for label, eventname in self.rmenu_specs: - def command(text=self.text, eventname=eventname): - text.event_generate(eventname) - rmenu.add_command(label=label, command=command) + for label, eventname, _ in self.rmenu_specs: + 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): + if getattr(self, 'interp', None) is None: + return self.rmenu_check_copy() + + text = self.text + try: + indx = text.index('sel.first') + except TclError: + return 'disabled' + else: + if indx and text.compare(indx, '>=', text.index('iomark')): + return 'normal' + return 'disabled' + + 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): aboutDialog.AboutDialog(self.top,'About IDLE') def config_dialog(self, event=None): configDialog.ConfigDialog(self.top,'Settings') + @_singledialog def help_dialog(self, event=None): fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt') - textView.view_file(self.top,'Help',fn) + return textView.view_file(self.top, 'Help', fn, modal=False) def python_docs(self, event=None): if sys.platform[:3] == 'win': @@ -578,11 +646,11 @@ return None head, tail = os.path.split(filename) base, ext = os.path.splitext(tail) - from idlelib import ClassBrowser + from . import ClassBrowser ClassBrowser.ClassBrowser(self.flist, base, [head]) def open_path_browser(self, event=None): - from idlelib import PathBrowser + from . import PathBrowser PathBrowser.PathBrowser(self.flist) def gotoline(self, lineno): @@ -784,8 +852,8 @@ for instance in self.top.instance_dict: menu = instance.recent_files_menu menu.delete(1, END) # clear, and rebuild: - for i, file_name in enumerate(rf_list): - file_name = file_name.rstrip() # zap \n + for i, file in zip(count(), rf_list): + file_name = file[0:-1] # zap \n # make unicode string to display non-ASCII chars correctly ufile_name = self._filename_to_unicode(file_name) callback = instance.__recent_file_callback(file_name) @@ -897,7 +965,7 @@ self.color.close(False) self.color = None self.text = None - self.tkinter_vars = None + self.Tkinter_vars = None self.per.close() self.per = None self.top.destroy() @@ -1012,10 +1080,10 @@ raise NameError(name) def get_var_obj(self, name, vartype=None): - var = self.tkinter_vars.get(name) + var = self.Tkinter_vars.get(name) if not var and vartype: # create a Tkinter variable object with self.text as master: - self.tkinter_vars[name] = var = vartype(self.text) + self.Tkinter_vars[name] = var = vartype(self.text) return var # Tk implementations of "virtual text methods" -- each platform Index: FileList.py =================================================================== --- FileList.py (revision 85696) +++ FileList.py (working copy) @@ -1,12 +1,11 @@ import os -from tkinter import * -import tkinter.messagebox as tkMessageBox +from Tkinter import * +import tkMessageBox - class FileList: # N.B. this import overridden in PyShellFileList. - from idlelib.EditorWindow import EditorWindow + from .EditorWindow import EditorWindow def __init__(self, root): self.root = root @@ -111,7 +110,7 @@ def _test(): - from idlelib.EditorWindow import fixwordbreaks + from .EditorWindow import fixwordbreaks import sys root = Tk() fixwordbreaks(root) Index: FormatParagraph.py =================================================================== --- FormatParagraph.py (revision 85696) +++ FormatParagraph.py (working copy) @@ -15,7 +15,7 @@ # * Fancy comments, like this bulleted list, arent handled :-) import re -from idlelib.configHandler import idleConf +from .configHandler import idleConf class FormatParagraph: Index: GrepDialog.py =================================================================== --- GrepDialog.py (revision 85696) +++ GrepDialog.py (working copy) @@ -1,9 +1,9 @@ import os import fnmatch import sys -from tkinter import * -from idlelib import SearchEngine -from idlelib.SearchDialogBase import SearchDialogBase +from Tkinter import * +from . import SearchEngine +from .SearchDialogBase import SearchDialogBase def grep(text, io=None, flist=None): root = text._root() @@ -63,7 +63,7 @@ if not path: self.top.bell() return - from idlelib.OutputWindow import OutputWindow + from .OutputWindow import OutputWindow save = sys.stdout try: sys.stdout = OutputWindow(self.flist) Index: help.txt =================================================================== --- help.txt (revision 85696) +++ help.txt (working copy) @@ -25,6 +25,7 @@ --- Close -- Close current window (asks to save if unsaved) Exit -- Close all windows, quit (asks to save if unsaved) + Revert -- Discard all changes since the last save Edit Menu: @@ -220,6 +221,9 @@ Command history: + F6 adjusts the view in the shell window to display the start of the + most recent non-empty shell restart. + Alt-p retrieves previous command matching what you have typed. Alt-n retrieves next. (These are Control-p, Control-n on the Mac) Index: HyperParser.py =================================================================== --- HyperParser.py (revision 85696) +++ HyperParser.py (working copy) @@ -10,7 +10,7 @@ import string import keyword -from idlelib import PyParse +from . import PyParse class HyperParser: Index: idle.py =================================================================== --- idle.py (revision 85696) +++ idle.py (working copy) @@ -2,10 +2,10 @@ import sys # If we are working on a development version of IDLE, we need to prepend the -# parent of this idlelib dir to sys.path. Otherwise, importing idlelib gets +# parent of this dir to sys.path. Otherwise, importing this module gets # the version installed with the Python used to call this module: idlelib_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, idlelib_dir) -import idlelib.PyShell -idlelib.PyShell.main() +from idlelib import PyShell +PyShell.main() Index: idle.pyw =================================================================== --- idle.pyw (revision 85696) +++ idle.pyw (working copy) @@ -3,7 +3,7 @@ except ImportError: # IDLE is not installed, but maybe PyShell is on sys.path: try: - from . import PyShell + import PyShell except ImportError: raise else: Index: IdleHistory.py =================================================================== --- IdleHistory.py (revision 85696) +++ IdleHistory.py (working copy) @@ -1,4 +1,4 @@ -from idlelib.configHandler import idleConf +from .configHandler import idleConf class History: Index: idlever.py =================================================================== --- idlever.py (revision 85696) +++ idlever.py (working copy) @@ -1 +1 @@ -IDLE_VERSION = "3.2a3" +IDLE_VERSION = "3.1.2" Index: IOBinding.py =================================================================== --- IOBinding.py (revision 85696) +++ IOBinding.py (working copy) @@ -1,15 +1,14 @@ import os -import types import sys import codecs import tempfile -import tkinter.filedialog as tkFileDialog -import tkinter.messagebox as tkMessageBox +import tkFileDialog +import tkMessageBox import re -from tkinter import * -from tkinter.simpledialog import askstring +from Tkinter import * +from SimpleDialog import SimpleDialog -from idlelib.configHandler import idleConf +from .configHandler import idleConf from codecs import BOM_UTF8 @@ -64,43 +63,74 @@ coding_re = re.compile("coding[:=]\s*([-\w_.]+)") -def coding_spec(data): +class EncodingMessage(SimpleDialog): + "Inform user that an encoding declaration is needed." + def __init__(self, master, enc): + self.should_edit = False + + self.root = top = Toplevel(master) + top.bind("", self.return_event) + top.bind("", self.do_ok) + top.protocol("WM_DELETE_WINDOW", self.wm_delete_window) + top.wm_title("I/O Warning") + top.wm_iconname("I/O Warning") + self.top = top + + l1 = Label(top, + text="Non-ASCII found, yet no encoding declared. Add a line like") + l1.pack(side=TOP, anchor=W) + l2 = Entry(top, font="courier") + l2.insert(0, "# -*- coding: %s -*-" % enc) + # For some reason, the text is not selectable anymore if the + # widget is disabled. + # l2['state'] = DISABLED + l2.pack(side=TOP, anchor = W, fill=X) + l3 = Label(top, text="to your file\n" + "Choose OK to save this file as %s\n" + "Edit your general options to silence this warning" % enc) + l3.pack(side=TOP, anchor = W) + + buttons = Frame(top) + buttons.pack(side=TOP, fill=X) + # Both return and cancel mean the same thing: do nothing + self.default = self.cancel = 0 + b1 = Button(buttons, text="Ok", default="active", + command=self.do_ok) + b1.pack(side=LEFT, fill=BOTH, expand=1) + b2 = Button(buttons, text="Edit my file", + command=self.do_edit) + b2.pack(side=LEFT, fill=BOTH, expand=1) + + self._set_transient(master) + + def do_ok(self): + self.done(0) + + def do_edit(self): + self.done(1) + +def coding_spec(str): """Return the encoding declaration according to PEP 263. - When checking encoded data, only the first two lines should be passed - in to avoid a UnicodeDecodeError if the rest of the data is not unicode. - The first two lines would contain the encoding specification. + Raise LookupError if the encoding is declared but unknown. + """ + # Only consider the first two lines + str = str.split("\n")[:2] + str = "\n".join(str) - Raise a LookupError if the encoding is declared but unknown. - """ - if isinstance(data, bytes): - # This encoding might be wrong. However, the coding - # spec must be ASCII-only, so any non-ASCII characters - # around here will be ignored. Decoding to Latin-1 should - # never fail (except for memory outage) - lines = data.decode('iso-8859-1') - else: - lines = data - # consider only the first two lines - if '\n' in lines: - lst = lines.split('\n')[:2] - elif '\r' in lines: - lst = lines.split('\r')[:2] - else: - lst = list(lines) - str = '\n'.join(lst) match = coding_re.search(str) if not match: return None name = match.group(1) + # Check whether the encoding is known + import codecs try: codecs.lookup(name) except LookupError: # The standard encoding error does not indicate the encoding - raise LookupError("Unknown encoding: "+name) + raise LookupError, "Unknown encoding "+name return name - class IOBinding: def __init__(self, editwin): @@ -142,14 +172,20 @@ self.filename_change_hook = hook filename = None + file_timestamp = None dirname = None def set_filename(self, filename): if filename and os.path.isdir(filename): self.filename = None + self.file_timestamp = None self.dirname = filename else: self.filename = filename + if filename is not None: + self.file_timestamp = os.stat(filename).st_mtime + else: + self.file_timestamp = None self.dirname = None self.set_saved(1) if self.filename_change_hook: @@ -202,107 +238,81 @@ def loadfile(self, filename): try: # open the file in binary mode so that we can handle - # end-of-line convention ourselves. + # end-of-line convention ourselves. f = open(filename,'rb') - two_lines = f.readline() + f.readline() - f.seek(0) - bytes = f.read() + chars = f.read() f.close() - except IOError as msg: + except IOError, msg: tkMessageBox.showerror("I/O Error", str(msg), master=self.text) return False - chars, converted = self._decode(two_lines, bytes) - if chars is None: - tkMessageBox.showerror("Decoding Error", - "File %s\nFailed to Decode" % filename, - parent=self.text) - return False + + chars = self.decode(chars) # We now convert all end-of-lines to '\n's firsteol = self.eol_re.search(chars) if firsteol: self.eol_convention = firsteol.group(0) + if isinstance(self.eol_convention, unicode): + # Make sure it is an ASCII string + self.eol_convention = self.eol_convention.encode("ascii") chars = self.eol_re.sub(r"\n", chars) + self.text.delete("1.0", "end") self.set_filename(None) self.text.insert("1.0", chars) self.reset_undo() self.set_filename(filename) - if converted: - # We need to save the conversion results first - # before being able to execute the code - self.set_saved(False) self.text.mark_set("insert", "1.0") - self.text.see("insert") +## The following has been commented out because on Windows +## it places the first line above the edit display, which +## is confusing. It apparently has no effect on Mac or Linux. +## self.text.see("insert") self.updaterecentfileslist(filename) return True - def _decode(self, two_lines, bytes): - "Create a Unicode string." - chars = None + def decode(self, chars): + """Create a Unicode string + + If that fails, let Tcl try its best + """ # Check presence of a UTF-8 signature first - if bytes.startswith(BOM_UTF8): + if chars.startswith(BOM_UTF8): try: - chars = bytes[3:].decode("utf-8") - except UnicodeDecodeError: + chars = chars[3:].decode("utf-8") + except UnicodeError: # has UTF-8 signature, but fails to decode... - return None, False + return chars else: # Indicates that this file originally had a BOM - self.fileencoding = 'BOM' - return chars, False + self.fileencoding = BOM_UTF8 + return chars # Next look for coding specification try: - enc = coding_spec(two_lines) - except LookupError as name: + enc = coding_spec(chars) + except LookupError, name: tkMessageBox.showerror( title="Error loading the file", message="The encoding '%s' is not known to this Python "\ "installation. The file may not display correctly" % name, master = self.text) enc = None - except UnicodeDecodeError: - return None, False if enc: try: - chars = str(bytes, enc) - self.fileencoding = enc - return chars, False - except UnicodeDecodeError: + return unicode(chars, enc) + except UnicodeError: pass - # Try ascii: + # If it is ASCII, we need not to record anything try: - chars = str(bytes, 'ascii') - self.fileencoding = None - return chars, False - except UnicodeDecodeError: + return unicode(chars, 'ascii') + except UnicodeError: pass - # Try utf-8: - try: - chars = str(bytes, 'utf-8') - self.fileencoding = 'utf-8' - return chars, False - except UnicodeDecodeError: - pass # Finally, try the locale's encoding. This is deprecated; # the user should declare a non-ASCII encoding try: - # Wait for the editor window to appear - self.editwin.text.update() - enc = askstring( - "Specify file encoding", - "The file's encoding is invalid for Python 3.x.\n" - "IDLE will convert it to UTF-8.\n" - "What is the current encoding of the file?", - initialvalue = locale_encoding, - parent = self.editwin.text) - - if enc: - chars = str(bytes, enc) - self.fileencoding = None - return chars, True - except (UnicodeDecodeError, LookupError): + chars = unicode(chars, encoding) + self.fileencoding = encoding + except UnicodeError: pass - return None, False # None on failure + return chars def maybesave(self): if self.get_saved(): @@ -327,7 +337,24 @@ if not self.filename: self.save_as(event) else: + # Check the time of most recent content modification so the + # user doesn't accidentally overwrite a newer version of the file. + if self.file_timestamp != os.stat(self.filename).st_mtime: + dlg = Tkinter.messagebox.Message( + master=self.text, + title="File has changed", + message=( + "The file has changed since reading it!\n\n" + "Do you really want to overwrite it?"), + default=Tkinter.messagebox.NO, + icon=Tkinter.messagebox.WARNING, + type=Tkinter.messagebox.YESNO) + res = dlg.show() + if not self.text.tk.getboolean(str(res)): + return + if self.writefile(self.filename): + self.file_timestamp = os.stat(self.filename).st_mtime self.set_saved(1) try: self.editwin.store_file_breaks() @@ -358,6 +385,15 @@ self.updaterecentfileslist(filename) return "break" + def save_as_temp(self, prefix='IDLE_tmp_'): + """Save the current text content to a temp file.""" + tfd, tempfilename = tempfile.mkstemp(prefix=prefix) + os.close(tfd) + if not self.writefile(tempfilename): + os.unlink(tempfilename) + return None + return tempfilename + def writefile(self, filename): self.fixlastline() text = self.text.get("1.0", "end-1c") @@ -376,43 +412,78 @@ return False def encode(self, chars): - if isinstance(chars, bytes): + if isinstance(chars, types.StringType): # This is either plain ASCII, or Tk was returning mixed-encoding # text to us. Don't try to guess further. return chars - # Preserve a BOM that might have been present on opening - if self.fileencoding == 'BOM': - return BOM_UTF8 + chars.encode("utf-8") # See whether there is anything non-ASCII in it. # If not, no need to figure out the encoding. try: return chars.encode('ascii') except UnicodeError: pass - # Check if there is an encoding declared + # If there is an encoding declared, try this first. try: - # a string, let coding_spec slice it to the first two lines enc = coding_spec(chars) failed = None - except LookupError as msg: + except LookupError, msg: failed = msg enc = None - else: - if not enc: - # PEP 3120: default source encoding is UTF-8 - enc = 'utf-8' if enc: try: return chars.encode(enc) except UnicodeError: failed = "Invalid encoding '%s'" % enc - tkMessageBox.showerror( - "I/O Error", - "%s.\nSaving as UTF-8" % failed, - master = self.text) - # Fallback: save as UTF-8, with BOM - ignoring the incorrect - # declared encoding - return BOM_UTF8 + chars.encode("utf-8") + if failed: + tkMessageBox.showerror( + "I/O Error", + "%s. Saving as UTF-8" % failed, + master = self.text) + # If there was a UTF-8 signature, use that. This should not fail + if self.fileencoding == BOM_UTF8 or failed: + return BOM_UTF8 + chars.encode("utf-8") + # Try the original file encoding next, if any + if self.fileencoding: + try: + return chars.encode(self.fileencoding) + except UnicodeError: + tkMessageBox.showerror( + "I/O Error", + "Cannot save this as '%s' anymore. Saving as UTF-8" \ + % self.fileencoding, + master = self.text) + return BOM_UTF8 + chars.encode("utf-8") + # Nothing was declared, and we had not determined an encoding + # on loading. Recommend an encoding line. + config_encoding = idleConf.GetOption("main","EditorWindow", + "encoding") + if config_encoding == 'utf-8': + # User has requested that we save files as UTF-8 + return BOM_UTF8 + chars.encode("utf-8") + ask_user = True + try: + chars = chars.encode(encoding) + enc = encoding + if config_encoding == 'locale': + ask_user = False + except UnicodeError: + chars = BOM_UTF8 + chars.encode("utf-8") + enc = "utf-8" + if not ask_user: + return chars + dialog = EncodingMessage(self.editwin.top, enc) + dialog.go() + if dialog.num == 1: + # User asked us to edit the file + encline = "# -*- coding: %s -*-\n" % enc + firstline = self.text.get("1.0", "2.0") + if firstline.startswith("#!"): + # Insert encoding after #! line + self.text.insert("2.0", encline) + else: + self.text.insert("1.0", encline) + return self.encode(self.text.get("1.0", "end-1c")) + return chars def fixlastline(self): c = self.text.get("end-2c") @@ -437,11 +508,8 @@ filename = self.filename # shell undo is reset after every prompt, looks saved, probably isn't if not saved or filename is None: - (tfd, tempfilename) = tempfile.mkstemp(prefix='IDLE_tmp_') - filename = tempfilename - os.close(tfd) - if not self.writefile(tempfilename): - os.unlink(tempfilename) + tempfilename = self.save_as_temp() + if tempfilename is None: return "break" platform=os.name printPlatform=1 @@ -481,6 +549,8 @@ ("All files", "*"), ] + defaultextension = '.py' if sys.platform[:3] == 'win' else '' + def askopenfile(self): dir, base = self.defaultfilename("open") if not self.opendialog: @@ -504,8 +574,10 @@ def asksavefile(self): dir, base = self.defaultfilename("save") if not self.savedialog: - self.savedialog = tkFileDialog.SaveAs(master=self.text, - filetypes=self.filetypes) + self.savedialog = tkFileDialog.SaveAs( + master=self.text, + filetypes=self.filetypes, + defaultextension=self.defaultextension) filename = self.savedialog.show(initialdir=dir, initialfile=base) return filename Index: keybindingDialog.py =================================================================== --- keybindingDialog.py (revision 85696) +++ keybindingDialog.py (working copy) @@ -1,10 +1,10 @@ """ Dialog for building Tkinter accelerator key bindings """ -from tkinter import * -import tkinter.messagebox as tkMessageBox +from Tkinter import * +import tkMessageBox import string -from idlelib import macosxSupport +from . import macosxSupport class GetKeysDialog(Toplevel): def __init__(self,parent,title,action,currentKeySequences): Index: macosxSupport.py =================================================================== --- macosxSupport.py (revision 85696) +++ macosxSupport.py (working copy) @@ -3,21 +3,15 @@ GUI application (as opposed to an X11 application). """ import sys -import tkinter +import Tkinter - -_appbundle = None - def runningAsOSXApp(): """ Returns True if Python is running from within an app on OSX. If so, assume that Python was built with Aqua Tcl/Tk rather than X11 Tcl/Tk. """ - global _appbundle - if _appbundle is None: - _appbundle = (sys.platform == 'darwin' and '.app' in sys.executable) - return _appbundle + return (sys.platform == 'darwin' and '.app' in sys.executable) def addOpenEventSupport(root, flist): """ @@ -26,7 +20,7 @@ """ def doOpenFile(*args): for fn in args: - flist.open(fn) + flist.open(str(fn)) # The command below is a hook in aquatk that is called whenever the app # receives a file open event. The callback can have multiple arguments, @@ -36,7 +30,7 @@ def hideTkConsole(root): try: root.tk.call('console', 'hide') - except tkinter.TclError: + except Tkinter.TclError: # Some versions of the Tk framework don't have a console object pass @@ -56,11 +50,11 @@ # # Due to a (mis-)feature of TkAqua the user will also see an empty Help # menu. - from tkinter import Menu, Text, Text - from idlelib.EditorWindow import prepstr, get_accelerator - from idlelib import Bindings - from idlelib import WindowList - from idlelib.MultiCall import MultiCallCreator + from Tkinter import Menu, Text, Text + from .EditorWindow import prepstr, get_accelerator + from . import Bindings + from . import WindowList + from .MultiCall import MultiCallCreator menubar = Menu(root) root.configure(menu=menubar) @@ -83,11 +77,11 @@ menubar.add_cascade(label='IDLE', menu=menu) def about_dialog(event=None): - from idlelib import aboutDialog + from . import aboutDialog aboutDialog.AboutDialog(root, 'About IDLE') def config_dialog(event=None): - from idlelib import configDialog + from . import configDialog # Ensure that the root object has an instance_dict attribute, # mirrors code in EditorWindow (although that sets the attribute Index: MultiCall.py =================================================================== --- MultiCall.py (revision 85696) +++ MultiCall.py (working copy) @@ -31,8 +31,8 @@ import sys import re -import tkinter -from idlelib import macosxSupport +import Tkinter +from . import macosxSupport # the event type constants, which define the meaning of mc_type MC_KEYPRESS=0; MC_KEYRELEASE=1; MC_BUTTONPRESS=2; MC_BUTTONRELEASE=3; @@ -305,7 +305,7 @@ return _multicall_dict[widget] class MultiCall (widget): - assert issubclass(widget, tkinter.Misc) + assert issubclass(widget, Tkinter.Misc) def __init__(self, *args, **kwargs): widget.__init__(self, *args, **kwargs) @@ -397,8 +397,8 @@ if __name__ == "__main__": # Test - root = tkinter.Tk() - text = MultiCallCreator(tkinter.Text)(root) + root = Tkinter.Tk() + text = MultiCallCreator(Tkinter.Text)(root) text.pack() def bindseq(seq, n=[0]): def handler(event): Index: MultiStatusBar.py =================================================================== --- MultiStatusBar.py (revision 85696) +++ MultiStatusBar.py (working copy) @@ -1,4 +1,4 @@ -from tkinter import * +from Tkinter import * class MultiStatusBar(Frame): Index: NEWS.txt =================================================================== --- NEWS.txt (revision 85696) +++ NEWS.txt (working copy) @@ -1,3 +1,48 @@ +What's New in IDLE 2.7 post-release of Python 2.7? + +======================= + +*Release date: xx-xx-xx* + +merged VIDLE into standard IDLE 2.7 + +What is VIDLE? +========================= + +In December 2008 David Scherer created an alternative version of IDLE to fix +some long-standing problems. In the 2009 Google Summer of Code Guilherme Polo +continued this work, assisted by Bruce Sherwood. Important new features +include a configuration preference that permits writing and running test +programs from the editor without having to save the file, and bringing the +shell window forward in case of an error (because novices often failed to +realize why their program had stopped). + +Polo submitted patches at the end of the summer of 2009, found here: +http://code.google.com/p/google-summer-of-code-2009-python/downloads/list +This was for the Python 2.X series. + +Because the mechanism was not clear for getting these patches into the +version of IDLE distributed with Python, the patched version has been +distributed with VPython (vpython.org) with the name VIDLE to attempt to +avoid confusion with IDLE. It is installed into site-packages. + +In September 2010 Bruce Sherwood manually applied Polo's patches to the +version of IDLE distributed with Python 3.1.2, using relative addressing +of modules (e.g. from . import PyShell). Except for having to change one +absolute address in the use of __import__ in PyShell.py, and the module +names idlelib or vidle in idle.py and idle.pyw, this version can work either +in Python31/Lib/idlelib or in Python31/Lib/site-packages/vidle. + +This Python 3 VIDLE is available at vpython.org and will be included in the +Windows and Mac installers for VPython for Python 3. + +At Guido's request, Bruce Sherwood carried out the same update to the IDLE +distributed with Python 2.7. Guido was concerned that with significant +problems reported for IDLE 2.7, there should be an update despite Python 2.7 +itself being essentially frozen. The basic structure for IDLE 3.2 was +carried over. The main changes that had to be made were the change in module +names between Python 2.7 and Python 3.2 (e.g. Tkinter in 2.7 is tkinter in 3.2). + What's New in IDLE 3.1? ========================= Index: ObjectBrowser.py =================================================================== --- ObjectBrowser.py (revision 85696) +++ ObjectBrowser.py (working copy) @@ -9,9 +9,9 @@ # XXX TO DO: # - for classes/modules, add "open source" to object browser -from idlelib.TreeWidget import TreeItem, TreeNode, ScrolledCanvas +from .TreeWidget import TreeItem, TreeNode, ScrolledCanvas -from reprlib import Repr +from repr import Repr myrepr = Repr() myrepr.maxstring = 100 @@ -123,7 +123,7 @@ def _test(): import sys - from tkinter import Tk + from Tkinter import Tk root = Tk() root.configure(bd=0, bg="yellow") root.focus_set() Index: OutputWindow.py =================================================================== --- OutputWindow.py (revision 85696) +++ OutputWindow.py (working copy) @@ -1,8 +1,8 @@ -from tkinter import * -from idlelib.EditorWindow import EditorWindow +from Tkinter import * +from .EditorWindow import EditorWindow import re -import tkinter.messagebox as tkMessageBox -from idlelib import IOBinding +import tkMessageBox +from . import IOBinding class OutputWindow(EditorWindow): @@ -51,7 +51,11 @@ # Our own right-button menu rmenu_specs = [ - ("Go to file/line", "<>"), + ("Cut", "<>", "rmenu_check_cut"), + ("Copy", "<>", "rmenu_check_copy"), + ("Paste", "<>", "rmenu_check_paste"), + (None, None, None), + ("Go to file/line", "<>", None) ] file_line_pats = [ Index: ParenMatch.py =================================================================== --- ParenMatch.py (revision 85696) +++ ParenMatch.py (working copy) @@ -5,8 +5,8 @@ parentheses, square brackets, and curly braces. """ -from idlelib.HyperParser import HyperParser -from idlelib.configHandler import idleConf +from .HyperParser import HyperParser +from .configHandler import idleConf _openers = {')':'(',']':'[','}':'{'} CHECK_DELAY = 100 # miliseconds Index: PathBrowser.py =================================================================== --- PathBrowser.py (revision 85696) +++ PathBrowser.py (working copy) @@ -2,8 +2,8 @@ import sys import imp -from idlelib.TreeWidget import TreeItem -from idlelib.ClassBrowser import ClassBrowser, ModuleBrowserTreeItem +from .TreeWidget import TreeItem +from .ClassBrowser import ClassBrowser, ModuleBrowserTreeItem class PathBrowser(ClassBrowser): @@ -86,7 +86,7 @@ return sorted def main(): - from idlelib import PyShell + from . import PyShell PathBrowser(PyShell.flist) if sys.stdin is sys.__stdin__: mainloop() Index: Percolator.py =================================================================== --- Percolator.py (revision 85696) +++ Percolator.py (working copy) @@ -1,5 +1,5 @@ -from idlelib.WidgetRedirector import WidgetRedirector -from idlelib.Delegator import Delegator +from .WidgetRedirector import WidgetRedirector +from .Delegator import Delegator class Percolator: @@ -52,7 +52,7 @@ filter.setdelegate(None) def main(): - import tkinter as Tk + import Tkinter as Tk class Tracer(Delegator): def __init__(self, name): self.name = name Index: PyShell.py =================================================================== --- PyShell.py (revision 85696) +++ PyShell.py (working copy) @@ -1,4 +1,4 @@ -#! /usr/bin/env python3 +#! /usr/bin/env python import os import os.path @@ -9,39 +9,44 @@ import time import threading import traceback +import subprocess import types import linecache from code import InteractiveInterpreter +################################ +#Work around compatibility issues between Python 2 and Python 3 + +def print_to_file(text, outputfile): + print >> outputfile, text + +################################ + try: - from tkinter import * + from Tkinter import * except ImportError: - print("** IDLE can't import Tkinter. " \ - "Your Python may not be configured for Tk. **", file=sys.__stderr__) + print_to_file("** IDLE can't import Tkinter. " \ + "Your Python may not be configured for Tk. **", sys.__stderr__) sys.exit(1) -import tkinter.messagebox as tkMessageBox +import tkMessageBox -from idlelib.EditorWindow import EditorWindow, fixwordbreaks -from idlelib.FileList import FileList -from idlelib.ColorDelegator import ColorDelegator -from idlelib.UndoDelegator import UndoDelegator -from idlelib.OutputWindow import OutputWindow -from idlelib.configHandler import idleConf -from idlelib import idlever -from idlelib import rpc -from idlelib import Debugger -from idlelib import RemoteDebugger -from idlelib import macosxSupport +from .EditorWindow import EditorWindow, fixwordbreaks +from .FileList import FileList +from .ColorDelegator import ColorDelegator +from .UndoDelegator import UndoDelegator +from .OutputWindow import OutputWindow +from .configHandler import idleConf +from .utils import tb_print_list +from . import idlever +from . import rpc +from . import Debugger +from . import RemoteDebugger +from . import macosxSupport HOST = '127.0.0.1' # python execution server on localhost loopback PORT = 0 # someday pass in host, port for remote debug capability -try: - from signal import SIGTERM -except ImportError: - SIGTERM = 15 - # Override warnings module to write to warning_stream. Initialize to send IDLE # internal warnings to the console. ScriptBinding.check_syntax() will # temporarily redirect the stream to the shell window to display warnings when @@ -86,6 +91,7 @@ orig_checkcache is bound at definition time to the original method, allowing it to be patched. + """ cache = linecache.cache save = {} @@ -119,8 +125,14 @@ old_hook() self.io.set_filename_change_hook(filename_changed_hook) - rmenu_specs = [("Set Breakpoint", "<>"), - ("Clear Breakpoint", "<>")] + rmenu_specs = [ + ("Cut", "<>", "rmenu_check_cut"), + ("Copy", "<>", "rmenu_check_copy"), + ("Paste", "<>", "rmenu_check_paste"), + (None, None, None), + ("Set Breakpoint", "<>", None), + ("Clear Breakpoint", "<>", None) + ] def set_breakpoint(self, lineno): text = self.text @@ -239,10 +251,12 @@ self.breakpoints = linenumber_list def ranges_to_linenumbers(self, ranges): + """Convert a tuple of ranges returned by Text.tag_ranges to + line numbers.""" lines = [] for index in range(0, len(ranges), 2): - lineno = int(float(ranges[index])) - end = int(float(ranges[index+1])) + lineno = int(float(str(ranges[index]))) + end = int(float(str(ranges[index+1]))) while lineno < end: lines.append(lineno) lineno += 1 @@ -344,13 +358,13 @@ self.port = PORT rpcclt = None - rpcpid = None + rpcproc = None def spawn_subprocess(self): if self.subprocess_arglist is None: self.subprocess_arglist = self.build_subprocess_arglist() args = self.subprocess_arglist - self.rpcpid = os.spawnv(os.P_NOWAIT, sys.executable, args) + self.rpcproc = subprocess.Popen([sys.executable] + args[1:]) def build_subprocess_arglist(self): assert (self.port!=0), ( @@ -414,6 +428,10 @@ self.poll_subprocess() return self.rpcclt + def get_restart_line(self): + halfbar = ((int(self.tkconsole.width) - 16) // 2) * '=' + return halfbar + ' RESTART ' + halfbar + def restart_subprocess(self): if self.restarting: return self.rpcclt @@ -428,7 +446,7 @@ pass # Kill subprocess, spawn a new one, accept connection. self.rpcclt.close() - self.unix_terminate() + self.terminate_subprocess() console = self.tkconsole was_executing = console.executing console.executing = False @@ -444,10 +462,8 @@ if was_executing: console.write('\n') console.showprompt() - halfbar = ((int(console.width) - 16) // 2) * '=' - console.write(halfbar + ' RESTART ' + halfbar) - console.text.mark_set("restart", "end-1c") - console.text.mark_gravity("restart", "left") + console.write(self.get_restart_line()) + console.text.tag_add("restart", "end - 1 line") console.showprompt() # restart subprocess debugger if debug: @@ -469,23 +485,20 @@ self.rpcclt.close() except AttributeError: # no socket pass - self.unix_terminate() + self.terminate_subprocess() self.tkconsole.executing = False self.rpcclt = None - def unix_terminate(self): - "UNIX: make sure subprocess is terminated and collect status" - if hasattr(os, 'kill'): + def terminate_subprocess(self): + "Make sure subprocess is terminated and collect status." + if sys.platform[:3] == 'win': try: - os.kill(self.rpcpid, SIGTERM) - except OSError: - # process already terminated: - return - else: - try: - os.waitpid(self.rpcpid, 0) - except OSError: - return + self.rpcproc.kill() + except WindowsError: + pass + else: + self.rpcproc.kill() + self.rpcproc.wait() def transfer_path(self): self.runcommand("""if 1: @@ -516,14 +529,16 @@ console = self.tkconsole.console if how == "OK": if what is not None: - print(repr(what), file=console) + print_to_file(repr(what), console) elif how == "EXCEPTION": if self.tkconsole.getvar("<>"): self.remote_stack_viewer() elif how == "ERROR": errmsg = "PyShell.ModifiedInterpreter: Subprocess ERROR:\n" - print(errmsg, what, file=sys.__stderr__) - print(errmsg, what, file=console) + print_to_file(errmsg, sys.__stderr__) + print_to_file(what, sys.__stderr__) + print_to_file(errmsg, console) + print_to_file(what, file=console) # we received a response to the currently active seq number: try: self.tkconsole.endexecuting() @@ -548,7 +563,7 @@ This method is called from the subprocess, and by returning from this method we allow the subprocess to unblock. After a bit the shell requests the subprocess to open the remote stack viewer which returns a - static object looking at the last exception. It is queried through + static object looking at the last exceptiopn. It is queried through the RPC mechanism. """ @@ -556,13 +571,13 @@ return def remote_stack_viewer(self): - from idlelib import RemoteObjectBrowser + from . import RemoteObjectBrowser oid = self.rpcclt.remotequeue("exec", "stackviewer", ("flist",), {}) if oid is None: self.tkconsole.root.bell() return item = RemoteObjectBrowser.StubObjectTreeItem(self.rpcclt, oid) - from idlelib.TreeWidget import ScrolledCanvas, TreeNode + from .TreeWidget import ScrolledCanvas, TreeNode top = Toplevel(self.tkconsole.root) theme = idleConf.GetOption('main','Theme','name') background = idleConf.GetHighlight(theme, 'normal')['background'] @@ -588,8 +603,8 @@ except (OverflowError, SyntaxError): self.tkconsole.resetoutput() tkerr = self.tkconsole.stderr - print('*** Error in script or command!\n', file=tkerr) - print('Traceback (most recent call last):', file=tkerr) + print_to_file('*** Error in script or command!\n', tkerr) + print_to_file('Traceback (most recent call last):', tkerr) InteractiveInterpreter.showsyntaxerror(self, filename) self.tkconsole.showprompt() else: @@ -604,7 +619,7 @@ # at the moment, InteractiveInterpreter expects str assert isinstance(source, str) #if isinstance(source, str): - # from idlelib import IOBinding + # from . import IOBinding # try: # source = source.encode(IOBinding.encoding) # except UnicodeError: @@ -666,11 +681,33 @@ self.write("SyntaxError: %s\n" % msg) tkconsole.showprompt() - def showtraceback(self): - "Extend base class method to reset output properly" + def showtraceback(self, temp_filename=None): + """Extend base class method to reset output properly and print an + customized traceback.""" self.tkconsole.resetoutput() self.checklinecache() - InteractiveInterpreter.showtraceback(self) + + typ, value, tb = sys.exc_info() + sys.last_type = typ + sys.last_value = value + sys.last_traceback = tb + tblist = traceback.extract_tb(tb) + del tblist[:1] + sys.stderr.write('\nTraceback (most recent call last):\n') + if temp_filename is not None: + # Replace the name of the temporary file by 'Untitled' + main_fname = 'Untitled' + new_tb = [] + for t in tblist: + fname = main_fname if t[0] == temp_filename else t[0] + new_tb.append((fname, ) + t[1:]) + tblist = new_tb + else: + main_fname = tblist[0][0] + tb_print_list(tblist, main_fname, sys.stdout, sys.stderr) + lines = traceback.format_exception_only(typ, value) + list(map(sys.stderr.write, lines)) + if self.tkconsole.getvar("<>"): self.tkconsole.open_stack_viewer() @@ -692,7 +729,7 @@ exec(code, self.locals) return 1 - def runcode(self, code): + def runcode(self, code, tempname=None): "Override base class method" if self.tkconsole.executing: self.interp.restart_subprocess() @@ -705,7 +742,7 @@ self.tkconsole.beginexecuting() if not debugger and self.rpcclt is not None: self.active_seq = self.rpcclt.asyncqueue("exec", "runcode", - (code,), {}) + (code, tempname), {}) elif debugger: debugger.run(code, self.locals) else: @@ -724,16 +761,16 @@ raise except: if use_subprocess: - print("IDLE internal error in runcode()", - file=self.tkconsole.stderr) + print_to_file("IDLE internal error in runcode()", + self.tkconsole.stderr) self.showtraceback() self.tkconsole.endexecuting() else: if self.tkconsole.canceled: self.tkconsole.canceled = False - print("KeyboardInterrupt", file=self.tkconsole.stderr) + print_to_file("KeyboardInterrupt", self.tkconsole.stderr) else: - self.showtraceback() + self.showtraceback(tempname) finally: if not use_subprocess: try: @@ -796,7 +833,7 @@ # New classes - from idlelib.IdleHistory import History + from .IdleHistory import History def __init__(self, flist=None): if use_subprocess: @@ -820,6 +857,7 @@ # text = self.text text.configure(wrap="char") + text.bind("<>", self.paste_callback) text.bind("<>", self.enter_callback) text.bind("<>", self.linefeed_callback) text.bind("<>", self.cancel_callback) @@ -836,9 +874,9 @@ self.save_stdout = sys.stdout self.save_stderr = sys.stderr self.save_stdin = sys.stdin - from idlelib import IOBinding + from . import IOBinding self.stdout = PseudoFile(self, "stdout", IOBinding.encoding) - self.stderr = PseudoFile(self, "stderr", IOBinding.encoding) + self.stderr = PseudoStderrFile(self, encoding=IOBinding.encoding) self.console = PseudoFile(self, "console", IOBinding.encoding) if not use_subprocess: sys.stdout = self.stdout @@ -856,7 +894,12 @@ self.history = self.History(self.text) # self.pollinterval = 50 # millisec + # Cleanup functions to be called when endexecuting is called + self._cleanup_funcs = [] + def append_cleanup_func(self, func, *args, **kwargs): + self._cleanup_funcs.append((func, args, kwargs)) + def get_standard_extension_names(self): return idleConf.GetExtensions(shell_only=True) @@ -930,6 +973,10 @@ self.canceled = 0 self.showprompt() + for func, args, kwargs in self._cleanup_funcs: + func(*args, **kwargs) + self._cleanup_funcs = [] + def close(self): "Extend EditorWindow.close()" if self.executing: @@ -990,8 +1037,8 @@ self.write("Python %s on %s\n%s\n%s" % (sys.version, sys.platform, self.COPYRIGHT, nosub)) self.showprompt() - import tkinter - tkinter._default_root = None # 03Jan04 KBK What's this? + import Tkinter + Tkinter._default_root = None # 03Jan04 KBK What's this? return True def readline(self): @@ -1016,7 +1063,40 @@ def isatty(self): return True + + def paste_callback(self, event): + text = self.text + tk = text.tk + try: + selection = tk.call('::tk::GetSelection', text, 'CLIPBOARD') + except TclError: + # No selection, probably. + return "break" + oldseparator = text.cget('autoseparators') + if oldseparator: + text.configure(autoseparators=0) + text.edit_separator() + if tk.call('tk', 'windowingsystem') != 'x11': + try: + text.delete('sel.first', 'sel.last') + except TclError: + # Nothing tagged with 'sel', probably. + pass + selection = selection.split('\n')[::-1] + while selection: + line = selection.pop() + if line == '': + text.event_generate('<>') + else: + text.insert('insert', line) + + if oldseparator: + text.edit_separator() + text.configure(autoseparators=1) + + return "break" + def cancel_callback(self, event=None): try: if self.text.compare("sel.first", "!=", "sel.last"): @@ -1177,14 +1257,28 @@ "(sys.last_traceback is not defined)", master=self.text) return - from idlelib.StackViewer import StackBrowser + from .StackViewer import StackBrowser sv = StackBrowser(self.root, self.flist) def view_restart_mark(self, event=None): - self.text.see("iomark") - self.text.see("restart") + text = self.text + text.see("iomark") + ranges = text.tag_ranges("restart") + if not ranges: + return + restart_line = self.interp.get_restart_line() + for indx in range(len(ranges), 0, -2): + lineno = '%s.0' % str(ranges[indx - 1]).split('.')[0] + start, end = ('%s +1 line' % lineno, '%s +2 lines +1c' % lineno) + content = text.get(start, end)[4:].rstrip() + if content and content[:-2] != restart_line: + break + + text.see(lineno) + def restart_shell(self, event=None): + self.stderr.signaled = False self.interp.restart_subprocess() def showprompt(self): @@ -1240,7 +1334,20 @@ def isatty(self): return True +class PseudoStderrFile(PseudoFile): + def __init__(self, shell, tags="stderr", encoding=None): + PseudoFile.__init__(self, shell, tags, encoding) + self.signaled = False + def write(self, s): + if not self.signaled: + signal_err = idleConf.GetOption('main', 'General', + 'signal-first-error', default=1, type='bool') + if signal_err: + self.shell.top.wakeup(anystate=True) + self.signaled = True + PseudoFile.write(self, s) + usage_msg = """\ USAGE: idle [-deins] [-t title] [file]* @@ -1298,7 +1405,7 @@ global flist, root, use_subprocess use_subprocess = True - enable_shell = True + enable_shell = False enable_edit = False debug = False cmd = None @@ -1318,8 +1425,7 @@ debug = True enable_shell = True if o == '-e': - enable_edit = True - enable_shell = False + enable_edit = False if o == '-h': sys.stdout.write(usage_msg) sys.exit() @@ -1332,7 +1438,7 @@ if os.path.isfile(script): pass else: - print("No script file: ", script) + ("No script file: ", script) sys.exit() enable_shell = True if o == '-s': @@ -1370,6 +1476,7 @@ edit_start = idleConf.GetOption('main', 'General', 'editor-on-startup', type='bool') enable_edit = enable_edit or edit_start + enable_shell = enable_shell or not edit_start # start editor and/or shell windows: root = Tk(className="Idle") @@ -1380,8 +1487,10 @@ if enable_edit: if not (cmd or script): - for filename in args: - flist.open(filename) + for filename in args[:]: + if flist.open(filename) is None: + # filename is a directory actually, disconsider it + args.remove(filename) if not args: flist.new() if enable_shell: Index: RemoteDebugger.py =================================================================== --- RemoteDebugger.py (revision 85696) +++ RemoteDebugger.py (working copy) @@ -21,8 +21,8 @@ """ import types -from idlelib import rpc -from idlelib import Debugger +from . import rpc +from . import Debugger debugging = 0 Index: RemoteObjectBrowser.py =================================================================== --- RemoteObjectBrowser.py (revision 85696) +++ RemoteObjectBrowser.py (working copy) @@ -1,4 +1,4 @@ -from idlelib import rpc +from . import rpc def remote_object_tree_item(item): wrapper = WrappedObjectTreeItem(item) Index: ReplaceDialog.py =================================================================== --- ReplaceDialog.py (revision 85696) +++ ReplaceDialog.py (working copy) @@ -1,7 +1,7 @@ -from tkinter import * +from Tkinter import * -from idlelib import SearchEngine -from idlelib.SearchDialogBase import SearchDialogBase +from . import SearchEngine +from .SearchDialogBase import SearchDialogBase def replace(text): root = text._root() Index: rpc.py =================================================================== --- rpc.py (revision 85696) +++ rpc.py (working copy) @@ -5,7 +5,7 @@ has only one client per server, this was not a limitation. +---------------------------------+ +-------------+ - | socketserver.BaseRequestHandler | | SocketIO | + | SocketServer.BaseRequestHandler | | SocketIO | +---------------------------------+ +-------------+ ^ | register() | | | unregister()| @@ -31,17 +31,24 @@ import os import socket import select -import socketserver +import SocketServer import struct import pickle import threading -import queue +import Queue import traceback -import copyreg +import copy_reg import types import marshal +################################ +#Work around compatibility issues between Python 2 and Python 3 +def print_to_file(text, outputfile): + print >> outputfile, text + +################################ + def unpickle_code(ms): co = marshal.loads(ms) assert isinstance(co, types.CodeType) @@ -60,18 +67,18 @@ # assert isinstance(fn, type.FunctionType) # return repr(fn) -copyreg.pickle(types.CodeType, pickle_code, unpickle_code) +copy_reg.pickle(types.CodeType, pickle_code, unpickle_code) # copyreg.pickle(types.FunctionType, pickle_function, unpickle_function) BUFSIZE = 8*1024 LOCALHOST = '127.0.0.1' -class RPCServer(socketserver.TCPServer): +class RPCServer(SocketServer.TCPServer): def __init__(self, addr, handlerclass=None): if handlerclass is None: handlerclass = RPCHandler - socketserver.TCPServer.__init__(self, addr, handlerclass) + SocketServer.TCPServer.__init__(self, addr, handlerclass) def server_bind(self): "Override TCPServer method, no bind() phase for connecting entity" @@ -104,21 +111,21 @@ raise except: erf = sys.__stderr__ - print('\n' + '-'*40, file=erf) - print('Unhandled server exception!', file=erf) - print('Thread: %s' % threading.current_thread().name, file=erf) - print('Client Address: ', client_address, file=erf) - print('Request: ', repr(request), file=erf) + print_to_file('\n' + '-'*40, erf) + print_to_file('Unhandled server exception!', erf) + print_to_file('Thread: %s' % threading.current_thread().name, erf) + print_to_file('Client Address: ', client_address, erf) + print_to_file('Request: ', repr(request), erf) traceback.print_exc(file=erf) - print('\n*** Unrecoverable, server exiting!', file=erf) - print('-'*40, file=erf) + print_to_file('\n*** Unrecoverable, server exiting!', erf) + print_to_file('-'*40, erf) os._exit(0) #----------------- end class RPCServer -------------------- objecttable = {} -request_queue = queue.Queue(0) -response_queue = queue.Queue(0) +request_queue = Queue.Queue(0) +response_queue = Queue.Queue(0) class SocketIO(object): @@ -144,7 +151,7 @@ def exithook(self): "override for specific exit action" - os._exit() + os._exit(0) def debug(self, *args): if not self.debugging: @@ -152,7 +159,7 @@ s = self.location + " " + str(threading.current_thread().name) for a in args: s = s + " " + str(a) - print(s, file=sys.__stderr__) + print_to_file(s, sys.__stderr__) def register(self, oid, object): self.objtable[oid] = object @@ -201,7 +208,7 @@ except: msg = "*** Internal Error: rpc.py:SocketIO.localcall()\n\n"\ " Object: %s \n Method: %s \n Args: %s\n" - print(msg % (oid, method, args), file=sys.__stderr__) + print_to_file(msg % (oid, method, args), sys.__stderr__) traceback.print_exc(file=sys.__stderr__) return ("EXCEPTION", None) @@ -323,7 +330,7 @@ try: s = pickle.dumps(message) except pickle.PicklingError: - print("Cannot pickle:", repr(message), file=sys.__stderr__) + print_to_file("Cannot pickle:", repr(message), sys.__stderr__) raise s = struct.pack(" 0: @@ -379,10 +386,10 @@ try: message = pickle.loads(packet) except pickle.UnpicklingError: - print("-----------------------", file=sys.__stderr__) - print("cannot unpickle packet:", repr(packet), file=sys.__stderr__) + print_to_file("-----------------------", sys.__stderr__) + print_to_file("cannot unpickle packet:", repr(packet), sys.__stderr__) traceback.print_stack(file=sys.__stderr__) - print("-----------------------", file=sys.__stderr__) + print_to_file("-----------------------", sys.__stderr__) raise return message @@ -413,7 +420,7 @@ # send queued response if there is one available try: qmsg = response_queue.get(0) - except queue.Empty: + except Queue.Empty: pass else: seq, response = qmsg @@ -492,7 +499,7 @@ def __init__(self, oid): self.oid = oid -class RPCHandler(socketserver.BaseRequestHandler, SocketIO): +class RPCHandler(SocketServer.BaseRequestHandler, SocketIO): debugging = False location = "#S" # Server @@ -500,7 +507,7 @@ def __init__(self, sock, addr, svr): svr.current_handler = self ## cgt xxx SocketIO.__init__(self, sock) - socketserver.BaseRequestHandler.__init__(self, sock, addr, svr) + SocketServer.BaseRequestHandler.__init__(self, sock, addr, svr) def handle(self): "handle() method required by socketserver" @@ -524,11 +531,11 @@ def accept(self): working_sock, address = self.listening_sock.accept() if self.debugging: - print("****** Connection request from ", address, file=sys.__stderr__) + print_to_file("****** Connection request from ", address, sys.__stderr__) if address[0] == LOCALHOST: SocketIO.__init__(self, working_sock) else: - print("** Invalid host: ", address, file=sys.__stderr__) + print_to_file("** Invalid host: ", address, sys.__stderr__) raise socket.error def get_remote_proxy(self, oid): Index: run.py =================================================================== --- run.py (revision 85696) +++ run.py (working copy) @@ -3,22 +3,31 @@ import time import socket import traceback -import _thread as thread +import thread import threading -import queue +import Queue -from idlelib import CallTips -from idlelib import AutoComplete +from . import CallTips +from . import AutoComplete +from .utils import tb_print_list -from idlelib import RemoteDebugger -from idlelib import RemoteObjectBrowser -from idlelib import StackViewer -from idlelib import rpc +from . import RemoteDebugger +from . import RemoteObjectBrowser +from . import StackViewer +from . import rpc import __main__ LOCALHOST = '127.0.0.1' +################################ +#Work around compatibility issues between Python 2 and Python 3 + +def print_to_file(text, outputfile): + print >> outputfile, text + +################################ + try: import warnings except ImportError: @@ -73,8 +82,7 @@ assert(len(sys.argv) > 1) port = int(sys.argv[-1]) except: - print("IDLE Subprocess: no IP port passed in sys.argv.", - file=sys.__stderr__) + print_to_file("IDLE Subprocess: no IP port passed in sys.argv.", sys.__stderr__) return sys.argv[:] = [""] sockthread = threading.Thread(target=manage_socket, @@ -92,7 +100,7 @@ continue try: seq, request = rpc.request_queue.get(block=True, timeout=0.05) - except queue.Empty: + except Queue.Empty: continue method, args, kwargs = request ret = method(*args, **kwargs) @@ -122,12 +130,12 @@ server = MyRPCServer(address, MyHandler) break except socket.error as err: - print("IDLE Subprocess: socket error: " + err.args[1] + - ", retrying....", file=sys.__stderr__) + print_to_file("IDLE Subprocess: socket error: " + err.args[1] + + ", retrying....", sys.__stderr__) socket_error = err else: - print("IDLE Subprocess: Connection to " - "IDLE GUI failed, exiting.", file=sys.__stderr__) + print_to_file("IDLE Subprocess: Connection to " + "IDLE GUI failed, exiting.", sys.__stderr__) show_socket_error(socket_error, address) global exit_now exit_now = True @@ -135,9 +143,9 @@ server.handle_request() # A single request only def show_socket_error(err, address): - import tkinter - import tkinter.messagebox as tkMessageBox - root = tkinter.Tk() + import Tkinter + import Tkinter.messagebox as tkMessageBox + root = Tkinter.Tk() root.withdraw() if err.args[0] == 61: # connection refused msg = "IDLE's subprocess can't connect to %s:%d. This may be due "\ @@ -150,7 +158,7 @@ "Socket Error: %s" % err.args[1]) root.destroy() -def print_exception(): +def print_exception(temp_filename=None): import linecache linecache.checkcache() flush_stdout() @@ -158,14 +166,24 @@ typ, val, tb = excinfo = sys.exc_info() sys.last_type, sys.last_value, sys.last_traceback = excinfo tbe = traceback.extract_tb(tb) - print('Traceback (most recent call last):', file=efile) + print_to_file('Traceback (most recent call last):', efile) exclude = ("run.py", "rpc.py", "threading.py", "queue.py", "RemoteDebugger.py", "bdb.py") cleanup_traceback(tbe, exclude) - traceback.print_list(tbe, file=efile) + if temp_filename is not None: + # Replace the name of the temporary file by 'Untitled' + main_fname = 'Untitled' + new_tbe = [] + for t in tbe: + fname = main_fname if t[0] == temp_filename else t[0] + new_tbe.append((fname, ) + t[1:]) + tbe = new_tbe + else: + main_fname = tbe[0][0] + tb_print_list(tbe, main_fname, sys.stdout, efile) lines = traceback.format_exception_only(typ, val) for line in lines: - print(line, end='', file=efile) + print_to_file(line+'', efile) def cleanup_traceback(tb, exclude): "Remove excluded traces from beginning/end of tb; get cached lines" @@ -187,7 +205,7 @@ if len(tb) == 0: # exception was in IDLE internals, don't prune! tb[:] = orig_tb[:] - print("** IDLE Internal Exception: ", file=sys.stderr) + print_to_file("** IDLE Internal Exception: ", sys.stderr) rpchandler = rpc.objecttable['exec'].rpchandler for i in range(len(tb)): fn, ln, nm, line = tb[i] @@ -233,14 +251,14 @@ thread.interrupt_main() except: erf = sys.__stderr__ - print('\n' + '-'*40, file=erf) - print('Unhandled server exception!', file=erf) - print('Thread: %s' % threading.current_thread().name, file=erf) - print('Client Address: ', client_address, file=erf) - print('Request: ', repr(request), file=erf) + print_to_file('\n' + '-'*40, erf) + print_to_file('Unhandled server exception!', erf) + print_to_file('Thread: %s' % threading.current_thread().name, erf) + print_to_file('Client Address: ', client_address, erf) + print_to_file('Request: ', repr(request), erf) traceback.print_exc(file=erf) - print('\n*** Unrecoverable, server exiting!', file=erf) - print('-'*40, file=erf) + print_to_file('\n*** Unrecoverable, server exiting!', erf) + print_to_file('-'*40, erf) quitting = True thread.interrupt_main() @@ -257,7 +275,7 @@ # page help() text to shell. import pydoc # import must be done here to capture i/o binding pydoc.pager = pydoc.plainpager - from idlelib import IOBinding + from . import IOBinding sys.stdin.encoding = sys.stdout.encoding = \ sys.stderr.encoding = IOBinding.encoding self.interp = self.get_remote_proxy("interp") @@ -288,7 +306,7 @@ self.calltip = CallTips.CallTips() self.autocomplete = AutoComplete.AutoComplete() - def runcode(self, code): + def runcode(self, code, temp_filename=None): global interruptable try: self.usr_exc_info = None @@ -302,7 +320,7 @@ if quitting: exit() # even print a user code SystemExit exception, continue - print_exception() + print_exception(temp_filename) jit = self.rpchandler.console.getvar("<>") if jit: self.rpchandler.interp.open_remote_stack_viewer() Index: ScriptBinding.py =================================================================== --- ScriptBinding.py (revision 85696) +++ ScriptBinding.py (working copy) @@ -22,11 +22,11 @@ import string import tabnanny import tokenize -import tkinter.messagebox as tkMessageBox -from idlelib.EditorWindow import EditorWindow -from idlelib import PyShell, IOBinding +import tkMessageBox +from .EditorWindow import EditorWindow +from . import PyShell, IOBinding -from idlelib.configHandler import idleConf +from .configHandler import idleConf indent_message = """Error: Inconsistent indentation detected! @@ -52,38 +52,45 @@ self.flist = self.editwin.flist self.root = self.editwin.root + def _cleanup_temp(self, filename, is_temp=True): + if is_temp: + try: + os.unlink(filename) + except OSError: + pass + def check_module_event(self, event): - filename = self.getfilename() - if not filename: - return 'break' - if not self.checksyntax(filename): - return 'break' - if not self.tabnanny(filename): - return 'break' + filename, is_temp = self.getfilename() + ret = None + if filename: + if self.checksyntax(filename): + if not self.tabnanny(filename): + ret = 'break' + else: + ret = 'break' + self._cleanup_temp(filename, is_temp) + else: + ret = 'break' + return ret + def tabnanny(self, filename): - # XXX: tabnanny should work on binary files as well - with open(filename, 'r', encoding='iso-8859-1') as f: - two_lines = f.readline() + f.readline() - encoding = IOBinding.coding_spec(two_lines) - if not encoding: - encoding = 'utf-8' - f = open(filename, 'r', encoding=encoding) + f = open(filename, 'r') try: tabnanny.process_tokens(tokenize.generate_tokens(f.readline)) - except tokenize.TokenError as msg: + except tokenize.TokenError, msg: msgtxt, (lineno, start) = msg self.editwin.gotoline(lineno) self.errorbox("Tabnanny Tokenizing Error", "Token Error: %s" % msgtxt) return False - except tabnanny.NannyNag as nag: + except tabnanny.NannyNag, nag: # The error messages from tabnanny are too confusing... self.editwin.gotoline(nag.get_lineno()) self.errorbox("Tab/space error", indent_message) return False return True - + def checksyntax(self, filename): self.shell = shell = self.flist.open_shell() saved_stream = shell.get_warning_stream() @@ -124,13 +131,15 @@ add that directory to its sys.path if not already included. """ - filename = self.getfilename() + filename, is_temp = self.getfilename() if not filename: return 'break' code = self.checksyntax(filename) if not code: + self._cleanup_temp(filename, is_temp) return 'break' if not self.tabnanny(filename): + self._cleanup_temp(filename, is_temp) return 'break' shell = self.shell interp = shell.interp @@ -153,7 +162,11 @@ # XXX KBK 03Jul04 When run w/o subprocess, runtime warnings still # go to __stderr__. With subprocess, they go to the shell. # Need to change streams in PyShell.ModifiedInterpreter. - interp.runcode(code) + if is_temp: + interp.runcode(code, filename) + interp.tkconsole.append_cleanup_func(self._cleanup_temp, filename) + else: + interp.runcode(code) return 'break' def getfilename(self): @@ -168,21 +181,38 @@ """ filename = self.editwin.io.filename + is_temp = False if not self.editwin.get_saved(): autosave = idleConf.GetOption('main', 'General', 'autosave', type='bool') - if autosave and filename: - self.editwin.io.save(None) - else: + save_before_run = idleConf.GetOption('main', 'General', + 'save-before-run', default=1, type='bool') + io = self.editwin.io + if filename and autosave: + # This has been saved before and IDLE is configured to not + # prompt before saving this file again. + io.save(None) + elif filename or save_before_run: + # This has been saved before and IDLE is configured to prompt + # before saving this file again, or, this has never been + # saved and the configuration tells it must be saved. reply = self.ask_save_dialog() self.editwin.text.focus_set() if reply == "ok": - self.editwin.io.save(None) + io.save(None) filename = self.editwin.io.filename else: filename = None - return filename + else: + # This has never been saved before but IDLE is configured + # to allow running the present code without explicitly + # saving. + if not save_before_run: + filename = io.save_as_temp(prefix='IDLE_rtmp_') + is_temp = True + return filename, is_temp + def ask_save_dialog(self): msg = "Source Must Be Saved\n" + 5*' ' + "OK to Save?" mb = tkMessageBox.Message(title="Save Before Run or Check", Index: ScrolledList.py =================================================================== --- ScrolledList.py (revision 85696) +++ ScrolledList.py (working copy) @@ -1,4 +1,4 @@ -from tkinter import * +from Tkinter import * class ScrolledList: Index: SearchDialog.py =================================================================== --- SearchDialog.py (revision 85696) +++ SearchDialog.py (working copy) @@ -1,7 +1,7 @@ -from tkinter import * +from Tkinter import * -from idlelib import SearchEngine -from idlelib.SearchDialogBase import SearchDialogBase +from . import SearchEngine +from .SearchDialogBase import SearchDialogBase def _setup(text): root = text._root() Index: SearchDialogBase.py =================================================================== --- SearchDialogBase.py (revision 85696) +++ SearchDialogBase.py (working copy) @@ -1,4 +1,4 @@ -from tkinter import * +from Tkinter import * class SearchDialogBase: Index: SearchEngine.py =================================================================== --- SearchEngine.py (revision 85696) +++ SearchEngine.py (working copy) @@ -1,6 +1,6 @@ import re -from tkinter import * -import tkinter.messagebox as tkMessageBox +from Tkinter import * +import tkMessageBox def get(root): if not hasattr(root, "_searchengine"): Index: StackViewer.py =================================================================== --- StackViewer.py (revision 85696) +++ StackViewer.py (working copy) @@ -2,12 +2,12 @@ import sys import linecache -from idlelib.TreeWidget import TreeNode, TreeItem, ScrolledCanvas -from idlelib.ObjectBrowser import ObjectTreeItem, make_objecttreeitem +from .TreeWidget import TreeNode, TreeItem, ScrolledCanvas +from .ObjectBrowser import ObjectTreeItem, make_objecttreeitem def StackBrowser(root, flist=None, tb=None, top=None): if top is None: - from tkinter import Toplevel + from Tkinter import Toplevel top = Toplevel(root) sc = ScrolledCanvas(top, bg="white", highlightthickness=0) sc.frame.pack(expand=1, fill="both") Index: tabbedpages.py =================================================================== --- tabbedpages.py (revision 85696) +++ tabbedpages.py (working copy) @@ -7,7 +7,7 @@ TabSet -- A widget containing tabs (buttons) in one or more rows. """ -from tkinter import * +from Tkinter import * class InvalidNameError(Exception): pass class AlreadyExistsError(Exception): pass Index: textView.py =================================================================== --- textView.py (revision 85696) +++ textView.py (working copy) @@ -2,14 +2,14 @@ """ -from tkinter import * -import tkinter.messagebox as tkMessageBox +from Tkinter import * +import tkMessageBox class TextViewer(Toplevel): """A simple text viewer dialog for IDLE """ - def __init__(self, parent, title, text): + def __init__(self, parent, title, text, modal=True): """Show the given text in a scrollable window with a 'close' button """ @@ -24,8 +24,6 @@ self.CreateWidgets() self.title(title) - self.transient(parent) - self.grab_set() self.protocol("WM_DELETE_WINDOW", self.Ok) self.parent = parent self.textView.focus_set() @@ -34,8 +32,12 @@ self.bind('',self.Ok) #dismiss dialog self.textView.insert(0.0, text) self.textView.config(state=DISABLED) - self.wait_window() + if modal: + self.transient(parent) + self.grab_set() + self.wait_window() + def CreateWidgets(self): frameText = Frame(self, relief=SUNKEN, height=700) frameButtons = Frame(self) @@ -56,34 +58,46 @@ def Ok(self, event=None): self.destroy() +def view_text(parent, title, text, modal=True): + return TextViewer(parent, title, text, modal) -def view_text(parent, title, text): - TextViewer(parent, title, text) - -def view_file(parent, title, filename, encoding=None): +def view_file(parent, title, filename, encoding=None, modal=True): try: - textFile = open(filename, 'r', encoding=encoding) + if encoding: + import codecs + textFile = codecs.open(filename, 'r', encoding) + else: + textFile = open(filename, 'r') except IOError: - import tkinter.messagebox as tkMessageBox + import Tkinter.messagebox as tkMessageBox tkMessageBox.showerror(title='File Load Error', message='Unable to load file %r .' % filename, parent=parent) else: - return view_text(parent, title, textFile.read()) + return view_text(parent, title, textFile.read(), modal) if __name__ == '__main__': #test the dialog + import os root=Tk() root.title('textView test') - filename = './textView.py' + filename = os.path.abspath(__file__) text = file(filename, 'r').read() + btn1 = Button(root, text='view_text', - command=lambda:view_text(root, 'view_text', text)) + command=lambda:view_text(root, 'view_text', text)) btn1.pack(side=LEFT) + btn2 = Button(root, text='view_file', command=lambda:view_file(root, 'view_file', filename)) btn2.pack(side=LEFT) + + btn3 = Button(root, text='nonmodal view_text', + command=lambda:view_text(root, 'nonmodal view_text', text, + modal=False)) + btn3.pack(side=LEFT) + close = Button(root, text='Close', command=root.destroy) close.pack(side=RIGHT) root.mainloop() Index: ToolTip.py =================================================================== --- ToolTip.py (revision 85696) +++ ToolTip.py (working copy) @@ -3,7 +3,7 @@ # may be useful for some purposes in (or almost in ;) the current project scope # Ideas gleaned from PySol -from tkinter import * +from Tkinter import * class ToolTipBase: Index: TreeWidget.py =================================================================== --- TreeWidget.py (revision 85696) +++ TreeWidget.py (working copy) @@ -15,11 +15,11 @@ # - optimize tree redraw after expand of subnode import os -from tkinter import * +from Tkinter import * import imp -from idlelib import ZoomHeight -from idlelib.configHandler import idleConf +from . import ZoomHeight +from .configHandler import idleConf ICONDIR = "Icons" @@ -452,7 +452,7 @@ # Testing functions def test(): - from idlelib import PyShell + from . import PyShell root = Toplevel(PyShell.root) root.configure(bd=0, bg="yellow") root.focus_set() Index: UndoDelegator.py =================================================================== --- UndoDelegator.py (revision 85696) +++ UndoDelegator.py (working copy) @@ -1,7 +1,7 @@ import string -from tkinter import * +from Tkinter import * -from idlelib.Delegator import Delegator +from .Delegator import Delegator #$ event <> #$ win @@ -15,7 +15,6 @@ #$ win #$ unix - class UndoDelegator(Delegator): max_undo = 1000 @@ -38,10 +37,10 @@ def dump_event(self, event): from pprint import pprint pprint(self.undolist[:self.pointer]) - print("pointer:", self.pointer, end=' ') - print("saved:", self.saved, end=' ') - print("can_merge:", self.can_merge, end=' ') - print("get_saved():", self.get_saved()) + print "pointer:", self.pointer, ' ', + print "saved:", self.saved, ' ', + print "can_merge:", self.can_merge, ' ', + print "get_saved():", self.get_saved() pprint(self.undolist[self.pointer:]) return "break" @@ -337,7 +336,7 @@ return self.depth def main(): - from idlelib.Percolator import Percolator + from .Percolator import Percolator root = Tk() root.wm_protocol("WM_DELETE_WINDOW", root.quit) text = Text() Index: WidgetRedirector.py =================================================================== --- WidgetRedirector.py (revision 85696) +++ WidgetRedirector.py (working copy) @@ -1,4 +1,4 @@ -from tkinter import * +from Tkinter import * class WidgetRedirector: @@ -77,6 +77,7 @@ to *args to accomplish that. For an example, see ColorDelegator.py. ''' + operation = str(operation) m = self._operations.get(operation) try: if m: Index: WindowList.py =================================================================== --- WindowList.py (revision 85696) +++ WindowList.py (working copy) @@ -1,4 +1,4 @@ -from tkinter import * +from Tkinter import * class WindowList: @@ -77,9 +77,13 @@ # Subclass can override return self.wm_title() - def wakeup(self): + def wakeup(self, anystate=False): + """Signal the user about something important. + + If anystate is true the window will always come forward, otherwise, + this will only happen if the window has been minimized.""" try: - if self.wm_state() == "iconic": + if anystate or self.wm_state() == "iconic": self.wm_withdraw() self.wm_deiconify() self.tkraise() Index: ZoomHeight.py =================================================================== --- ZoomHeight.py (revision 85696) +++ ZoomHeight.py (working copy) @@ -3,7 +3,7 @@ import re import sys -from idlelib import macosxSupport +from . import macosxSupport class ZoomHeight: @@ -35,7 +35,7 @@ elif macosxSupport.runningAsOSXApp(): # The '88' below is a magic number that avoids placing the bottom # of the window below the panel on my machine. I don't know how - # to calculate the correct value for this with tkinter. + # to calculate the correct value for this with Tkinter. newy = 22 newheight = newheight - newy - 88