PyShell.py revision d076153ee8a9e4e2e7ecd3ff77fdb3b7aaf7ef77
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 macosxSupport
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        if 1/2 > 0: # account for new division
355            w.append('-Qnew')
356        # Maybe IDLE is installed and is being accessed via sys.path,
357        # or maybe it's not installed and the idle.py script is being
358        # run from the IDLE source directory.
359        del_exitf = idleConf.GetOption('main', 'General', 'delete-exitfunc',
360                                       default=False, type='bool')
361        if __name__ == 'idlelib.PyShell':
362            command = "__import__('idlelib.run').run.main(%r)" % (del_exitf,)
363        else:
364            command = "__import__('run').main(%r)" % (del_exitf,)
365        if sys.platform[:3] == 'win' and ' ' in sys.executable:
366            # handle embedded space in path by quoting the argument
367            decorated_exec = '"%s"' % sys.executable
368        else:
369            decorated_exec = sys.executable
370        return [decorated_exec] + w + ["-c", command, str(self.port)]
371
372    def start_subprocess(self):
373        # spawning first avoids passing a listening socket to the subprocess
374        self.spawn_subprocess()
375        #time.sleep(20) # test to simulate GUI not accepting connection
376        addr = (LOCALHOST, self.port)
377        # Idle starts listening for connection on localhost
378        for i in range(3):
379            time.sleep(i)
380            try:
381                self.rpcclt = MyRPCClient(addr)
382                break
383            except socket.error, err:
384                pass
385        else:
386            self.display_port_binding_error()
387            return None
388        # Accept the connection from the Python execution server
389        self.rpcclt.listening_sock.settimeout(10)
390        try:
391            self.rpcclt.accept()
392        except socket.timeout, err:
393            self.display_no_subprocess_error()
394            return None
395        self.rpcclt.register("stdin", self.tkconsole)
396        self.rpcclt.register("stdout", self.tkconsole.stdout)
397        self.rpcclt.register("stderr", self.tkconsole.stderr)
398        self.rpcclt.register("flist", self.tkconsole.flist)
399        self.rpcclt.register("linecache", linecache)
400        self.rpcclt.register("interp", self)
401        self.transfer_path()
402        self.poll_subprocess()
403        return self.rpcclt
404
405    def restart_subprocess(self):
406        if self.restarting:
407            return self.rpcclt
408        self.restarting = True
409        # close only the subprocess debugger
410        debug = self.getdebugger()
411        if debug:
412            try:
413                # Only close subprocess debugger, don't unregister gui_adap!
414                RemoteDebugger.close_subprocess_debugger(self.rpcclt)
415            except:
416                pass
417        # Kill subprocess, spawn a new one, accept connection.
418        self.rpcclt.close()
419        self.unix_terminate()
420        console = self.tkconsole
421        was_executing = console.executing
422        console.executing = False
423        self.spawn_subprocess()
424        try:
425            self.rpcclt.accept()
426        except socket.timeout, err:
427            self.display_no_subprocess_error()
428            return None
429        self.transfer_path()
430        # annotate restart in shell window and mark it
431        console.text.delete("iomark", "end-1c")
432        if was_executing:
433            console.write('\n')
434            console.showprompt()
435        halfbar = ((int(console.width) - 16) // 2) * '='
436        console.write(halfbar + ' RESTART ' + halfbar)
437        console.text.mark_set("restart", "end-1c")
438        console.text.mark_gravity("restart", "left")
439        console.showprompt()
440        # restart subprocess debugger
441        if debug:
442            # Restarted debugger connects to current instance of debug GUI
443            gui = RemoteDebugger.restart_subprocess_debugger(self.rpcclt)
444            # reload remote debugger breakpoints for all PyShellEditWindows
445            debug.load_breakpoints()
446        self.restarting = False
447        return self.rpcclt
448
449    def __request_interrupt(self):
450        self.rpcclt.remotecall("exec", "interrupt_the_server", (), {})
451
452    def interrupt_subprocess(self):
453        threading.Thread(target=self.__request_interrupt).start()
454
455    def kill_subprocess(self):
456        try:
457            self.rpcclt.close()
458        except AttributeError:  # no socket
459            pass
460        self.unix_terminate()
461        self.tkconsole.executing = False
462        self.rpcclt = None
463
464    def unix_terminate(self):
465        "UNIX: make sure subprocess is terminated and collect status"
466        if hasattr(os, 'kill'):
467            try:
468                os.kill(self.rpcpid, SIGTERM)
469            except OSError:
470                # process already terminated:
471                return
472            else:
473                try:
474                    os.waitpid(self.rpcpid, 0)
475                except OSError:
476                    return
477
478    def transfer_path(self):
479        self.runcommand("""if 1:
480        import sys as _sys
481        _sys.path = %r
482        del _sys
483        \n""" % (sys.path,))
484
485    active_seq = None
486
487    def poll_subprocess(self):
488        clt = self.rpcclt
489        if clt is None:
490            return
491        try:
492            response = clt.pollresponse(self.active_seq, wait=0.05)
493        except (EOFError, IOError, KeyboardInterrupt):
494            # lost connection or subprocess terminated itself, restart
495            # [the KBI is from rpc.SocketIO.handle_EOF()]
496            if self.tkconsole.closing:
497                return
498            response = None
499            self.restart_subprocess()
500        if response:
501            self.tkconsole.resetoutput()
502            self.active_seq = None
503            how, what = response
504            console = self.tkconsole.console
505            if how == "OK":
506                if what is not None:
507                    print >>console, repr(what)
508            elif how == "EXCEPTION":
509                if self.tkconsole.getvar("<<toggle-jit-stack-viewer>>"):
510                    self.remote_stack_viewer()
511            elif how == "ERROR":
512                errmsg = "PyShell.ModifiedInterpreter: Subprocess ERROR:\n"
513                print >>sys.__stderr__, errmsg, what
514                print >>console, errmsg, what
515            # we received a response to the currently active seq number:
516            try:
517                self.tkconsole.endexecuting()
518            except AttributeError:  # shell may have closed
519                pass
520        # Reschedule myself
521        if not self.tkconsole.closing:
522            self.tkconsole.text.after(self.tkconsole.pollinterval,
523                                      self.poll_subprocess)
524
525    debugger = None
526
527    def setdebugger(self, debugger):
528        self.debugger = debugger
529
530    def getdebugger(self):
531        return self.debugger
532
533    def open_remote_stack_viewer(self):
534        """Initiate the remote stack viewer from a separate thread.
535
536        This method is called from the subprocess, and by returning from this
537        method we allow the subprocess to unblock.  After a bit the shell
538        requests the subprocess to open the remote stack viewer which returns a
539        static object looking at the last exceptiopn.  It is queried through
540        the RPC mechanism.
541
542        """
543        self.tkconsole.text.after(300, self.remote_stack_viewer)
544        return
545
546    def remote_stack_viewer(self):
547        import RemoteObjectBrowser
548        oid = self.rpcclt.remotequeue("exec", "stackviewer", ("flist",), {})
549        if oid is None:
550            self.tkconsole.root.bell()
551            return
552        item = RemoteObjectBrowser.StubObjectTreeItem(self.rpcclt, oid)
553        from TreeWidget import ScrolledCanvas, TreeNode
554        top = Toplevel(self.tkconsole.root)
555        theme = idleConf.GetOption('main','Theme','name')
556        background = idleConf.GetHighlight(theme, 'normal')['background']
557        sc = ScrolledCanvas(top, bg=background, highlightthickness=0)
558        sc.frame.pack(expand=1, fill="both")
559        node = TreeNode(sc.canvas, None, item)
560        node.expand()
561        # XXX Should GC the remote tree when closing the window
562
563    gid = 0
564
565    def execsource(self, source):
566        "Like runsource() but assumes complete exec source"
567        filename = self.stuffsource(source)
568        self.execfile(filename, source)
569
570    def execfile(self, filename, source=None):
571        "Execute an existing file"
572        if source is None:
573            source = open(filename, "r").read()
574        try:
575            code = compile(source, filename, "exec")
576        except (OverflowError, SyntaxError):
577            self.tkconsole.resetoutput()
578            tkerr = self.tkconsole.stderr
579            print>>tkerr, '*** Error in script or command!\n'
580            print>>tkerr, 'Traceback (most recent call last):'
581            InteractiveInterpreter.showsyntaxerror(self, filename)
582            self.tkconsole.showprompt()
583        else:
584            self.runcode(code)
585
586    def runsource(self, source):
587        "Extend base class method: Stuff the source in the line cache first"
588        filename = self.stuffsource(source)
589        self.more = 0
590        self.save_warnings_filters = warnings.filters[:]
591        warnings.filterwarnings(action="error", category=SyntaxWarning)
592        if isinstance(source, types.UnicodeType):
593            import IOBinding
594            try:
595                source = source.encode(IOBinding.encoding)
596            except UnicodeError:
597                self.tkconsole.resetoutput()
598                self.write("Unsupported characters in input\n")
599                return
600        try:
601            # InteractiveInterpreter.runsource() calls its runcode() method,
602            # which is overridden (see below)
603            return InteractiveInterpreter.runsource(self, source, filename)
604        finally:
605            if self.save_warnings_filters is not None:
606                warnings.filters[:] = self.save_warnings_filters
607                self.save_warnings_filters = None
608
609    def stuffsource(self, source):
610        "Stuff source in the filename cache"
611        filename = "<pyshell#%d>" % self.gid
612        self.gid = self.gid + 1
613        lines = source.split("\n")
614        linecache.cache[filename] = len(source)+1, 0, lines, filename
615        return filename
616
617    def prepend_syspath(self, filename):
618        "Prepend sys.path with file's directory if not already included"
619        self.runcommand("""if 1:
620            _filename = %r
621            import sys as _sys
622            from os.path import dirname as _dirname
623            _dir = _dirname(_filename)
624            if not _dir in _sys.path:
625                _sys.path.insert(0, _dir)
626            del _filename, _sys, _dirname, _dir
627            \n""" % (filename,))
628
629    def showsyntaxerror(self, filename=None):
630        """Extend base class method: Add Colorizing
631
632        Color the offending position instead of printing it and pointing at it
633        with a caret.
634
635        """
636        text = self.tkconsole.text
637        stuff = self.unpackerror()
638        if stuff:
639            msg, lineno, offset, line = stuff
640            if lineno == 1:
641                pos = "iomark + %d chars" % (offset-1)
642            else:
643                pos = "iomark linestart + %d lines + %d chars" % \
644                      (lineno-1, offset-1)
645            text.tag_add("ERROR", pos)
646            text.see(pos)
647            char = text.get(pos)
648            if char and char in IDENTCHARS:
649                text.tag_add("ERROR", pos + " wordstart", pos)
650            self.tkconsole.resetoutput()
651            self.write("SyntaxError: %s\n" % str(msg))
652        else:
653            self.tkconsole.resetoutput()
654            InteractiveInterpreter.showsyntaxerror(self, filename)
655        self.tkconsole.showprompt()
656
657    def unpackerror(self):
658        type, value, tb = sys.exc_info()
659        ok = type is SyntaxError
660        if ok:
661            try:
662                msg, (dummy_filename, lineno, offset, line) = value
663                if not offset:
664                    offset = 0
665            except:
666                ok = 0
667        if ok:
668            return msg, lineno, offset, line
669        else:
670            return None
671
672    def showtraceback(self):
673        "Extend base class method to reset output properly"
674        self.tkconsole.resetoutput()
675        self.checklinecache()
676        InteractiveInterpreter.showtraceback(self)
677        if self.tkconsole.getvar("<<toggle-jit-stack-viewer>>"):
678            self.tkconsole.open_stack_viewer()
679
680    def checklinecache(self):
681        c = linecache.cache
682        for key in c.keys():
683            if key[:1] + key[-1:] != "<>":
684                del c[key]
685
686    def runcommand(self, code):
687        "Run the code without invoking the debugger"
688        # The code better not raise an exception!
689        if self.tkconsole.executing:
690            self.display_executing_dialog()
691            return 0
692        if self.rpcclt:
693            self.rpcclt.remotequeue("exec", "runcode", (code,), {})
694        else:
695            exec code in self.locals
696        return 1
697
698    def runcode(self, code):
699        "Override base class method"
700        if self.tkconsole.executing:
701            self.interp.restart_subprocess()
702        self.checklinecache()
703        if self.save_warnings_filters is not None:
704            warnings.filters[:] = self.save_warnings_filters
705            self.save_warnings_filters = None
706        debugger = self.debugger
707        try:
708            self.tkconsole.beginexecuting()
709            try:
710                if not debugger and self.rpcclt is not None:
711                    self.active_seq = self.rpcclt.asyncqueue("exec", "runcode",
712                                                            (code,), {})
713                elif debugger:
714                    debugger.run(code, self.locals)
715                else:
716                    exec code in self.locals
717            except SystemExit:
718                if not self.tkconsole.closing:
719                    if tkMessageBox.askyesno(
720                        "Exit?",
721                        "Do you want to exit altogether?",
722                        default="yes",
723                        master=self.tkconsole.text):
724                        raise
725                    else:
726                        self.showtraceback()
727                else:
728                    raise
729            except:
730                if use_subprocess:
731                    # When run w/o subprocess, both user and IDLE errors
732                    # are printed here; skip message in that case.
733                    print >> self.tkconsole.stderr, \
734                             "IDLE internal error in runcode()"
735                self.showtraceback()
736                if use_subprocess:
737                    self.tkconsole.endexecuting()
738        finally:
739            if not use_subprocess:
740                try:
741                    self.tkconsole.endexecuting()
742                except AttributeError:  # shell may have closed
743                    pass
744
745    def write(self, s):
746        "Override base class method"
747        self.tkconsole.stderr.write(s)
748
749    def display_port_binding_error(self):
750        tkMessageBox.showerror(
751            "Port Binding Error",
752            "IDLE can't bind TCP/IP port 8833, which is necessary to "
753            "communicate with its Python execution server.  Either "
754            "no networking is installed on this computer or another "
755            "process (another IDLE?) is using the port.  Run IDLE with the -n "
756            "command line switch to start without a subprocess and refer to "
757            "Help/IDLE Help 'Running without a subprocess' for further "
758            "details.",
759            master=self.tkconsole.text)
760
761    def display_no_subprocess_error(self):
762        tkMessageBox.showerror(
763            "Subprocess Startup Error",
764            "IDLE's subprocess didn't make connection.  Either IDLE can't "
765            "start a subprocess or personal firewall software is blocking "
766            "the connection.",
767            master=self.tkconsole.text)
768
769    def display_executing_dialog(self):
770        tkMessageBox.showerror(
771            "Already executing",
772            "The Python Shell window is already executing a command; "
773            "please wait until it is finished.",
774            master=self.tkconsole.text)
775
776
777class PyShell(OutputWindow):
778
779    shell_title = "Python Shell"
780
781    # Override classes
782    ColorDelegator = ModifiedColorDelegator
783    UndoDelegator = ModifiedUndoDelegator
784
785    # Override menus
786    menu_specs = [
787        ("file", "_File"),
788        ("edit", "_Edit"),
789        ("debug", "_Debug"),
790        ("options", "_Options"),
791        ("windows", "_Windows"),
792        ("help", "_Help"),
793    ]
794
795    if macosxSupport.runningAsOSXApp():
796        del menu_specs[-3]
797        menu_specs[-2] = ("windows", "_Window")
798
799
800    # New classes
801    from IdleHistory import History
802
803    def __init__(self, flist=None):
804        if use_subprocess:
805            ms = self.menu_specs
806            if ms[2][0] != "shell":
807                ms.insert(2, ("shell", "She_ll"))
808        self.interp = ModifiedInterpreter(self)
809        if flist is None:
810            root = Tk()
811            fixwordbreaks(root)
812            root.withdraw()
813            flist = PyShellFileList(root)
814        #
815        OutputWindow.__init__(self, flist, None, None)
816        #
817##        self.config(usetabs=1, indentwidth=8, context_use_ps1=1)
818        self.usetabs = True
819        # indentwidth must be 8 when using tabs.  See note in EditorWindow:
820        self.indentwidth = 8
821        self.context_use_ps1 = True
822        #
823        text = self.text
824        text.configure(wrap="char")
825        text.bind("<<newline-and-indent>>", self.enter_callback)
826        text.bind("<<plain-newline-and-indent>>", self.linefeed_callback)
827        text.bind("<<interrupt-execution>>", self.cancel_callback)
828        text.bind("<<beginning-of-line>>", self.home_callback)
829        text.bind("<<end-of-file>>", self.eof_callback)
830        text.bind("<<open-stack-viewer>>", self.open_stack_viewer)
831        text.bind("<<toggle-debugger>>", self.toggle_debugger)
832        text.bind("<<toggle-jit-stack-viewer>>", self.toggle_jit_stack_viewer)
833        if use_subprocess:
834            text.bind("<<view-restart>>", self.view_restart_mark)
835            text.bind("<<restart-shell>>", self.restart_shell)
836        #
837        self.save_stdout = sys.stdout
838        self.save_stderr = sys.stderr
839        self.save_stdin = sys.stdin
840        import IOBinding
841        self.stdout = PseudoFile(self, "stdout", IOBinding.encoding)
842        self.stderr = PseudoFile(self, "stderr", IOBinding.encoding)
843        self.console = PseudoFile(self, "console", IOBinding.encoding)
844        if not use_subprocess:
845            sys.stdout = self.stdout
846            sys.stderr = self.stderr
847            sys.stdin = self
848        #
849        self.history = self.History(self.text)
850        #
851        self.pollinterval = 50  # millisec
852
853    def get_standard_extension_names(self):
854        return idleConf.GetExtensions(shell_only=True)
855
856    reading = False
857    executing = False
858    canceled = False
859    endoffile = False
860    closing = False
861
862    def set_warning_stream(self, stream):
863        global warning_stream
864        warning_stream = stream
865
866    def get_warning_stream(self):
867        return warning_stream
868
869    def toggle_debugger(self, event=None):
870        if self.executing:
871            tkMessageBox.showerror("Don't debug now",
872                "You can only toggle the debugger when idle",
873                master=self.text)
874            self.set_debugger_indicator()
875            return "break"
876        else:
877            db = self.interp.getdebugger()
878            if db:
879                self.close_debugger()
880            else:
881                self.open_debugger()
882
883    def set_debugger_indicator(self):
884        db = self.interp.getdebugger()
885        self.setvar("<<toggle-debugger>>", not not db)
886
887    def toggle_jit_stack_viewer(self, event=None):
888        pass # All we need is the variable
889
890    def close_debugger(self):
891        db = self.interp.getdebugger()
892        if db:
893            self.interp.setdebugger(None)
894            db.close()
895            if self.interp.rpcclt:
896                RemoteDebugger.close_remote_debugger(self.interp.rpcclt)
897            self.resetoutput()
898            self.console.write("[DEBUG OFF]\n")
899            sys.ps1 = ">>> "
900            self.showprompt()
901        self.set_debugger_indicator()
902
903    def open_debugger(self):
904        if self.interp.rpcclt:
905            dbg_gui = RemoteDebugger.start_remote_debugger(self.interp.rpcclt,
906                                                           self)
907        else:
908            dbg_gui = Debugger.Debugger(self)
909        self.interp.setdebugger(dbg_gui)
910        dbg_gui.load_breakpoints()
911        sys.ps1 = "[DEBUG ON]\n>>> "
912        self.showprompt()
913        self.set_debugger_indicator()
914
915    def beginexecuting(self):
916        "Helper for ModifiedInterpreter"
917        self.resetoutput()
918        self.executing = 1
919
920    def endexecuting(self):
921        "Helper for ModifiedInterpreter"
922        self.executing = 0
923        self.canceled = 0
924        self.showprompt()
925
926    def close(self):
927        "Extend EditorWindow.close()"
928        if self.executing:
929            response = tkMessageBox.askokcancel(
930                "Kill?",
931                "The program is still running!\n Do you want to kill it?",
932                default="ok",
933                parent=self.text)
934            if response == False:
935                return "cancel"
936        if self.reading:
937            self.top.quit()
938        self.canceled = True
939        self.closing = True
940        # Wait for poll_subprocess() rescheduling to stop
941        self.text.after(2 * self.pollinterval, self.close2)
942
943    def close2(self):
944        return EditorWindow.close(self)
945
946    def _close(self):
947        "Extend EditorWindow._close(), shut down debugger and execution server"
948        self.close_debugger()
949        if use_subprocess:
950            self.interp.kill_subprocess()
951        # Restore std streams
952        sys.stdout = self.save_stdout
953        sys.stderr = self.save_stderr
954        sys.stdin = self.save_stdin
955        # Break cycles
956        self.interp = None
957        self.console = None
958        self.flist.pyshell = None
959        self.history = None
960        EditorWindow._close(self)
961
962    def ispythonsource(self, filename):
963        "Override EditorWindow method: never remove the colorizer"
964        return True
965
966    def short_title(self):
967        return self.shell_title
968
969    COPYRIGHT = \
970          'Type "copyright", "credits" or "license()" for more information.'
971
972    firewallmessage = """
973    ****************************************************************
974    Personal firewall software may warn about the connection IDLE
975    makes to its subprocess using this computer's internal loopback
976    interface.  This connection is not visible on any external
977    interface and no data is sent to or received from the Internet.
978    ****************************************************************
979    """
980
981    def begin(self):
982        self.resetoutput()
983        if use_subprocess:
984            nosub = ''
985            client = self.interp.start_subprocess()
986            if not client:
987                self.close()
988                return False
989        else:
990            nosub = "==== No Subprocess ===="
991        self.write("Python %s on %s\n%s\n%s\nIDLE %s      %s\n" %
992                   (sys.version, sys.platform, self.COPYRIGHT,
993                    self.firewallmessage, idlever.IDLE_VERSION, nosub))
994        self.showprompt()
995        import Tkinter
996        Tkinter._default_root = None # 03Jan04 KBK What's this?
997        return True
998
999    def readline(self):
1000        save = self.reading
1001        try:
1002            self.reading = 1
1003            self.top.mainloop()  # nested mainloop()
1004        finally:
1005            self.reading = save
1006        line = self.text.get("iomark", "end-1c")
1007        if len(line) == 0:  # may be EOF if we quit our mainloop with Ctrl-C
1008            line = "\n"
1009        if isinstance(line, unicode):
1010            import IOBinding
1011            try:
1012                line = line.encode(IOBinding.encoding)
1013            except UnicodeError:
1014                pass
1015        self.resetoutput()
1016        if self.canceled:
1017            self.canceled = 0
1018            if not use_subprocess:
1019                raise KeyboardInterrupt
1020        if self.endoffile:
1021            self.endoffile = 0
1022            line = ""
1023        return line
1024
1025    def isatty(self):
1026        return True
1027
1028    def cancel_callback(self, event=None):
1029        try:
1030            if self.text.compare("sel.first", "!=", "sel.last"):
1031                return # Active selection -- always use default binding
1032        except:
1033            pass
1034        if not (self.executing or self.reading):
1035            self.resetoutput()
1036            self.interp.write("KeyboardInterrupt\n")
1037            self.showprompt()
1038            return "break"
1039        self.endoffile = 0
1040        self.canceled = 1
1041        if (self.executing and self.interp.rpcclt):
1042            if self.interp.getdebugger():
1043                self.interp.restart_subprocess()
1044            else:
1045                self.interp.interrupt_subprocess()
1046        if self.reading:
1047            self.top.quit()  # exit the nested mainloop() in readline()
1048        return "break"
1049
1050    def eof_callback(self, event):
1051        if self.executing and not self.reading:
1052            return # Let the default binding (delete next char) take over
1053        if not (self.text.compare("iomark", "==", "insert") and
1054                self.text.compare("insert", "==", "end-1c")):
1055            return # Let the default binding (delete next char) take over
1056        if not self.executing:
1057            self.resetoutput()
1058            self.close()
1059        else:
1060            self.canceled = 0
1061            self.endoffile = 1
1062            self.top.quit()
1063        return "break"
1064
1065    def home_callback(self, event):
1066        if event.state != 0 and event.keysym == "Home":
1067            return # <Modifier-Home>; fall back to class binding
1068        if self.text.compare("iomark", "<=", "insert") and \
1069           self.text.compare("insert linestart", "<=", "iomark"):
1070            self.text.mark_set("insert", "iomark")
1071            self.text.tag_remove("sel", "1.0", "end")
1072            self.text.see("insert")
1073            return "break"
1074
1075    def linefeed_callback(self, event):
1076        # Insert a linefeed without entering anything (still autoindented)
1077        if self.reading:
1078            self.text.insert("insert", "\n")
1079            self.text.see("insert")
1080        else:
1081            self.newline_and_indent_event(event)
1082        return "break"
1083
1084    def enter_callback(self, event):
1085        if self.executing and not self.reading:
1086            return # Let the default binding (insert '\n') take over
1087        # If some text is selected, recall the selection
1088        # (but only if this before the I/O mark)
1089        try:
1090            sel = self.text.get("sel.first", "sel.last")
1091            if sel:
1092                if self.text.compare("sel.last", "<=", "iomark"):
1093                    self.recall(sel, event)
1094                    return "break"
1095        except:
1096            pass
1097        # If we're strictly before the line containing iomark, recall
1098        # the current line, less a leading prompt, less leading or
1099        # trailing whitespace
1100        if self.text.compare("insert", "<", "iomark linestart"):
1101            # Check if there's a relevant stdin range -- if so, use it
1102            prev = self.text.tag_prevrange("stdin", "insert")
1103            if prev and self.text.compare("insert", "<", prev[1]):
1104                self.recall(self.text.get(prev[0], prev[1]), event)
1105                return "break"
1106            next = self.text.tag_nextrange("stdin", "insert")
1107            if next and self.text.compare("insert lineend", ">=", next[0]):
1108                self.recall(self.text.get(next[0], next[1]), event)
1109                return "break"
1110            # No stdin mark -- just get the current line, less any prompt
1111            indices = self.text.tag_nextrange("console", "insert linestart")
1112            if indices and \
1113               self.text.compare(indices[0], "<=", "insert linestart"):
1114                self.recall(self.text.get(indices[1], "insert lineend"), event)
1115            else:
1116                self.recall(self.text.get("insert linestart", "insert lineend"), event)
1117            return "break"
1118        # If we're between the beginning of the line and the iomark, i.e.
1119        # in the prompt area, move to the end of the prompt
1120        if self.text.compare("insert", "<", "iomark"):
1121            self.text.mark_set("insert", "iomark")
1122        # If we're in the current input and there's only whitespace
1123        # beyond the cursor, erase that whitespace first
1124        s = self.text.get("insert", "end-1c")
1125        if s and not s.strip():
1126            self.text.delete("insert", "end-1c")
1127        # If we're in the current input before its last line,
1128        # insert a newline right at the insert point
1129        if self.text.compare("insert", "<", "end-1c linestart"):
1130            self.newline_and_indent_event(event)
1131            return "break"
1132        # We're in the last line; append a newline and submit it
1133        self.text.mark_set("insert", "end-1c")
1134        if self.reading:
1135            self.text.insert("insert", "\n")
1136            self.text.see("insert")
1137        else:
1138            self.newline_and_indent_event(event)
1139        self.text.tag_add("stdin", "iomark", "end-1c")
1140        self.text.update_idletasks()
1141        if self.reading:
1142            self.top.quit() # Break out of recursive mainloop() in raw_input()
1143        else:
1144            self.runit()
1145        return "break"
1146
1147    def recall(self, s, event):
1148        # remove leading and trailing empty or whitespace lines
1149        s = re.sub(r'^\s*\n', '' , s)
1150        s = re.sub(r'\n\s*$', '', s)
1151        lines = s.split('\n')
1152        self.text.undo_block_start()
1153        try:
1154            self.text.tag_remove("sel", "1.0", "end")
1155            self.text.mark_set("insert", "end-1c")
1156            prefix = self.text.get("insert linestart", "insert")
1157            if prefix.rstrip().endswith(':'):
1158                self.newline_and_indent_event(event)
1159                prefix = self.text.get("insert linestart", "insert")
1160            self.text.insert("insert", lines[0].strip())
1161            if len(lines) > 1:
1162                orig_base_indent = re.search(r'^([ \t]*)', lines[0]).group(0)
1163                new_base_indent  = re.search(r'^([ \t]*)', prefix).group(0)
1164                for line in lines[1:]:
1165                    if line.startswith(orig_base_indent):
1166                        # replace orig base indentation with new indentation
1167                        line = new_base_indent + line[len(orig_base_indent):]
1168                    self.text.insert('insert', '\n'+line.rstrip())
1169        finally:
1170            self.text.see("insert")
1171            self.text.undo_block_stop()
1172
1173    def runit(self):
1174        line = self.text.get("iomark", "end-1c")
1175        # Strip off last newline and surrounding whitespace.
1176        # (To allow you to hit return twice to end a statement.)
1177        i = len(line)
1178        while i > 0 and line[i-1] in " \t":
1179            i = i-1
1180        if i > 0 and line[i-1] == "\n":
1181            i = i-1
1182        while i > 0 and line[i-1] in " \t":
1183            i = i-1
1184        line = line[:i]
1185        more = self.interp.runsource(line)
1186
1187    def open_stack_viewer(self, event=None):
1188        if self.interp.rpcclt:
1189            return self.interp.remote_stack_viewer()
1190        try:
1191            sys.last_traceback
1192        except:
1193            tkMessageBox.showerror("No stack trace",
1194                "There is no stack trace yet.\n"
1195                "(sys.last_traceback is not defined)",
1196                master=self.text)
1197            return
1198        from StackViewer import StackBrowser
1199        sv = StackBrowser(self.root, self.flist)
1200
1201    def view_restart_mark(self, event=None):
1202        self.text.see("iomark")
1203        self.text.see("restart")
1204
1205    def restart_shell(self, event=None):
1206        self.interp.restart_subprocess()
1207
1208    def showprompt(self):
1209        self.resetoutput()
1210        try:
1211            s = str(sys.ps1)
1212        except:
1213            s = ""
1214        self.console.write(s)
1215        self.text.mark_set("insert", "end-1c")
1216        self.set_line_and_column()
1217        self.io.reset_undo()
1218
1219    def resetoutput(self):
1220        source = self.text.get("iomark", "end-1c")
1221        if self.history:
1222            self.history.history_store(source)
1223        if self.text.get("end-2c") != "\n":
1224            self.text.insert("end-1c", "\n")
1225        self.text.mark_set("iomark", "end-1c")
1226        self.set_line_and_column()
1227        sys.stdout.softspace = 0
1228
1229    def write(self, s, tags=()):
1230        try:
1231            self.text.mark_gravity("iomark", "right")
1232            OutputWindow.write(self, s, tags, "iomark")
1233            self.text.mark_gravity("iomark", "left")
1234        except:
1235            pass
1236        if self.canceled:
1237            self.canceled = 0
1238            if not use_subprocess:
1239                raise KeyboardInterrupt
1240
1241class PseudoFile(object):
1242
1243    def __init__(self, shell, tags, encoding=None):
1244        self.shell = shell
1245        self.tags = tags
1246        self.softspace = 0
1247        self.encoding = encoding
1248
1249    def write(self, s):
1250        self.shell.write(s, self.tags)
1251
1252    def writelines(self, l):
1253        map(self.write, l)
1254
1255    def flush(self):
1256        pass
1257
1258    def isatty(self):
1259        return True
1260
1261
1262usage_msg = """\
1263
1264USAGE: idle  [-deins] [-t title] [file]*
1265       idle  [-dns] [-t title] (-c cmd | -r file) [arg]*
1266       idle  [-dns] [-t title] - [arg]*
1267
1268  -h         print this help message and exit
1269  -n         run IDLE without a subprocess (see Help/IDLE Help for details)
1270
1271The following options will override the IDLE 'settings' configuration:
1272
1273  -e         open an edit window
1274  -i         open a shell window
1275
1276The following options imply -i and will open a shell:
1277
1278  -c cmd     run the command in a shell, or
1279  -r file    run script from file
1280
1281  -d         enable the debugger
1282  -s         run $IDLESTARTUP or $PYTHONSTARTUP before anything else
1283  -t title   set title of shell window
1284
1285A default edit window will be bypassed when -c, -r, or - are used.
1286
1287[arg]* are passed to the command (-c) or script (-r) in sys.argv[1:].
1288
1289Examples:
1290
1291idle
1292        Open an edit window or shell depending on IDLE's configuration.
1293
1294idle foo.py foobar.py
1295        Edit the files, also open a shell if configured to start with shell.
1296
1297idle -est "Baz" foo.py
1298        Run $IDLESTARTUP or $PYTHONSTARTUP, edit foo.py, and open a shell
1299        window with the title "Baz".
1300
1301idle -c "import sys; print sys.argv" "foo"
1302        Open a shell window and run the command, passing "-c" in sys.argv[0]
1303        and "foo" in sys.argv[1].
1304
1305idle -d -s -r foo.py "Hello World"
1306        Open a shell window, run a startup script, enable the debugger, and
1307        run foo.py, passing "foo.py" in sys.argv[0] and "Hello World" in
1308        sys.argv[1].
1309
1310echo "import sys; print sys.argv" | idle - "foobar"
1311        Open a shell window, run the script piped in, passing '' in sys.argv[0]
1312        and "foobar" in sys.argv[1].
1313"""
1314
1315def main():
1316    global flist, root, use_subprocess
1317
1318    use_subprocess = True
1319    enable_shell = False
1320    enable_edit = False
1321    debug = False
1322    cmd = None
1323    script = None
1324    startup = False
1325    try:
1326        opts, args = getopt.getopt(sys.argv[1:], "c:deihnr:st:")
1327    except getopt.error, msg:
1328        sys.stderr.write("Error: %s\n" % str(msg))
1329        sys.stderr.write(usage_msg)
1330        sys.exit(2)
1331    for o, a in opts:
1332        if o == '-c':
1333            cmd = a
1334            enable_shell = True
1335        if o == '-d':
1336            debug = True
1337            enable_shell = True
1338        if o == '-e':
1339            enable_edit = True
1340        if o == '-h':
1341            sys.stdout.write(usage_msg)
1342            sys.exit()
1343        if o == '-i':
1344            enable_shell = True
1345        if o == '-n':
1346            use_subprocess = False
1347        if o == '-r':
1348            script = a
1349            if os.path.isfile(script):
1350                pass
1351            else:
1352                print "No script file: ", script
1353                sys.exit()
1354            enable_shell = True
1355        if o == '-s':
1356            startup = True
1357            enable_shell = True
1358        if o == '-t':
1359            PyShell.shell_title = a
1360            enable_shell = True
1361    if args and args[0] == '-':
1362        cmd = sys.stdin.read()
1363        enable_shell = True
1364    # process sys.argv and sys.path:
1365    for i in range(len(sys.path)):
1366        sys.path[i] = os.path.abspath(sys.path[i])
1367    if args and args[0] == '-':
1368        sys.argv = [''] + args[1:]
1369    elif cmd:
1370        sys.argv = ['-c'] + args
1371    elif script:
1372        sys.argv = [script] + args
1373    elif args:
1374        enable_edit = True
1375        pathx = []
1376        for filename in args:
1377            pathx.append(os.path.dirname(filename))
1378        for dir in pathx:
1379            dir = os.path.abspath(dir)
1380            if not dir in sys.path:
1381                sys.path.insert(0, dir)
1382    else:
1383        dir = os.getcwd()
1384        if not dir in sys.path:
1385            sys.path.insert(0, dir)
1386    # check the IDLE settings configuration (but command line overrides)
1387    edit_start = idleConf.GetOption('main', 'General',
1388                                    'editor-on-startup', type='bool')
1389    enable_edit = enable_edit or edit_start
1390    enable_shell = enable_shell or not edit_start
1391    # start editor and/or shell windows:
1392    root = Tk(className="Idle")
1393
1394    fixwordbreaks(root)
1395    root.withdraw()
1396    flist = PyShellFileList(root)
1397    macosxSupport.setupApp(root, flist)
1398
1399    if enable_edit:
1400        if not (cmd or script):
1401            for filename in args:
1402                flist.open(filename)
1403            if not args:
1404                flist.new()
1405    if enable_shell:
1406        shell = flist.open_shell()
1407        if not shell:
1408            return # couldn't open shell
1409
1410        if macosxSupport.runningAsOSXApp() and flist.dict:
1411            # On OSX: when the user has double-clicked on a file that causes
1412            # IDLE to be launched the shell window will open just in front of
1413            # the file she wants to see. Lower the interpreter window when
1414            # there are open files.
1415            shell.top.lower()
1416
1417    shell = flist.pyshell
1418    # handle remaining options:
1419    if debug:
1420        shell.open_debugger()
1421    if startup:
1422        filename = os.environ.get("IDLESTARTUP") or \
1423                   os.environ.get("PYTHONSTARTUP")
1424        if filename and os.path.isfile(filename):
1425            shell.interp.execfile(filename)
1426    if shell and cmd or script:
1427        shell.interp.runcommand("""if 1:
1428            import sys as _sys
1429            _sys.argv = %r
1430            del _sys
1431            \n""" % (sys.argv,))
1432        if cmd:
1433            shell.interp.execsource(cmd)
1434        elif script:
1435            shell.interp.prepend_syspath(script)
1436            shell.interp.execfile(script)
1437
1438    root.mainloop()
1439    root.destroy()
1440
1441if __name__ == "__main__":
1442    sys.modules['PyShell'] = sys.modules['__main__']
1443    main()
1444