Index: __init__.py =================================================================== --- __init__.py (revision 85402) +++ __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 85402) +++ aboutDialog.py (working copy) @@ -5,8 +5,8 @@ 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 85402) +++ 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 85402) +++ AutoCompleteWindow.py (working copy) @@ -2,8 +2,8 @@ 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 .MultiCall import MC_SHIFT +from .AutoComplete import COMPLETE_FILES, COMPLETE_ATTRIBUTES HIDE_VIRTUAL_EVENT_NAME = "<>" HIDE_SEQUENCES = ("", "") Index: Bindings.py =================================================================== --- Bindings.py (revision 85402) +++ 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 85402) +++ 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: ClassBrowser.py =================================================================== --- ClassBrowser.py (revision 85402) +++ 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 85402) +++ CodeContext.py (working copy) @@ -13,7 +13,7 @@ from tkinter.constants 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"]) Index: ColorDelegator.py =================================================================== --- ColorDelegator.py (revision 85402) +++ ColorDelegator.py (working copy) @@ -3,8 +3,8 @@ import keyword import builtins from tkinter import * -from idlelib.Delegator import Delegator -from idlelib.configHandler import idleConf +from .Delegator import Delegator +from .configHandler import idleConf DEBUG = False @@ -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 85402) +++ 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] Index: config-main.def =================================================================== --- config-main.def (revision 85402) +++ config-main.def (working copy) @@ -46,6 +46,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 85402) +++ configDialog.py (working copy) @@ -13,15 +13,14 @@ import tkinter.messagebox as tkMessageBox import tkinter.colorchooser as tkColorChooser import tkinter.font as tkFont -import copy -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 85402) +++ configHandler.py (working copy) @@ -20,7 +20,7 @@ import os import sys -from idlelib import macosxSupport +from . import macosxSupport from configparser import ConfigParser, NoOptionError, NoSectionError class InvalidConfigType(Exception): pass Index: Debugger.py =================================================================== --- Debugger.py (revision 85402) +++ Debugger.py (working copy) @@ -2,11 +2,10 @@ import bdb import types from tkinter import * -from idlelib.WindowList import ListedToplevel -from idlelib.ScrolledList import ScrolledList -from idlelib import macosxSupport +from .WindowList import ListedToplevel +from .ScrolledList import ScrolledList +from . import macosxSupport - class Idb(bdb.Bdb): def __init__(self, gui): Index: EditorWindow.py =================================================================== --- EditorWindow.py (revision 85402) +++ EditorWindow.py (working copy) @@ -10,16 +10,16 @@ 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 @@ -53,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 .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 idlelib.MultiStatusBar import MultiStatusBar + from .MultiStatusBar import MultiStatusBar help_url = None @@ -406,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() @@ -415,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': @@ -579,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): Index: FileList.py =================================================================== --- FileList.py (revision 85402) +++ FileList.py (working copy) @@ -6,7 +6,7 @@ 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 +111,7 @@ def _test(): - from idlelib.EditorWindow import fixwordbreaks + from .EditorWindow import fixwordbreaks import sys root = Tk() fixwordbreaks(root) Index: FileRevert.py =================================================================== --- FileRevert.py (revision 0) +++ FileRevert.py (revision 0) @@ -0,0 +1,37 @@ +""" +FileRevert is an extension that re-reads the currently open file and +displays the possibly updated content. +""" +from tkinter import * +import tkinter.messagebox as tkMessageBox + +class FileRevert: + + menudefs = [('file', [('Revert', '<>')])] + + def __init__(self, editwin): + self.text = editwin.text + self.io = editwin.io + + def revert_file_event(self, event): + fname = self.io.filename + if fname is None: + # No file associated. + return + + if not self.io.get_saved(): + msg = "Do you want to save a copy of %s before reverting?" % fname + res = tkMessageBox.askyesnocancel( + master=self.text, + title="Save a copy", + message=msg) + if res is None: + # Canceled. + return + elif res: + self.io.save_a_copy(event) + # XXX Maybe it would be better to not revert the file when + # user cancels the dialog that saves a copy of the current + # file. + + self.io.loadfile(fname) Index: FormatParagraph.py =================================================================== --- FormatParagraph.py (revision 85402) +++ 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 85402) +++ GrepDialog.py (working copy) @@ -2,8 +2,8 @@ import fnmatch import sys from tkinter import * -from idlelib import SearchEngine -from idlelib.SearchDialogBase import SearchDialogBase +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 85402) +++ 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 85402) +++ 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 85402) +++ 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() +import vidle.PyShell +vidle.PyShell.main() Index: idle.pyw =================================================================== --- idle.pyw (revision 85402) +++ idle.pyw (working copy) @@ -1,5 +1,5 @@ try: - import idlelib.PyShell + import vidle.PyShell except ImportError: # IDLE is not installed, but maybe PyShell is on sys.path: try: @@ -18,4 +18,4 @@ os.environ['PYTHONPATH'] = idledir PyShell.main() else: - idlelib.PyShell.main() + vidle.PyShell.main() Index: IdleHistory.py =================================================================== --- IdleHistory.py (revision 85402) +++ IdleHistory.py (working copy) @@ -1,4 +1,4 @@ -from idlelib.configHandler import idleConf +from .configHandler import idleConf class History: Index: IOBinding.py =================================================================== --- IOBinding.py (revision 85402) +++ IOBinding.py (working copy) @@ -1,5 +1,4 @@ import os -import types import sys import codecs import tempfile @@ -9,7 +8,7 @@ from tkinter import * from tkinter.simpledialog import askstring -from idlelib.configHandler import idleConf +from .configHandler import idleConf from codecs import BOM_UTF8 @@ -142,14 +141,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: @@ -232,7 +237,7 @@ # before being able to execute the code self.set_saved(False) self.text.mark_set("insert", "1.0") - self.text.see("insert") +## self.text.see("insert") self.updaterecentfileslist(filename) return True @@ -327,7 +332,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 +380,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") @@ -437,11 +468,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 +509,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 +534,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 85402) +++ keybindingDialog.py (working copy) @@ -4,7 +4,7 @@ from tkinter import * import tkinter.messagebox as 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 85402) +++ macosxSupport.py (working copy) @@ -20,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, @@ -51,10 +51,10 @@ # 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 .EditorWindow import prepstr, get_accelerator + from . import Bindings + from . import WindowList + from .MultiCall import MultiCallCreator menubar = Menu(root) root.configure(menu=menubar) @@ -77,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 85402) +++ MultiCall.py (working copy) @@ -32,7 +32,7 @@ import sys import re import tkinter -from idlelib import macosxSupport +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; Index: NEWS.txt =================================================================== --- NEWS.txt (revision 85402) +++ NEWS.txt (working copy) @@ -1,8 +1,53 @@ -What's New in IDLE 3.1b1? +What is VIDLE? ========================= -*Release date: XX-XXX-09* +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 fixes and features include: + * A configuration preference that permits writing and running + test programs from the editor without having to save the file + (you're warned upon quitting whether to save). + + * Bringing the shell window forward in case of an error (because + novices often failed to realize why their program had stopped). + * A revert plug-in (FileRevert.py). + + * Unreliable and slow termination of user program on Windows; + now you can re-run without first closing the graphics window. + + * Missing preferences and other menus on Macintosh. + + * Use any port for RPC server, so multiple instances of VIDLE + can run at the same time without interference. + +Polo submitted patches at the end of the summer of 2009, found at +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 October 2010 Bruce Sherwood manually applied Polo's patches +to the idlelib found at + svn.python.org/projects/python/tags/r312/Lib/idlelib + +In addition to applying Polo's patches, the new code uses the new +dot addressing of modules (e.g. from . import PyShell). Except for +having to change one absolute address in the use of __import__ in +PyShell.py, this version can work either in Python31/Lib/idlelib or +in Python31/Lib/site-packages/vidle. This VIDLE will be included in +the installer for VPython for Python 3. + +What's New in IDLE 3.1? +========================= + +*Release date: 27-Jun-09* + - Use of 'filter' in keybindingDialog.py was causing custom key assignment to fail. Patch 5707 amaury.forgeotdarc. Index: ObjectBrowser.py =================================================================== --- ObjectBrowser.py (revision 85402) +++ ObjectBrowser.py (working copy) @@ -9,7 +9,7 @@ # 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 Index: OutputWindow.py =================================================================== --- OutputWindow.py (revision 85402) +++ OutputWindow.py (working copy) @@ -1,8 +1,8 @@ from tkinter import * -from idlelib.EditorWindow import EditorWindow +from .EditorWindow import EditorWindow import re import tkinter.messagebox as tkMessageBox -from idlelib import IOBinding +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 85402) +++ 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 85402) +++ 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 85402) +++ 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: Index: PyShell.py =================================================================== --- PyShell.py (revision 85402) +++ PyShell.py (working copy) @@ -9,6 +9,7 @@ import time import threading import traceback +import subprocess import types import linecache @@ -22,26 +23,22 @@ sys.exit(1) import tkinter.messagebox as 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 @@ -55,20 +52,21 @@ else: def idle_showwarning(message, category, filename, lineno, file=None, line=None): - file = warning_stream + if file is None: + file = warning_stream try: - file.write(warnings.formatwarning(message, category, filename,\ + file.write(warnings.formatwarning(message, category, filename, lineno, file=file, line=line)) except IOError: pass ## file (probably __stderr__) is invalid, warning dropped. warnings.showwarning = idle_showwarning - def idle_formatwarning(message, category, filename, lineno, - file=None, line=None): + def idle_formatwarning(message, category, filename, lineno, line=None): """Format warnings the IDLE way""" s = "\nWarning (from warnings module):\n" s += ' File \"%s\", line %s\n' % (filename, lineno) - line = linecache.getline(filename, lineno).strip() \ - if line is None else line + if line is None: + line = linecache.getline(filename, lineno) + line = line.strip() if line: s += " %s\n" % line s += "%s: %s\n>>> " % (category.__name__, message) @@ -81,7 +79,7 @@ Rather than repeating the linecache code, patch it to save the entries, call the original linecache.checkcache() - (which destroys them), and then restore the saved entries. + (skipping them), and then restore the saved entries. orig_checkcache is bound at definition time to the original method, allowing it to be patched. @@ -89,10 +87,10 @@ """ cache = linecache.cache save = {} - for filename in cache: - if filename[:1] + filename[-1:] == '<>': - save[filename] = cache[filename] - orig_checkcache() + for key in list(cache): + if key[:1] + key[-1:] == '<>': + save[key] = cache.pop(key) + orig_checkcache(filename) cache.update(save) # Patch linecache.checkcache(): @@ -119,8 +117,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 +243,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 +350,13 @@ self.port = PORT rpcclt = None - rpcpid = None + rpcproc = None def spawn_subprocess(self): - if self.subprocess_arglist == None: + 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), ( @@ -361,8 +367,8 @@ # run from the IDLE source directory. del_exitf = idleConf.GetOption('main', 'General', 'delete-exitfunc', default=False, type='bool') - if __name__ == 'idlelib.PyShell': - command = "__import__('idlelib.run').run.main(%r)" % (del_exitf,) + if __name__ == 'vidle.PyShell': + command = "__import__('vidle.run').run.main(%r)" % (del_exitf,) else: command = "__import__('run').main(%r)" % (del_exitf,) if sys.platform[:3] == 'win' and ' ' in sys.executable: @@ -414,6 +420,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 +438,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 +454,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 +477,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: @@ -556,13 +561,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'] @@ -604,7 +609,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 +671,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 +719,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 +732,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: @@ -733,7 +760,7 @@ self.tkconsole.canceled = False print("KeyboardInterrupt", file=self.tkconsole.stderr) else: - self.showtraceback() + self.showtraceback(tempname) finally: if not use_subprocess: try: @@ -796,7 +823,7 @@ # New classes - from idlelib.IdleHistory import History + from .IdleHistory import History def __init__(self, flist=None): if use_subprocess: @@ -820,6 +847,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 +864,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 +884,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 +963,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: @@ -1016,7 +1053,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 +1247,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 +1324,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 +1395,7 @@ global flist, root, use_subprocess use_subprocess = True - enable_shell = True + enable_shell = False enable_edit = False debug = False cmd = None @@ -1318,8 +1415,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() @@ -1370,6 +1466,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 +1477,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 85402) +++ 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 85402) +++ 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 85402) +++ ReplaceDialog.py (working copy) @@ -1,7 +1,7 @@ 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 85402) +++ rpc.py (working copy) @@ -144,7 +144,7 @@ def exithook(self): "override for specific exit action" - os._exit() + os._exit(0) def debug(self, *args): if not self.debugging: Index: run.py =================================================================== --- run.py (revision 85402) +++ run.py (working copy) @@ -7,13 +7,14 @@ import threading 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__ @@ -25,12 +26,13 @@ pass else: def idle_formatwarning_subproc(message, category, filename, lineno, - file=None, line=None): + line=None): """Format warnings the IDLE way""" s = "\nWarning (from warnings module):\n" s += ' File \"%s\", line %s\n' % (filename, lineno) - line = linecache.getline(filename, lineno).strip() \ - if line is None else line + if line is None: + line = linecache.getline(filename, lineno) + line = line.strip() if line: s += " %s\n" % line s += "%s: %s\n" % (category.__name__, message) @@ -149,7 +151,7 @@ "Socket Error: %s" % err.args[1]) root.destroy() -def print_exception(): +def print_exception(temp_filename=None): import linecache linecache.checkcache() flush_stdout() @@ -161,7 +163,17 @@ 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) @@ -256,7 +268,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") @@ -287,7 +299,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 @@ -301,7 +313,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 85402) +++ ScriptBinding.py (working copy) @@ -23,10 +23,10 @@ import tabnanny import tokenize import tkinter.messagebox as tkMessageBox -from idlelib.EditorWindow import EditorWindow -from idlelib import PyShell, IOBinding +from .EditorWindow import EditorWindow +from . import PyShell, IOBinding -from idlelib.configHandler import idleConf +from .configHandler import idleConf indent_message = """Error: Inconsistent indentation detected! @@ -52,15 +52,28 @@ 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: @@ -124,13 +137,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 +168,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 +187,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: SearchDialog.py =================================================================== --- SearchDialog.py (revision 85402) +++ SearchDialog.py (working copy) @@ -1,7 +1,7 @@ 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: StackViewer.py =================================================================== --- StackViewer.py (revision 85402) +++ StackViewer.py (working copy) @@ -2,8 +2,8 @@ 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: Index: textView.py =================================================================== --- textView.py (revision 85402) +++ textView.py (working copy) @@ -9,7 +9,7 @@ """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,15 +58,14 @@ 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: if encoding: import codecs - textFile = codecs.open(filename, 'r') + textFile = codecs.open(filename, 'r', encoding) else: textFile = open(filename, 'r') except IOError: @@ -73,21 +74,30 @@ 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: TreeWidget.py =================================================================== --- TreeWidget.py (revision 85402) +++ TreeWidget.py (working copy) @@ -18,8 +18,8 @@ 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 85402) +++ UndoDelegator.py (working copy) @@ -1,7 +1,7 @@ import string from tkinter import * -from idlelib.Delegator import Delegator +from .Delegator import Delegator #$ event <> #$ win @@ -337,7 +337,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 85402) +++ WidgetRedirector.py (working copy) @@ -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 85402) +++ WindowList.py (working copy) @@ -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 85402) +++ ZoomHeight.py (working copy) @@ -3,7 +3,7 @@ import re import sys -from idlelib import macosxSupport +from . import macosxSupport class ZoomHeight: