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