Initial revision
diff --git a/Lib/idlelib/ExecBinding.py b/Lib/idlelib/ExecBinding.py
new file mode 100644
index 0000000..67b0822
--- /dev/null
+++ b/Lib/idlelib/ExecBinding.py
@@ -0,0 +1,198 @@
+"""Extension to execute a script in a separate process
+
+David Scherer <dscherer@cmu.edu>
+
+  The ExecBinding module, a replacement for ScriptBinding, executes
+  programs in a separate process.  Unlike previous versions, this version
+  communicates with the user process via an RPC protocol (see the 'protocol'
+  module).  The user program is loaded by the 'loader' and 'Remote'
+  modules.  Its standard output and input are directed back to the
+  ExecBinding class through the RPC mechanism and implemented here.
+
+  A "stop program" command is provided and bound to control-break.  Closing
+  the output window also stops the running program.
+"""
+
+import sys
+import os
+import imp
+import OutputWindow
+import protocol
+import spawn
+import traceback
+import tempfile
+
+# Find Python and the loader.  This should be done as early in execution
+#   as possible, because if the current directory or sys.path is changed
+#   it may no longer be possible to get correct paths for these things.
+
+pyth_exe = spawn.hardpath( sys.executable )
+load_py  = spawn.hardpath( imp.find_module("loader")[1] )
+
+# The following mechanism matches loaders up with ExecBindings that are
+#    trying to load something.
+
+waiting_for_loader = []
+
+def loader_connect(client, addr):
+    if waiting_for_loader:
+        a = waiting_for_loader.pop(0)
+        try:
+            return a.connect(client, addr)
+        except:
+            return loader_connect(client,addr)
+
+protocol.publish('ExecBinding', loader_connect)
+
+class ExecBinding:
+    keydefs = {
+        '<<run-complete-script>>': ['<F5>'],
+        '<<stop-execution>>': ['<Cancel>'],   #'<Control-c>'
+    }
+    
+    menudefs = [
+        ('run', [None,
+                  ('Run program', '<<run-complete-script>>'),
+                  ('Stop program', '<<stop-execution>>'),
+                 ]
+        ),
+    ]
+
+    delegate = 1
+
+    def __init__(self, editwin):
+        self.editwin = editwin
+        self.client = None
+        self.temp = []
+
+        if not hasattr(editwin, 'source_window'):
+            self.delegate = 0
+            self.output = OutputWindow.OnDemandOutputWindow(editwin.flist)
+            self.output.close_hook = self.stopProgram
+            self.output.source_window = editwin
+        else:
+            if (self.editwin.source_window and
+                self.editwin.source_window.extensions.has_key('ExecBinding') and
+                not self.editwin.source_window.extensions['ExecBinding'].delegate):
+                    delegate = self.editwin.source_window.extensions['ExecBinding']
+                    self.run_complete_script_event = delegate.run_complete_script_event
+                    self.stop_execution_event = delegate.stop_execution_event
+
+    def __del__(self):
+        self.stopProgram()
+
+    def stop_execution_event(self, event):
+        if self.client:
+            self.stopProgram()
+            self.write('\nProgram stopped.\n','stderr')
+
+    def run_complete_script_event(self, event):
+        filename = self.getfilename()
+        if not filename: return
+        filename = os.path.abspath(filename)
+
+        self.stopProgram()
+
+        self.commands = [ ('run', filename) ]
+        waiting_for_loader.append(self)
+        spawn.spawn( pyth_exe, load_py )
+
+    def connect(self, client, addr):
+        # Called by loader_connect() above.  It is remotely possible that
+        #   we get connected to two loaders if the user is running the
+        #   program repeatedly in a short span of time.  In this case, we
+        #   simply return None, refusing to connect and letting the redundant
+        #   loader die.
+        if self.client: return None
+
+        self.client = client
+        client.set_close_hook( self.connect_lost )
+
+        title = self.editwin.short_title()
+        if title:
+            self.output.set_title(title + " Output")
+        else:
+            self.output.set_title("Output")
+        self.output.write('\n',"stderr")
+        self.output.scroll_clear()
+
+        return self
+
+    def connect_lost(self):
+        # Called by the client's close hook when the loader closes its
+        #   socket.
+
+        # We print a disconnect message only if the output window is already
+        #   open.
+        if self.output.owin and self.output.owin.text:
+            self.output.owin.interrupt()
+            self.output.write("\nProgram disconnected.\n","stderr")
+
+        for t in self.temp:
+            try:
+                os.remove(t)
+            except:
+                pass
+        self.temp = []
+        self.client = None
+
+    def get_command(self):
+        # Called by Remote to find out what it should be executing.
+        # Later this will be used to implement debugging, interactivity, etc.
+        if self.commands:
+            return self.commands.pop(0)
+        return ('finish',)
+
+    def program_exception(self, type, value, tb, first, last):
+        if type == SystemExit: return 0
+
+        for i in range(len(tb)):
+            filename, lineno, name, line = tb[i]
+            if filename in self.temp:
+                filename = 'Untitled'
+            tb[i] = filename, lineno, name, line
+
+        list = traceback.format_list(tb[first:last])
+        exc = traceback.format_exception_only( type, value )
+
+        self.write('Traceback (innermost last)\n', 'stderr')
+        for i in (list+exc):
+            self.write(i, 'stderr')
+
+        self.commands = []
+        return 1
+
+    def write(self, text, tag):
+        self.output.write(text,tag)
+
+    def readline(self):
+        return self.output.readline()
+
+    def stopProgram(self):
+        if self.client:
+          self.client.close()
+          self.client = None
+
+    def getfilename(self):
+        # Save all files which have been named, because they might be modules
+        for edit in self.editwin.flist.inversedict.keys():
+            if edit.io and edit.io.filename and not edit.get_saved():
+                edit.io.save(None)
+
+        # Experimental: execute unnamed buffer
+        if not self.editwin.io.filename:
+            filename = os.path.normcase(os.path.abspath(tempfile.mktemp()))
+            self.temp.append(filename)
+            if self.editwin.io.writefile(filename):
+                return filename
+
+        # If the file isn't save, we save it.  If it doesn't have a filename,
+        #   the user will be prompted.
+        if self.editwin.io and not self.editwin.get_saved():
+            self.editwin.io.save(None)
+
+        # If the file *still* isn't saved, we give up.
+        if not self.editwin.get_saved():
+            return
+
+        return self.editwin.io.filename