PyShell.py revision 73360a3e61274ffcc4c9fc3d09746bd6603e92a5
1#! /usr/bin/env python
2
3import os
4import os.path
5import sys
6import string
7import getopt
8import re
9import socket
10import time
11import threading
12import traceback
13import types
14import exceptions
15
16import linecache
17from code import InteractiveInterpreter
18
19try:
20    from Tkinter import *
21except ImportError:
22    print>>sys.__stderr__, "** IDLE can't import Tkinter.  " \
23                           "Your Python may not be configured for Tk. **"
24    sys.exit(1)
25import tkMessageBox
26
27from EditorWindow import EditorWindow, fixwordbreaks
28from FileList import FileList
29from ColorDelegator import ColorDelegator
30from UndoDelegator import UndoDelegator
31from OutputWindow import OutputWindow
32from configHandler import idleConf
33import idlever
34
35import rpc
36import Debugger
37import RemoteDebugger
38
39IDENTCHARS = string.ascii_letters + string.digits + "_"
40LOCALHOST = '127.0.0.1'
41
42try:
43    from signal import SIGTERM
44except ImportError:
45    SIGTERM = 15
46
47# Change warnings module to write to sys.__stderr__
48try:
49    import warnings
50except ImportError:
51    pass
52else:
53    def idle_showwarning(message, category, filename, lineno):
54        file = sys.__stderr__
55        file.write(warnings.formatwarning(message, category, filename, lineno))
56    warnings.showwarning = idle_showwarning
57
58def extended_linecache_checkcache(orig_checkcache=linecache.checkcache):
59    """Extend linecache.checkcache to preserve the <pyshell#...> entries
60
61    Rather than repeating the linecache code, patch it to save the pyshell#
62    entries, call the original linecache.checkcache(), and then restore the
63    saved entries.  Assigning the orig_checkcache keyword arg freezes its value
64    at definition time to the (original) method linecache.checkcache(), i.e.
65    makes orig_checkcache lexical.
66
67    """
68    cache = linecache.cache
69    save = {}
70    for filename in cache.keys():
71        if filename[:1] + filename[-1:] == '<>':
72            save[filename] = cache[filename]
73    orig_checkcache()
74    cache.update(save)
75
76# Patch linecache.checkcache():
77linecache.checkcache = extended_linecache_checkcache
78
79
80class PyShellEditorWindow(EditorWindow):
81    "Regular text edit window when a shell is present"
82
83    def __init__(self, *args):
84        self.breakpoints = []
85        EditorWindow.__init__(self, *args)
86        self.text.bind("<<set-breakpoint-here>>", self.set_breakpoint_here)
87        self.text.bind("<<clear-breakpoint-here>>", self.clear_breakpoint_here)
88        self.text.bind("<<open-python-shell>>", self.flist.open_shell)
89
90        self.breakpointPath = os.path.join(idleConf.GetUserCfgDir(),
91                                           'breakpoints.lst')
92        # whenever a file is changed, restore breakpoints
93        if self.io.filename: self.restore_file_breaks()
94        def filename_changed_hook(old_hook=self.io.filename_change_hook,
95                                  self=self):
96            self.restore_file_breaks()
97            old_hook()
98        self.io.set_filename_change_hook(filename_changed_hook)
99
100    rmenu_specs = [("Set Breakpoint", "<<set-breakpoint-here>>"),
101                   ("Clear Breakpoint", "<<clear-breakpoint-here>>")]
102
103    def set_breakpoint(self, lineno):
104        text = self.text
105        filename = self.io.filename
106        text.tag_add("BREAK", "%d.0" % lineno, "%d.0" % (lineno+1))
107        try:
108            i = self.breakpoints.index(lineno)
109        except ValueError:  # only add if missing, i.e. do once
110            self.breakpoints.append(lineno)
111        try:    # update the subprocess debugger
112            debug = self.flist.pyshell.interp.debugger
113            debug.set_breakpoint_here(filename, lineno)
114        except: # but debugger may not be active right now....
115            pass
116
117    def set_breakpoint_here(self, event=None):
118        text = self.text
119        filename = self.io.filename
120        if not filename:
121            text.bell()
122            return
123        lineno = int(float(text.index("insert")))
124        self.set_breakpoint(lineno)
125
126    def clear_breakpoint_here(self, event=None):
127        text = self.text
128        filename = self.io.filename
129        if not filename:
130            text.bell()
131            return
132        lineno = int(float(text.index("insert")))
133        try:
134            self.breakpoints.remove(lineno)
135        except:
136            pass
137        text.tag_remove("BREAK", "insert linestart",\
138                        "insert lineend +1char")
139        try:
140            debug = self.flist.pyshell.interp.debugger
141            debug.clear_breakpoint_here(filename, lineno)
142        except:
143            pass
144
145    def clear_file_breaks(self):
146        if self.breakpoints:
147            text = self.text
148            filename = self.io.filename
149            if not filename:
150                text.bell()
151                return
152            self.breakpoints = []
153            text.tag_remove("BREAK", "1.0", END)
154            try:
155                debug = self.flist.pyshell.interp.debugger
156                debug.clear_file_breaks(filename)
157            except:
158                pass
159
160    def store_file_breaks(self):
161        "Save breakpoints when file is saved"
162        # XXX 13 Dec 2002 KBK Currently the file must be saved before it can
163        #     be run.  The breaks are saved at that time.  If we introduce
164        #     a temporary file save feature the save breaks functionality
165        #     needs to be re-verified, since the breaks at the time the
166        #     temp file is created may differ from the breaks at the last
167        #     permanent save of the file.  Currently, a break introduced
168        #     after a save will be effective, but not persistent.
169        #     This is necessary to keep the saved breaks synched with the
170        #     saved file.
171        #
172        #     Breakpoints are set as tagged ranges in the text.  Certain
173        #     kinds of edits cause these ranges to be deleted: Inserting
174        #     or deleting a line just before a breakpoint, and certain
175        #     deletions prior to a breakpoint.  These issues need to be
176        #     investigated and understood.  It's not clear if they are
177        #     Tk issues or IDLE issues, or whether they can actually
178        #     be fixed.  Since a modified file has to be saved before it is
179        #     run, and since self.breakpoints (from which the subprocess
180        #     debugger is loaded) is updated during the save, the visible
181        #     breaks stay synched with the subprocess even if one of these
182        #     unexpected breakpoint deletions occurs.
183        breaks = self.breakpoints
184        filename = self.io.filename
185        try:
186            lines = open(self.breakpointPath,"r").readlines()
187        except IOError:
188            lines = []
189        new_file = open(self.breakpointPath,"w")
190        for line in lines:
191            if not line.startswith(filename + '='):
192                new_file.write(line)
193        self.update_breakpoints()
194        breaks = self.breakpoints
195        if breaks:
196            new_file.write(filename + '=' + str(breaks) + '\n')
197        new_file.close()
198
199    def restore_file_breaks(self):
200        self.text.update()   # this enables setting "BREAK" tags to be visible
201        filename = self.io.filename
202        if filename is None:
203            return
204        if os.path.isfile(self.breakpointPath):
205            lines = open(self.breakpointPath,"r").readlines()
206            for line in lines:
207                if line.startswith(filename + '='):
208                    breakpoint_linenumbers = eval(line[len(filename)+1:])
209                    for breakpoint_linenumber in breakpoint_linenumbers:
210                        self.set_breakpoint(breakpoint_linenumber)
211
212    def update_breakpoints(self):
213        "Retrieves all the breakpoints in the current window"
214        text = self.text
215        ranges = text.tag_ranges("BREAK")
216        linenumber_list = self.ranges_to_linenumbers(ranges)
217        self.breakpoints = linenumber_list
218
219    def ranges_to_linenumbers(self, ranges):
220        lines = []
221        for index in range(0, len(ranges), 2):
222            lineno = int(float(ranges[index]))
223            end = int(float(ranges[index+1]))
224            while lineno < end:
225                lines.append(lineno)
226                lineno += 1
227        return lines
228
229# XXX 13 Dec 2002 KBK Not used currently
230#    def saved_change_hook(self):
231#        "Extend base method - clear breaks if module is modified"
232#        if not self.get_saved():
233#            self.clear_file_breaks()
234#        EditorWindow.saved_change_hook(self)
235
236    def _close(self):
237        "Extend base method - clear breaks when module is closed"
238        self.clear_file_breaks()
239        EditorWindow._close(self)
240
241
242class PyShellFileList(FileList):
243    "Extend base class: file list when a shell is present"
244
245    EditorWindow = PyShellEditorWindow
246
247    pyshell = None
248
249    def open_shell(self, event=None):
250        if self.pyshell:
251            self.pyshell.wakeup()
252        else:
253            self.pyshell = PyShell(self)
254            if self.pyshell:
255                if not self.pyshell.begin():
256                    return None
257        return self.pyshell
258
259
260class ModifiedColorDelegator(ColorDelegator):
261    "Extend base class: colorizer for the shell window itself"
262
263    def __init__(self):
264        ColorDelegator.__init__(self)
265        self.LoadTagDefs()
266
267    def recolorize_main(self):
268        self.tag_remove("TODO", "1.0", "iomark")
269        self.tag_add("SYNC", "1.0", "iomark")
270        ColorDelegator.recolorize_main(self)
271
272    def LoadTagDefs(self):
273        ColorDelegator.LoadTagDefs(self)
274        theme = idleConf.GetOption('main','Theme','name')
275        self.tagdefs.update({
276            "stdin": {'background':None,'foreground':None},
277            "stdout": idleConf.GetHighlight(theme, "stdout"),
278            "stderr": idleConf.GetHighlight(theme, "stderr"),
279            "console": idleConf.GetHighlight(theme, "console"),
280            None: idleConf.GetHighlight(theme, "normal"),
281        })
282
283class ModifiedUndoDelegator(UndoDelegator):
284    "Extend base class: forbid insert/delete before the I/O mark"
285
286    def insert(self, index, chars, tags=None):
287        try:
288            if self.delegate.compare(index, "<", "iomark"):
289                self.delegate.bell()
290                return
291        except TclError:
292            pass
293        UndoDelegator.insert(self, index, chars, tags)
294
295    def delete(self, index1, index2=None):
296        try:
297            if self.delegate.compare(index1, "<", "iomark"):
298                self.delegate.bell()
299                return
300        except TclError:
301            pass
302        UndoDelegator.delete(self, index1, index2)
303
304
305class MyRPCClient(rpc.RPCClient):
306
307    def handle_EOF(self):
308        "Override the base class - just re-raise EOFError"
309        raise EOFError
310
311
312class ModifiedInterpreter(InteractiveInterpreter):
313
314    def __init__(self, tkconsole):
315        self.tkconsole = tkconsole
316        locals = sys.modules['__main__'].__dict__
317        InteractiveInterpreter.__init__(self, locals=locals)
318        self.save_warnings_filters = None
319        self.restarting = False
320        self.subprocess_arglist = self.build_subprocess_arglist()
321
322    port = 8833
323    rpcclt = None
324    rpcpid = None
325
326    def spawn_subprocess(self):
327        args = self.subprocess_arglist
328        self.rpcpid = os.spawnv(os.P_NOWAIT, sys.executable, args)
329
330    def build_subprocess_arglist(self):
331        w = ['-W' + s for s in sys.warnoptions]
332        # Maybe IDLE is installed and is being accessed via sys.path,
333        # or maybe it's not installed and the idle.py script is being
334        # run from the IDLE source directory.
335        del_exitf = idleConf.GetOption('main', 'General', 'delete-exitfunc',
336                                       default=False, type='bool')
337        if __name__ == 'idlelib.PyShell':
338            command = "__import__('idlelib.run').run.main(%r)" % (del_exitf,)
339        else:
340            command = "__import__('run').main(%r)" % (del_exitf,)
341        if sys.platform[:3] == 'win' and ' ' in sys.executable:
342            # handle embedded space in path by quoting the argument
343            decorated_exec = '"%s"' % sys.executable
344        else:
345            decorated_exec = sys.executable
346        return [decorated_exec] + w + ["-c", command, str(self.port)]
347
348    def start_subprocess(self):
349        # spawning first avoids passing a listening socket to the subprocess
350        self.spawn_subprocess()
351        #time.sleep(20) # test to simulate GUI not accepting connection
352        addr = (LOCALHOST, self.port)
353        # Idle starts listening for connection on localhost
354        for i in range(3):
355            time.sleep(i)
356            try:
357                self.rpcclt = MyRPCClient(addr)
358                break
359            except socket.error, err:
360                pass
361        else:
362            self.display_port_binding_error()
363            return None
364        # Accept the connection from the Python execution server
365        self.rpcclt.listening_sock.settimeout(10)
366        try:
367            self.rpcclt.accept()
368        except socket.timeout, err:
369            self.display_no_subprocess_error()
370            return None
371        self.rpcclt.register("stdin", self.tkconsole)
372        self.rpcclt.register("stdout", self.tkconsole.stdout)
373        self.rpcclt.register("stderr", self.tkconsole.stderr)
374        self.rpcclt.register("flist", self.tkconsole.flist)
375        self.rpcclt.register("linecache", linecache)
376        self.rpcclt.register("interp", self)
377        self.transfer_path()
378        self.poll_subprocess()
379        return self.rpcclt
380
381    def restart_subprocess(self):
382        if self.restarting:
383            return self.rpcclt
384        self.restarting = True
385        # close only the subprocess debugger
386        debug = self.getdebugger()
387        if debug:
388            try:
389                # Only close subprocess debugger, don't unregister gui_adap!
390                RemoteDebugger.close_subprocess_debugger(self.rpcclt)
391            except:
392                pass
393        # Kill subprocess, spawn a new one, accept connection.
394        self.rpcclt.close()
395        self.unix_terminate()
396        console = self.tkconsole
397        was_executing = console.executing
398        console.executing = False
399        self.spawn_subprocess()
400        try:
401            self.rpcclt.accept()
402        except socket.timeout, err:
403            self.display_no_subprocess_error()
404            return None
405        self.transfer_path()
406        # annotate restart in shell window and mark it
407        console.text.delete("iomark", "end-1c")
408        if was_executing:
409            console.write('\n')
410            console.showprompt()
411        halfbar = ((int(console.width) - 16) // 2) * '='
412        console.write(halfbar + ' RESTART ' + halfbar)
413        console.text.mark_set("restart", "end-1c")
414        console.text.mark_gravity("restart", "left")
415        console.showprompt()
416        # restart subprocess debugger
417        if debug:
418            # Restarted debugger connects to current instance of debug GUI
419            gui = RemoteDebugger.restart_subprocess_debugger(self.rpcclt)
420            # reload remote debugger breakpoints for all PyShellEditWindows
421            debug.load_breakpoints()
422        self.restarting = False
423        return self.rpcclt
424
425    def __request_interrupt(self):
426        self.rpcclt.remotecall("exec", "interrupt_the_server", (), {})
427
428    def interrupt_subprocess(self):
429        threading.Thread(target=self.__request_interrupt).start()
430
431    def kill_subprocess(self):
432        try:
433            self.rpcclt.close()
434        except AttributeError:  # no socket
435            pass
436        self.unix_terminate()
437        self.tkconsole.executing = False
438        self.rpcclt = None
439
440    def unix_terminate(self):
441        "UNIX: make sure subprocess is terminated and collect status"
442        if hasattr(os, 'kill'):
443            try:
444                os.kill(self.rpcpid, SIGTERM)
445            except OSError:
446                # process already terminated:
447                return
448            else:
449                try:
450                    os.waitpid(self.rpcpid, 0)
451                except OSError:
452                    return
453
454    def transfer_path(self):
455        self.runcommand("""if 1:
456        import sys as _sys
457        _sys.path = %r
458        del _sys
459        _msg = 'Use File/Exit or your end-of-file key to quit IDLE'
460        __builtins__.quit = __builtins__.exit = _msg
461        del _msg
462        \n""" % (sys.path,))
463
464    active_seq = None
465
466    def poll_subprocess(self):
467        clt = self.rpcclt
468        if clt is None:
469            return
470        try:
471            response = clt.pollresponse(self.active_seq, wait=0.05)
472        except (EOFError, IOError, KeyboardInterrupt):
473            # lost connection or subprocess terminated itself, restart
474            # [the KBI is from rpc.SocketIO.handle_EOF()]
475            if self.tkconsole.closing:
476                return
477            response = None
478            self.restart_subprocess()
479        if response:
480            self.tkconsole.resetoutput()
481            self.active_seq = None
482            how, what = response
483            console = self.tkconsole.console
484            if how == "OK":
485                if what is not None:
486                    print >>console, repr(what)
487            elif how == "EXCEPTION":
488                if self.tkconsole.getvar("<<toggle-jit-stack-viewer>>"):
489                    self.remote_stack_viewer()
490            elif how == "ERROR":
491                errmsg = "PyShell.ModifiedInterpreter: Subprocess ERROR:\n"
492                print >>sys.__stderr__, errmsg, what
493                print >>console, errmsg, what
494            # we received a response to the currently active seq number:
495            self.tkconsole.endexecuting()
496        # Reschedule myself
497        if not self.tkconsole.closing:
498            self.tkconsole.text.after(self.tkconsole.pollinterval,
499                                      self.poll_subprocess)
500
501    debugger = None
502
503    def setdebugger(self, debugger):
504        self.debugger = debugger
505
506    def getdebugger(self):
507        return self.debugger
508
509    def open_remote_stack_viewer(self):
510        """Initiate the remote stack viewer from a separate thread.
511
512        This method is called from the subprocess, and by returning from this
513        method we allow the subprocess to unblock.  After a bit the shell
514        requests the subprocess to open the remote stack viewer which returns a
515        static object looking at the last exceptiopn.  It is queried through
516        the RPC mechanism.
517
518        """
519        self.tkconsole.text.after(300, self.remote_stack_viewer)
520        return
521
522    def remote_stack_viewer(self):
523        import RemoteObjectBrowser
524        oid = self.rpcclt.remotequeue("exec", "stackviewer", ("flist",), {})
525        if oid is None:
526            self.tkconsole.root.bell()
527            return
528        item = RemoteObjectBrowser.StubObjectTreeItem(self.rpcclt, oid)
529        from TreeWidget import ScrolledCanvas, TreeNode
530        top = Toplevel(self.tkconsole.root)
531        theme = idleConf.GetOption('main','Theme','name')
532        background = idleConf.GetHighlight(theme, 'normal')['background']
533        sc = ScrolledCanvas(top, bg=background, highlightthickness=0)
534        sc.frame.pack(expand=1, fill="both")
535        node = TreeNode(sc.canvas, None, item)
536        node.expand()
537        # XXX Should GC the remote tree when closing the window
538
539    gid = 0
540
541    def execsource(self, source):
542        "Like runsource() but assumes complete exec source"
543        filename = self.stuffsource(source)
544        self.execfile(filename, source)
545
546    def execfile(self, filename, source=None):
547        "Execute an existing file"
548        if source is None:
549            source = open(filename, "r").read()
550        try:
551            code = compile(source, filename, "exec")
552        except (OverflowError, SyntaxError):
553            self.tkconsole.resetoutput()
554            tkerr = self.tkconsole.stderr
555            print>>tkerr, '*** Error in script or command!\n'
556            print>>tkerr, 'Traceback (most recent call last):'
557            InteractiveInterpreter.showsyntaxerror(self, filename)
558            self.tkconsole.showprompt()
559        else:
560            self.runcode(code)
561
562    def runsource(self, source):
563        "Extend base class method: Stuff the source in the line cache first"
564        filename = self.stuffsource(source)
565        self.more = 0
566        self.save_warnings_filters = warnings.filters[:]
567        warnings.filterwarnings(action="error", category=SyntaxWarning)
568        if isinstance(source, types.UnicodeType):
569            import IOBinding
570            try:
571                source = source.encode(IOBinding.encoding)
572            except UnicodeError:
573                self.tkconsole.resetoutput()
574                self.write("Unsupported characters in input")
575                return
576        try:
577            return InteractiveInterpreter.runsource(self, source, filename)
578        finally:
579            if self.save_warnings_filters is not None:
580                warnings.filters[:] = self.save_warnings_filters
581                self.save_warnings_filters = None
582
583    def stuffsource(self, source):
584        "Stuff source in the filename cache"
585        filename = "<pyshell#%d>" % self.gid
586        self.gid = self.gid + 1
587        lines = source.split("\n")
588        linecache.cache[filename] = len(source)+1, 0, lines, filename
589        return filename
590
591    def prepend_syspath(self, filename):
592        "Prepend sys.path with file's directory if not already included"
593        self.runcommand("""if 1:
594            _filename = %r
595            import sys as _sys
596            from os.path import dirname as _dirname
597            _dir = _dirname(_filename)
598            if not _dir in _sys.path:
599                _sys.path.insert(0, _dir)
600            del _filename, _sys, _dirname, _dir
601            \n""" % (filename,))
602
603    def showsyntaxerror(self, filename=None):
604        """Extend base class method: Add Colorizing
605
606        Color the offending position instead of printing it and pointing at it
607        with a caret.
608
609        """
610        text = self.tkconsole.text
611        stuff = self.unpackerror()
612        if stuff:
613            msg, lineno, offset, line = stuff
614            if lineno == 1:
615                pos = "iomark + %d chars" % (offset-1)
616            else:
617                pos = "iomark linestart + %d lines + %d chars" % \
618                      (lineno-1, offset-1)
619            text.tag_add("ERROR", pos)
620            text.see(pos)
621            char = text.get(pos)
622            if char and char in IDENTCHARS:
623                text.tag_add("ERROR", pos + " wordstart", pos)
624            self.tkconsole.resetoutput()
625            self.write("SyntaxError: %s\n" % str(msg))
626        else:
627            self.tkconsole.resetoutput()
628            InteractiveInterpreter.showsyntaxerror(self, filename)
629        self.tkconsole.showprompt()
630
631    def unpackerror(self):
632        type, value, tb = sys.exc_info()
633        ok = type is SyntaxError
634        if ok:
635            try:
636                msg, (dummy_filename, lineno, offset, line) = value
637                if not offset:
638                    offset = 0
639            except:
640                ok = 0
641        if ok:
642            return msg, lineno, offset, line
643        else:
644            return None
645
646    def showtraceback(self):
647        "Extend base class method to reset output properly"
648        self.tkconsole.resetoutput()
649        self.checklinecache()
650        InteractiveInterpreter.showtraceback(self)
651        if self.tkconsole.getvar("<<toggle-jit-stack-viewer>>"):
652            self.tkconsole.open_stack_viewer()
653
654    def checklinecache(self):
655        c = linecache.cache
656        for key in c.keys():
657            if key[:1] + key[-1:] != "<>":
658                del c[key]
659
660    def runcommand(self, code):
661        "Run the code without invoking the debugger"
662        # The code better not raise an exception!
663        if self.tkconsole.executing:
664            self.display_executing_dialog()
665            return 0
666        if self.rpcclt:
667            self.rpcclt.remotequeue("exec", "runcode", (code,), {})
668        else:
669            exec code in self.locals
670        return 1
671
672    def runcode(self, code):
673        "Override base class method"
674        if self.tkconsole.executing:
675            self.interp.restart_subprocess()
676        self.checklinecache()
677        if self.save_warnings_filters is not None:
678            warnings.filters[:] = self.save_warnings_filters
679            self.save_warnings_filters = None
680        debugger = self.debugger
681        try:
682            self.tkconsole.beginexecuting()
683            try:
684                if not debugger and self.rpcclt is not None:
685                    self.active_seq = self.rpcclt.asyncqueue("exec", "runcode",
686                                                            (code,), {})
687                elif debugger:
688                    debugger.run(code, self.locals)
689                else:
690                    exec code in self.locals
691            except SystemExit:
692                if tkMessageBox.askyesno(
693                    "Exit?",
694                    "Do you want to exit altogether?",
695                    default="yes",
696                    master=self.tkconsole.text):
697                    raise
698                else:
699                    self.showtraceback()
700            except:
701                self.showtraceback()
702        finally:
703            if not use_subprocess:
704                self.tkconsole.endexecuting()
705
706    def write(self, s):
707        "Override base class method"
708        self.tkconsole.stderr.write(s)
709
710    def display_port_binding_error(self):
711        tkMessageBox.showerror(
712            "Port Binding Error",
713            "IDLE can't bind TCP/IP port 8833, which is necessary to "
714            "communicate with its Python execution server.  Either "
715            "no networking is installed on this computer or another "
716            "process (another IDLE?) is using the port.  Run IDLE with the -n "
717            "command line switch to start without a subprocess and refer to "
718            "Help/IDLE Help 'Running without a subprocess' for further "
719            "details.",
720            master=self.tkconsole.text)
721
722    def display_no_subprocess_error(self):
723        tkMessageBox.showerror(
724            "Subprocess Startup Error",
725            "IDLE's subprocess didn't make connection.  Either IDLE can't "
726            "start a subprocess or personal firewall software is blocking "
727            "the connection.",
728            master=self.tkconsole.text)
729
730    def display_executing_dialog(self):
731        tkMessageBox.showerror(
732            "Already executing",
733            "The Python Shell window is already executing a command; "
734            "please wait until it is finished.",
735            master=self.tkconsole.text)
736
737
738class PyShell(OutputWindow):
739
740    shell_title = "Python Shell"
741
742    # Override classes
743    ColorDelegator = ModifiedColorDelegator
744    UndoDelegator = ModifiedUndoDelegator
745
746    # Override menus
747    menu_specs = [
748        ("file", "_File"),
749        ("edit", "_Edit"),
750        ("debug", "_Debug"),
751        ("options", "_Options"),
752        ("windows", "_Windows"),
753        ("help", "_Help"),
754    ]
755
756    # New classes
757    from IdleHistory import History
758
759    def __init__(self, flist=None):
760        if use_subprocess:
761            ms = self.menu_specs
762            if ms[2][0] != "shell":
763                ms.insert(2, ("shell", "_Shell"))
764        self.interp = ModifiedInterpreter(self)
765        if flist is None:
766            root = Tk()
767            fixwordbreaks(root)
768            root.withdraw()
769            flist = PyShellFileList(root)
770        #
771        OutputWindow.__init__(self, flist, None, None)
772        #
773        import __builtin__
774        __builtin__.quit = __builtin__.exit = "To exit, type Ctrl-D."
775        #
776        self.config(usetabs=1, indentwidth=8, context_use_ps1=1)
777        #
778        text = self.text
779        text.configure(wrap="char")
780        text.bind("<<newline-and-indent>>", self.enter_callback)
781        text.bind("<<plain-newline-and-indent>>", self.linefeed_callback)
782        text.bind("<<interrupt-execution>>", self.cancel_callback)
783        text.bind("<<beginning-of-line>>", self.home_callback)
784        text.bind("<<end-of-file>>", self.eof_callback)
785        text.bind("<<open-stack-viewer>>", self.open_stack_viewer)
786        text.bind("<<toggle-debugger>>", self.toggle_debugger)
787        text.bind("<<open-python-shell>>", self.flist.open_shell)
788        text.bind("<<toggle-jit-stack-viewer>>", self.toggle_jit_stack_viewer)
789        if use_subprocess:
790            text.bind("<<view-restart>>", self.view_restart_mark)
791            text.bind("<<restart-shell>>", self.restart_shell)
792        #
793        self.save_stdout = sys.stdout
794        self.save_stderr = sys.stderr
795        self.save_stdin = sys.stdin
796        import IOBinding
797        self.stdout = PseudoFile(self, "stdout", IOBinding.encoding)
798        self.stderr = PseudoFile(self, "stderr", IOBinding.encoding)
799        self.console = PseudoFile(self, "console", IOBinding.encoding)
800        if not use_subprocess:
801            sys.stdout = self.stdout
802            sys.stderr = self.stderr
803            sys.stdin = self
804        #
805        self.history = self.History(self.text)
806        #
807        self.pollinterval = 50  # millisec
808
809    reading = False
810    executing = False
811    canceled = False
812    endoffile = False
813    closing = False
814
815    def toggle_debugger(self, event=None):
816        if self.executing:
817            tkMessageBox.showerror("Don't debug now",
818                "You can only toggle the debugger when idle",
819                master=self.text)
820            self.set_debugger_indicator()
821            return "break"
822        else:
823            db = self.interp.getdebugger()
824            if db:
825                self.close_debugger()
826            else:
827                self.open_debugger()
828
829    def set_debugger_indicator(self):
830        db = self.interp.getdebugger()
831        self.setvar("<<toggle-debugger>>", not not db)
832
833    def toggle_jit_stack_viewer(self, event=None):
834        pass # All we need is the variable
835
836    def close_debugger(self):
837        db = self.interp.getdebugger()
838        if db:
839            self.interp.setdebugger(None)
840            db.close()
841            if self.interp.rpcclt:
842                RemoteDebugger.close_remote_debugger(self.interp.rpcclt)
843            self.resetoutput()
844            self.console.write("[DEBUG OFF]\n")
845            sys.ps1 = ">>> "
846            self.showprompt()
847        self.set_debugger_indicator()
848
849    def open_debugger(self):
850        if self.interp.rpcclt:
851            dbg_gui = RemoteDebugger.start_remote_debugger(self.interp.rpcclt,
852                                                           self)
853        else:
854            dbg_gui = Debugger.Debugger(self)
855        self.interp.setdebugger(dbg_gui)
856        dbg_gui.load_breakpoints()
857        sys.ps1 = "[DEBUG ON]\n>>> "
858        self.showprompt()
859        self.set_debugger_indicator()
860
861    def beginexecuting(self):
862        "Helper for ModifiedInterpreter"
863        self.resetoutput()
864        self.executing = 1
865
866    def endexecuting(self):
867        "Helper for ModifiedInterpreter"
868        self.executing = 0
869        self.canceled = 0
870        self.showprompt()
871
872    def close(self):
873        "Extend EditorWindow.close()"
874        if self.executing:
875            response = tkMessageBox.askokcancel(
876                "Kill?",
877                "The program is still running!\n Do you want to kill it?",
878                default="ok",
879                parent=self.text)
880            if response == False:
881                return "cancel"
882        self.closing = True
883        # Wait for poll_subprocess() rescheduling to stop
884        self.text.after(2 * self.pollinterval, self.close2)
885
886    def close2(self):
887        return EditorWindow.close(self)
888
889    def _close(self):
890        "Extend EditorWindow._close(), shut down debugger and execution server"
891        self.close_debugger()
892        if use_subprocess:
893            self.interp.kill_subprocess()
894        # Restore std streams
895        sys.stdout = self.save_stdout
896        sys.stderr = self.save_stderr
897        sys.stdin = self.save_stdin
898        # Break cycles
899        self.interp = None
900        self.console = None
901        self.flist.pyshell = None
902        self.history = None
903        EditorWindow._close(self)
904
905    def ispythonsource(self, filename):
906        "Override EditorWindow method: never remove the colorizer"
907        return True
908
909    def short_title(self):
910        return self.shell_title
911
912    COPYRIGHT = \
913          'Type "copyright", "credits" or "license()" for more information.'
914
915    firewallmessage = """
916    ****************************************************************
917    Personal firewall software may warn about the connection IDLE
918    makes to its subprocess using this computer's internal loopback
919    interface.  This connection is not visible on any external
920    interface and no data is sent to or received from the Internet.
921    ****************************************************************
922    """
923
924    def begin(self):
925        self.resetoutput()
926        if use_subprocess:
927            nosub = ''
928            client = self.interp.start_subprocess()
929            if not client:
930                self.close()
931                return False
932        else:
933            nosub = "==== No Subprocess ===="
934        self.write("Python %s on %s\n%s\n%s\nIDLE %s      %s\n" %
935                   (sys.version, sys.platform, self.COPYRIGHT,
936                    self.firewallmessage, idlever.IDLE_VERSION, nosub))
937        self.showprompt()
938        import Tkinter
939        Tkinter._default_root = None # 03Jan04 KBK What's this?
940        return True
941
942    def readline(self):
943        save = self.reading
944        try:
945            self.reading = 1
946            self.top.mainloop()
947        finally:
948            self.reading = save
949        line = self.text.get("iomark", "end-1c")
950        if isinstance(line, unicode):
951            import IOBinding
952            try:
953                line = line.encode(IOBinding.encoding)
954            except UnicodeError:
955                pass
956        self.resetoutput()
957        if self.canceled:
958            self.canceled = 0
959            raise KeyboardInterrupt
960        if self.endoffile:
961            self.endoffile = 0
962            return ""
963        return line
964
965    def isatty(self):
966        return True
967
968    def cancel_callback(self, event=None):
969        try:
970            if self.text.compare("sel.first", "!=", "sel.last"):
971                return # Active selection -- always use default binding
972        except:
973            pass
974        if not (self.executing or self.reading):
975            self.resetoutput()
976            self.interp.write("KeyboardInterrupt\n")
977            self.showprompt()
978            return "break"
979        self.endoffile = 0
980        self.canceled = 1
981        if self.reading:
982            self.top.quit()
983        elif (self.executing and self.interp.rpcclt):
984            if self.interp.getdebugger():
985                self.interp.restart_subprocess()
986            else:
987                self.interp.interrupt_subprocess()
988        return "break"
989
990    def eof_callback(self, event):
991        if self.executing and not self.reading:
992            return # Let the default binding (delete next char) take over
993        if not (self.text.compare("iomark", "==", "insert") and
994                self.text.compare("insert", "==", "end-1c")):
995            return # Let the default binding (delete next char) take over
996        if not self.executing:
997            self.resetoutput()
998            self.close()
999        else:
1000            self.canceled = 0
1001            self.endoffile = 1
1002            self.top.quit()
1003        return "break"
1004
1005    def home_callback(self, event):
1006        if event.state != 0 and event.keysym == "Home":
1007            return # <Modifier-Home>; fall back to class binding
1008        if self.text.compare("iomark", "<=", "insert") and \
1009           self.text.compare("insert linestart", "<=", "iomark"):
1010            self.text.mark_set("insert", "iomark")
1011            self.text.tag_remove("sel", "1.0", "end")
1012            self.text.see("insert")
1013            return "break"
1014
1015    def linefeed_callback(self, event):
1016        # Insert a linefeed without entering anything (still autoindented)
1017        if self.reading:
1018            self.text.insert("insert", "\n")
1019            self.text.see("insert")
1020        else:
1021            self.newline_and_indent_event(event)
1022        return "break"
1023
1024    def enter_callback(self, event):
1025        if self.executing and not self.reading:
1026            return # Let the default binding (insert '\n') take over
1027        # If some text is selected, recall the selection
1028        # (but only if this before the I/O mark)
1029        try:
1030            sel = self.text.get("sel.first", "sel.last")
1031            if sel:
1032                if self.text.compare("sel.last", "<=", "iomark"):
1033                    self.recall(sel)
1034                    return "break"
1035        except:
1036            pass
1037        # If we're strictly before the line containing iomark, recall
1038        # the current line, less a leading prompt, less leading or
1039        # trailing whitespace
1040        if self.text.compare("insert", "<", "iomark linestart"):
1041            # Check if there's a relevant stdin range -- if so, use it
1042            prev = self.text.tag_prevrange("stdin", "insert")
1043            if prev and self.text.compare("insert", "<", prev[1]):
1044                self.recall(self.text.get(prev[0], prev[1]))
1045                return "break"
1046            next = self.text.tag_nextrange("stdin", "insert")
1047            if next and self.text.compare("insert lineend", ">=", next[0]):
1048                self.recall(self.text.get(next[0], next[1]))
1049                return "break"
1050            # No stdin mark -- just get the current line, less any prompt
1051            line = self.text.get("insert linestart", "insert lineend")
1052            last_line_of_prompt = sys.ps1.split('\n')[-1]
1053            if line.startswith(last_line_of_prompt):
1054                line = line[len(last_line_of_prompt):]
1055            self.recall(line)
1056            return "break"
1057        # If we're between the beginning of the line and the iomark, i.e.
1058        # in the prompt area, move to the end of the prompt
1059        if self.text.compare("insert", "<", "iomark"):
1060            self.text.mark_set("insert", "iomark")
1061        # If we're in the current input and there's only whitespace
1062        # beyond the cursor, erase that whitespace first
1063        s = self.text.get("insert", "end-1c")
1064        if s and not s.strip():
1065            self.text.delete("insert", "end-1c")
1066        # If we're in the current input before its last line,
1067        # insert a newline right at the insert point
1068        if self.text.compare("insert", "<", "end-1c linestart"):
1069            self.newline_and_indent_event(event)
1070            return "break"
1071        # We're in the last line; append a newline and submit it
1072        self.text.mark_set("insert", "end-1c")
1073        if self.reading:
1074            self.text.insert("insert", "\n")
1075            self.text.see("insert")
1076        else:
1077            self.newline_and_indent_event(event)
1078        self.text.tag_add("stdin", "iomark", "end-1c")
1079        self.text.update_idletasks()
1080        if self.reading:
1081            self.top.quit() # Break out of recursive mainloop() in raw_input()
1082        else:
1083            self.runit()
1084        return "break"
1085
1086    def recall(self, s):
1087        if self.history:
1088            self.history.recall(s)
1089
1090    def runit(self):
1091        line = self.text.get("iomark", "end-1c")
1092        # Strip off last newline and surrounding whitespace.
1093        # (To allow you to hit return twice to end a statement.)
1094        i = len(line)
1095        while i > 0 and line[i-1] in " \t":
1096            i = i-1
1097        if i > 0 and line[i-1] == "\n":
1098            i = i-1
1099        while i > 0 and line[i-1] in " \t":
1100            i = i-1
1101        line = line[:i]
1102        more = self.interp.runsource(line)
1103
1104    def open_stack_viewer(self, event=None):
1105        if self.interp.rpcclt:
1106            return self.interp.remote_stack_viewer()
1107        try:
1108            sys.last_traceback
1109        except:
1110            tkMessageBox.showerror("No stack trace",
1111                "There is no stack trace yet.\n"
1112                "(sys.last_traceback is not defined)",
1113                master=self.text)
1114            return
1115        from StackViewer import StackBrowser
1116        sv = StackBrowser(self.root, self.flist)
1117
1118    def view_restart_mark(self, event=None):
1119        self.text.see("iomark")
1120        self.text.see("restart")
1121
1122    def restart_shell(self, event=None):
1123        self.interp.restart_subprocess()
1124
1125    def showprompt(self):
1126        self.resetoutput()
1127        try:
1128            s = str(sys.ps1)
1129        except:
1130            s = ""
1131        self.console.write(s)
1132        self.text.mark_set("insert", "end-1c")
1133        self.set_line_and_column()
1134        self.io.reset_undo()
1135
1136    def resetoutput(self):
1137        source = self.text.get("iomark", "end-1c")
1138        if self.history:
1139            self.history.history_store(source)
1140        if self.text.get("end-2c") != "\n":
1141            self.text.insert("end-1c", "\n")
1142        self.text.mark_set("iomark", "end-1c")
1143        self.set_line_and_column()
1144        sys.stdout.softspace = 0
1145
1146    def write(self, s, tags=()):
1147        try:
1148            self.text.mark_gravity("iomark", "right")
1149            OutputWindow.write(self, s, tags, "iomark")
1150            self.text.mark_gravity("iomark", "left")
1151        except:
1152            pass
1153        if self.canceled:
1154            self.canceled = 0
1155            if not use_subprocess:
1156                raise KeyboardInterrupt
1157
1158class PseudoFile:
1159
1160    def __init__(self, shell, tags, encoding=None):
1161        self.shell = shell
1162        self.tags = tags
1163        self.softspace = 0
1164        self.encoding = encoding
1165
1166    def write(self, s):
1167        self.shell.write(s, self.tags)
1168
1169    def writelines(self, l):
1170        map(self.write, l)
1171
1172    def flush(self):
1173        pass
1174
1175    def isatty(self):
1176        return True
1177
1178
1179usage_msg = """\
1180
1181USAGE: idle  [-deins] [-t title] [file]*
1182       idle  [-dns] [-t title] (-c cmd | -r file) [arg]*
1183       idle  [-dns] [-t title] - [arg]*
1184
1185  -h         print this help message and exit
1186  -n         run IDLE without a subprocess (see Help/IDLE Help for details)
1187
1188The following options will override the IDLE 'settings' configuration:
1189
1190  -e         open an edit window
1191  -i         open a shell window
1192
1193The following options imply -i and will open a shell:
1194
1195  -c cmd     run the command in a shell, or
1196  -r file    run script from file
1197
1198  -d         enable the debugger
1199  -s         run $IDLESTARTUP or $PYTHONSTARTUP before anything else
1200  -t title   set title of shell window
1201
1202A default edit window will be bypassed when -c, -r, or - are used.
1203
1204[arg]* are passed to the command (-c) or script (-r) in sys.argv[1:].
1205
1206Examples:
1207
1208idle
1209        Open an edit window or shell depending on IDLE's configuration.
1210
1211idle foo.py foobar.py
1212        Edit the files, also open a shell if configured to start with shell.
1213
1214idle -est "Baz" foo.py
1215        Run $IDLESTARTUP or $PYTHONSTARTUP, edit foo.py, and open a shell
1216        window with the title "Baz".
1217
1218idle -c "import sys; print sys.argv" "foo"
1219        Open a shell window and run the command, passing "-c" in sys.argv[0]
1220        and "foo" in sys.argv[1].
1221
1222idle -d -s -r foo.py "Hello World"
1223        Open a shell window, run a startup script, enable the debugger, and
1224        run foo.py, passing "foo.py" in sys.argv[0] and "Hello World" in
1225        sys.argv[1].
1226
1227echo "import sys; print sys.argv" | idle - "foobar"
1228        Open a shell window, run the script piped in, passing '' in sys.argv[0]
1229        and "foobar" in sys.argv[1].
1230"""
1231
1232def main():
1233    global flist, root, use_subprocess
1234
1235    use_subprocess = True
1236    enable_shell = False
1237    enable_edit = False
1238    debug = False
1239    cmd = None
1240    script = None
1241    startup = False
1242    try:
1243        sys.ps1
1244    except AttributeError:
1245        sys.ps1 = '>>> '
1246    try:
1247        opts, args = getopt.getopt(sys.argv[1:], "c:deihnr:st:")
1248    except getopt.error, msg:
1249        sys.stderr.write("Error: %s\n" % str(msg))
1250        sys.stderr.write(usage_msg)
1251        sys.exit(2)
1252    for o, a in opts:
1253        if o == '-c':
1254            cmd = a
1255            enable_shell = True
1256        if o == '-d':
1257            debug = True
1258            enable_shell = True
1259        if o == '-e':
1260            enable_edit = True
1261        if o == '-h':
1262            sys.stdout.write(usage_msg)
1263            sys.exit()
1264        if o == '-i':
1265            enable_shell = True
1266        if o == '-n':
1267            use_subprocess = False
1268        if o == '-r':
1269            script = a
1270            if os.path.isfile(script):
1271                pass
1272            else:
1273                print "No script file: ", script
1274                sys.exit()
1275            enable_shell = True
1276        if o == '-s':
1277            startup = True
1278            enable_shell = True
1279        if o == '-t':
1280            PyShell.shell_title = a
1281            enable_shell = True
1282    if args and args[0] == '-':
1283        cmd = sys.stdin.read()
1284        enable_shell = True
1285    # process sys.argv and sys.path:
1286    for i in range(len(sys.path)):
1287        sys.path[i] = os.path.abspath(sys.path[i])
1288    if args and args[0] == '-':
1289        sys.argv = [''] + args[1:]
1290    elif cmd:
1291        sys.argv = ['-c'] + args
1292    elif script:
1293        sys.argv = [script] + args
1294    elif args:
1295        enable_edit = True
1296        pathx = []
1297        for filename in args:
1298            pathx.append(os.path.dirname(filename))
1299        for dir in pathx:
1300            dir = os.path.abspath(dir)
1301            if not dir in sys.path:
1302                sys.path.insert(0, dir)
1303    else:
1304        dir = os.getcwd()
1305        if not dir in sys.path:
1306            sys.path.insert(0, dir)
1307    # check the IDLE settings configuration (but command line overrides)
1308    edit_start = idleConf.GetOption('main', 'General',
1309                                    'editor-on-startup', type='bool')
1310    enable_edit = enable_edit or edit_start
1311    enable_shell = enable_shell or not edit_start
1312    # start editor and/or shell windows:
1313    root = Tk(className="Idle")
1314    fixwordbreaks(root)
1315    root.withdraw()
1316    flist = PyShellFileList(root)
1317    if enable_edit:
1318        if not (cmd or script):
1319            for filename in args:
1320                flist.open(filename)
1321            if not args:
1322                flist.new()
1323    if enable_shell:
1324        if not flist.open_shell():
1325            return # couldn't open shell
1326    shell = flist.pyshell
1327    # handle remaining options:
1328    if debug:
1329        shell.open_debugger()
1330    if startup:
1331        filename = os.environ.get("IDLESTARTUP") or \
1332                   os.environ.get("PYTHONSTARTUP")
1333        if filename and os.path.isfile(filename):
1334            shell.interp.execfile(filename)
1335    if shell and cmd or script:
1336        shell.interp.runcommand("""if 1:
1337            import sys as _sys
1338            _sys.argv = %r
1339            del _sys
1340            \n""" % (sys.argv,))
1341        if cmd:
1342            shell.interp.execsource(cmd)
1343        elif script:
1344            shell.interp.prepend_syspath(script)
1345            shell.interp.execfile(script)
1346    root.mainloop()
1347    root.destroy()
1348
1349if __name__ == "__main__":
1350    sys.modules['PyShell'] = sys.modules['__main__']
1351    main()
1352