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