summaryrefslogtreecommitdiffstats
path: root/Lib/idlelib/ExecBinding.py
blob: 67b08220d9dffbff8a0cac1740f81bf4b133a32c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
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