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