1import sys
2import io
3import linecache
4import time
5import socket
6import traceback
7import thread
8import threading
9import Queue
10
11from idlelib import CallTips
12from idlelib import AutoComplete
13
14from idlelib import RemoteDebugger
15from idlelib import RemoteObjectBrowser
16from idlelib import StackViewer
17from idlelib import rpc
18from idlelib import PyShell
19from idlelib import IOBinding
20
21import __main__
22
23LOCALHOST = '127.0.0.1'
24
25try:
26    import warnings
27except ImportError:
28    pass
29else:
30    def idle_formatwarning_subproc(message, category, filename, lineno,
31                                   line=None):
32        """Format warnings the IDLE way"""
33        s = "\nWarning (from warnings module):\n"
34        s += '  File \"%s\", line %s\n' % (filename, lineno)
35        if line is None:
36            line = linecache.getline(filename, lineno)
37        line = line.strip()
38        if line:
39            s += "    %s\n" % line
40        s += "%s: %s\n" % (category.__name__, message)
41        return s
42    warnings.formatwarning = idle_formatwarning_subproc
43
44# Thread shared globals: Establish a queue between a subthread (which handles
45# the socket) and the main thread (which runs user code), plus global
46# completion, exit and interruptable (the main thread) flags:
47
48exit_now = False
49quitting = False
50interruptable = False
51
52def main(del_exitfunc=False):
53    """Start the Python execution server in a subprocess
54
55    In the Python subprocess, RPCServer is instantiated with handlerclass
56    MyHandler, which inherits register/unregister methods from RPCHandler via
57    the mix-in class SocketIO.
58
59    When the RPCServer 'server' is instantiated, the TCPServer initialization
60    creates an instance of run.MyHandler and calls its handle() method.
61    handle() instantiates a run.Executive object, passing it a reference to the
62    MyHandler object.  That reference is saved as attribute rpchandler of the
63    Executive instance.  The Executive methods have access to the reference and
64    can pass it on to entities that they command
65    (e.g. RemoteDebugger.Debugger.start_debugger()).  The latter, in turn, can
66    call MyHandler(SocketIO) register/unregister methods via the reference to
67    register and unregister themselves.
68
69    """
70    global exit_now
71    global quitting
72    global no_exitfunc
73    no_exitfunc = del_exitfunc
74    #time.sleep(15) # test subprocess not responding
75    try:
76        assert(len(sys.argv) > 1)
77        port = int(sys.argv[-1])
78    except:
79        print>>sys.stderr, "IDLE Subprocess: no IP port passed in sys.argv."
80        return
81    sys.argv[:] = [""]
82    sockthread = threading.Thread(target=manage_socket,
83                                  name='SockThread',
84                                  args=((LOCALHOST, port),))
85    sockthread.setDaemon(True)
86    sockthread.start()
87    while 1:
88        try:
89            if exit_now:
90                try:
91                    exit()
92                except KeyboardInterrupt:
93                    # exiting but got an extra KBI? Try again!
94                    continue
95            try:
96                seq, request = rpc.request_queue.get(block=True, timeout=0.05)
97            except Queue.Empty:
98                continue
99            method, args, kwargs = request
100            ret = method(*args, **kwargs)
101            rpc.response_queue.put((seq, ret))
102        except KeyboardInterrupt:
103            if quitting:
104                exit_now = True
105            continue
106        except SystemExit:
107            raise
108        except:
109            type, value, tb = sys.exc_info()
110            try:
111                print_exception()
112                rpc.response_queue.put((seq, None))
113            except:
114                # Link didn't work, print same exception to __stderr__
115                traceback.print_exception(type, value, tb, file=sys.__stderr__)
116                exit()
117            else:
118                continue
119
120def manage_socket(address):
121    for i in range(3):
122        time.sleep(i)
123        try:
124            server = MyRPCServer(address, MyHandler)
125            break
126        except socket.error, err:
127            print>>sys.__stderr__,"IDLE Subprocess: socket error: "\
128                                        + err.args[1] + ", retrying...."
129    else:
130        print>>sys.__stderr__, "IDLE Subprocess: Connection to "\
131                               "IDLE GUI failed, exiting."
132        show_socket_error(err, address)
133        global exit_now
134        exit_now = True
135        return
136    server.handle_request() # A single request only
137
138def show_socket_error(err, address):
139    import Tkinter
140    import tkMessageBox
141    root = Tkinter.Tk()
142    root.withdraw()
143    if err.args[0] == 61: # connection refused
144        msg = "IDLE's subprocess can't connect to %s:%d.  This may be due "\
145              "to your personal firewall configuration.  It is safe to "\
146              "allow this internal connection because no data is visible on "\
147              "external ports." % address
148        tkMessageBox.showerror("IDLE Subprocess Error", msg, parent=root)
149    else:
150        tkMessageBox.showerror("IDLE Subprocess Error",
151                               "Socket Error: %s" % err.args[1])
152    root.destroy()
153
154def print_exception():
155    import linecache
156    linecache.checkcache()
157    flush_stdout()
158    efile = sys.stderr
159    typ, val, tb = excinfo = sys.exc_info()
160    sys.last_type, sys.last_value, sys.last_traceback = excinfo
161    tbe = traceback.extract_tb(tb)
162    print>>efile, '\nTraceback (most recent call last):'
163    exclude = ("run.py", "rpc.py", "threading.py", "Queue.py",
164               "RemoteDebugger.py", "bdb.py")
165    cleanup_traceback(tbe, exclude)
166    traceback.print_list(tbe, file=efile)
167    lines = traceback.format_exception_only(typ, val)
168    for line in lines:
169        print>>efile, line,
170
171def cleanup_traceback(tb, exclude):
172    "Remove excluded traces from beginning/end of tb; get cached lines"
173    orig_tb = tb[:]
174    while tb:
175        for rpcfile in exclude:
176            if tb[0][0].count(rpcfile):
177                break    # found an exclude, break for: and delete tb[0]
178        else:
179            break        # no excludes, have left RPC code, break while:
180        del tb[0]
181    while tb:
182        for rpcfile in exclude:
183            if tb[-1][0].count(rpcfile):
184                break
185        else:
186            break
187        del tb[-1]
188    if len(tb) == 0:
189        # exception was in IDLE internals, don't prune!
190        tb[:] = orig_tb[:]
191        print>>sys.stderr, "** IDLE Internal Exception: "
192    rpchandler = rpc.objecttable['exec'].rpchandler
193    for i in range(len(tb)):
194        fn, ln, nm, line = tb[i]
195        if nm == '?':
196            nm = "-toplevel-"
197        if not line and fn.startswith("<pyshell#"):
198            line = rpchandler.remotecall('linecache', 'getline',
199                                              (fn, ln), {})
200        tb[i] = fn, ln, nm, line
201
202def flush_stdout():
203    try:
204        if sys.stdout.softspace:
205            sys.stdout.softspace = 0
206            sys.stdout.write("\n")
207    except (AttributeError, EOFError):
208        pass
209
210def exit():
211    """Exit subprocess, possibly after first deleting sys.exitfunc
212
213    If config-main.cfg/.def 'General' 'delete-exitfunc' is True, then any
214    sys.exitfunc will be removed before exiting.  (VPython support)
215
216    """
217    if no_exitfunc:
218        try:
219            del sys.exitfunc
220        except AttributeError:
221            pass
222    sys.exit(0)
223
224class MyRPCServer(rpc.RPCServer):
225
226    def handle_error(self, request, client_address):
227        """Override RPCServer method for IDLE
228
229        Interrupt the MainThread and exit server if link is dropped.
230
231        """
232        global quitting
233        try:
234            raise
235        except SystemExit:
236            raise
237        except EOFError:
238            global exit_now
239            exit_now = True
240            thread.interrupt_main()
241        except:
242            erf = sys.__stderr__
243            print>>erf, '\n' + '-'*40
244            print>>erf, 'Unhandled server exception!'
245            print>>erf, 'Thread: %s' % threading.currentThread().getName()
246            print>>erf, 'Client Address: ', client_address
247            print>>erf, 'Request: ', repr(request)
248            traceback.print_exc(file=erf)
249            print>>erf, '\n*** Unrecoverable, server exiting!'
250            print>>erf, '-'*40
251            quitting = True
252            thread.interrupt_main()
253
254class MyHandler(rpc.RPCHandler):
255
256    def handle(self):
257        """Override base method"""
258        executive = Executive(self)
259        self.register("exec", executive)
260        self.console = self.get_remote_proxy("console")
261        sys.stdin = PyShell.PseudoInputFile(self.console, "stdin",
262                IOBinding.encoding)
263        sys.stdout = PyShell.PseudoOutputFile(self.console, "stdout",
264                IOBinding.encoding)
265        sys.stderr = PyShell.PseudoOutputFile(self.console, "stderr",
266                IOBinding.encoding)
267
268        # Keep a reference to stdin so that it won't try to exit IDLE if
269        # sys.stdin gets changed from within IDLE's shell. See issue17838.
270        self._keep_stdin = sys.stdin
271
272        self.interp = self.get_remote_proxy("interp")
273        rpc.RPCHandler.getresponse(self, myseq=None, wait=0.05)
274
275    def exithook(self):
276        "override SocketIO method - wait for MainThread to shut us down"
277        time.sleep(10)
278
279    def EOFhook(self):
280        "Override SocketIO method - terminate wait on callback and exit thread"
281        global quitting
282        quitting = True
283        thread.interrupt_main()
284
285    def decode_interrupthook(self):
286        "interrupt awakened thread"
287        global quitting
288        quitting = True
289        thread.interrupt_main()
290
291
292class Executive(object):
293
294    def __init__(self, rpchandler):
295        self.rpchandler = rpchandler
296        self.locals = __main__.__dict__
297        self.calltip = CallTips.CallTips()
298        self.autocomplete = AutoComplete.AutoComplete()
299
300    def runcode(self, code):
301        global interruptable
302        try:
303            self.usr_exc_info = None
304            interruptable = True
305            try:
306                exec code in self.locals
307            finally:
308                interruptable = False
309        except SystemExit:
310            # Scripts that raise SystemExit should just
311            # return to the interactive prompt
312            pass
313        except:
314            self.usr_exc_info = sys.exc_info()
315            if quitting:
316                exit()
317            print_exception()
318            jit = self.rpchandler.console.getvar("<<toggle-jit-stack-viewer>>")
319            if jit:
320                self.rpchandler.interp.open_remote_stack_viewer()
321        else:
322            flush_stdout()
323
324    def interrupt_the_server(self):
325        if interruptable:
326            thread.interrupt_main()
327
328    def start_the_debugger(self, gui_adap_oid):
329        return RemoteDebugger.start_debugger(self.rpchandler, gui_adap_oid)
330
331    def stop_the_debugger(self, idb_adap_oid):
332        "Unregister the Idb Adapter.  Link objects and Idb then subject to GC"
333        self.rpchandler.unregister(idb_adap_oid)
334
335    def get_the_calltip(self, name):
336        return self.calltip.fetch_tip(name)
337
338    def get_the_completion_list(self, what, mode):
339        return self.autocomplete.fetch_completions(what, mode)
340
341    def stackviewer(self, flist_oid=None):
342        if self.usr_exc_info:
343            typ, val, tb = self.usr_exc_info
344        else:
345            return None
346        flist = None
347        if flist_oid is not None:
348            flist = self.rpchandler.get_remote_proxy(flist_oid)
349        while tb and tb.tb_frame.f_globals["__name__"] in ["rpc", "run"]:
350            tb = tb.tb_next
351        sys.last_type = typ
352        sys.last_value = val
353        item = StackViewer.StackTreeItem(flist, tb)
354        return RemoteObjectBrowser.remote_object_tree_item(item)
355