• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#! /usr/bin/env python
2from __future__ import print_function
3
4import os
5import os.path
6import sys
7import string
8import getopt
9import re
10import socket
11import time
12import threading
13import io
14
15import linecache
16from code import InteractiveInterpreter
17from platform import python_version, system
18
19try:
20    from Tkinter import *
21except ImportError:
22    print("** IDLE can't import Tkinter.\n"
23          "Your Python may not be configured for Tk. **", file=sys.__stderr__)
24    sys.exit(1)
25import tkMessageBox
26
27from idlelib.EditorWindow import EditorWindow, fixwordbreaks
28from idlelib.FileList import FileList
29from idlelib.ColorDelegator import ColorDelegator
30from idlelib.UndoDelegator import UndoDelegator
31from idlelib.OutputWindow import OutputWindow
32from idlelib.configHandler import idleConf
33from idlelib import rpc
34from idlelib import Debugger
35from idlelib import RemoteDebugger
36from idlelib import macosxSupport
37from idlelib import IOBinding
38
39IDENTCHARS = string.ascii_letters + string.digits + "_"
40HOST = '127.0.0.1' # python execution server on localhost loopback
41PORT = 0  # someday pass in host, port for remote debug capability
42
43try:
44    from signal import SIGTERM
45except ImportError:
46    SIGTERM = 15
47
48# Override warnings module to write to warning_stream.  Initialize to send IDLE
49# internal warnings to the console.  ScriptBinding.check_syntax() will
50# temporarily redirect the stream to the shell window to display warnings when
51# checking user's code.
52warning_stream = sys.__stderr__  # None, at least on Windows, if no console.
53import warnings
54
55def idle_formatwarning(message, category, filename, lineno, line=None):
56    """Format warnings the IDLE way."""
57
58    s = "\nWarning (from warnings module):\n"
59    s += '  File \"%s\", line %s\n' % (filename, lineno)
60    if line is None:
61        line = linecache.getline(filename, lineno)
62    line = line.strip()
63    if line:
64        s += "    %s\n" % line
65    s += "%s: %s\n" % (category.__name__, message)
66    return s
67
68def idle_showwarning(
69        message, category, filename, lineno, file=None, line=None):
70    """Show Idle-format warning (after replacing warnings.showwarning).
71
72    The differences are the formatter called, the file=None replacement,
73    which can be None, the capture of the consequence AttributeError,
74    and the output of a hard-coded prompt.
75    """
76    if file is None:
77        file = warning_stream
78    try:
79        file.write(idle_formatwarning(
80                message, category, filename, lineno, line=line))
81        file.write(">>> ")
82    except (AttributeError, IOError):
83        pass  # if file (probably __stderr__) is invalid, skip warning.
84
85_warnings_showwarning = None
86
87def capture_warnings(capture):
88    "Replace warning.showwarning with idle_showwarning, or reverse."
89
90    global _warnings_showwarning
91    if capture:
92        if _warnings_showwarning is None:
93            _warnings_showwarning = warnings.showwarning
94            warnings.showwarning = idle_showwarning
95    else:
96        if _warnings_showwarning is not None:
97            warnings.showwarning = _warnings_showwarning
98            _warnings_showwarning = None
99
100capture_warnings(True)
101
102def extended_linecache_checkcache(filename=None,
103                                  orig_checkcache=linecache.checkcache):
104    """Extend linecache.checkcache to preserve the <pyshell#...> entries
105
106    Rather than repeating the linecache code, patch it to save the
107    <pyshell#...> entries, call the original linecache.checkcache()
108    (skipping them), and then restore the saved entries.
109
110    orig_checkcache is bound at definition time to the original
111    method, allowing it to be patched.
112    """
113    cache = linecache.cache
114    save = {}
115    for key in list(cache):
116        if key[:1] + key[-1:] == '<>':
117            save[key] = cache.pop(key)
118    orig_checkcache(filename)
119    cache.update(save)
120
121# Patch linecache.checkcache():
122linecache.checkcache = extended_linecache_checkcache
123
124
125class PyShellEditorWindow(EditorWindow):
126    "Regular text edit window in IDLE, supports breakpoints"
127
128    def __init__(self, *args):
129        self.breakpoints = []
130        EditorWindow.__init__(self, *args)
131        self.text.bind("<<set-breakpoint-here>>", self.set_breakpoint_here)
132        self.text.bind("<<clear-breakpoint-here>>", self.clear_breakpoint_here)
133        self.text.bind("<<open-python-shell>>", self.flist.open_shell)
134
135        self.breakpointPath = os.path.join(idleConf.GetUserCfgDir(),
136                                           'breakpoints.lst')
137        # whenever a file is changed, restore breakpoints
138        def filename_changed_hook(old_hook=self.io.filename_change_hook,
139                                  self=self):
140            self.restore_file_breaks()
141            old_hook()
142        self.io.set_filename_change_hook(filename_changed_hook)
143        if self.io.filename:
144            self.restore_file_breaks()
145        self.color_breakpoint_text()
146
147    rmenu_specs = [
148        ("Cut", "<<cut>>", "rmenu_check_cut"),
149        ("Copy", "<<copy>>", "rmenu_check_copy"),
150        ("Paste", "<<paste>>", "rmenu_check_paste"),
151        ("Set Breakpoint", "<<set-breakpoint-here>>", None),
152        ("Clear Breakpoint", "<<clear-breakpoint-here>>", None)
153    ]
154
155    def color_breakpoint_text(self, color=True):
156        "Turn colorizing of breakpoint text on or off"
157        if self.io is None:
158            # possible due to update in restore_file_breaks
159            return
160        if color:
161            theme = idleConf.CurrentTheme()
162            cfg = idleConf.GetHighlight(theme, "break")
163        else:
164            cfg = {'foreground': '', 'background': ''}
165        self.text.tag_config('BREAK', cfg)
166
167    def set_breakpoint(self, lineno):
168        text = self.text
169        filename = self.io.filename
170        text.tag_add("BREAK", "%d.0" % lineno, "%d.0" % (lineno+1))
171        try:
172            self.breakpoints.index(lineno)
173        except ValueError:  # only add if missing, i.e. do once
174            self.breakpoints.append(lineno)
175        try:    # update the subprocess debugger
176            debug = self.flist.pyshell.interp.debugger
177            debug.set_breakpoint_here(filename, lineno)
178        except: # but debugger may not be active right now....
179            pass
180
181    def set_breakpoint_here(self, event=None):
182        text = self.text
183        filename = self.io.filename
184        if not filename:
185            text.bell()
186            return
187        lineno = int(float(text.index("insert")))
188        self.set_breakpoint(lineno)
189
190    def clear_breakpoint_here(self, event=None):
191        text = self.text
192        filename = self.io.filename
193        if not filename:
194            text.bell()
195            return
196        lineno = int(float(text.index("insert")))
197        try:
198            self.breakpoints.remove(lineno)
199        except:
200            pass
201        text.tag_remove("BREAK", "insert linestart",\
202                        "insert lineend +1char")
203        try:
204            debug = self.flist.pyshell.interp.debugger
205            debug.clear_breakpoint_here(filename, lineno)
206        except:
207            pass
208
209    def clear_file_breaks(self):
210        if self.breakpoints:
211            text = self.text
212            filename = self.io.filename
213            if not filename:
214                text.bell()
215                return
216            self.breakpoints = []
217            text.tag_remove("BREAK", "1.0", END)
218            try:
219                debug = self.flist.pyshell.interp.debugger
220                debug.clear_file_breaks(filename)
221            except:
222                pass
223
224    def store_file_breaks(self):
225        "Save breakpoints when file is saved"
226        # XXX 13 Dec 2002 KBK Currently the file must be saved before it can
227        #     be run.  The breaks are saved at that time.  If we introduce
228        #     a temporary file save feature the save breaks functionality
229        #     needs to be re-verified, since the breaks at the time the
230        #     temp file is created may differ from the breaks at the last
231        #     permanent save of the file.  Currently, a break introduced
232        #     after a save will be effective, but not persistent.
233        #     This is necessary to keep the saved breaks synched with the
234        #     saved file.
235        #
236        #     Breakpoints are set as tagged ranges in the text.
237        #     Since a modified file has to be saved before it is
238        #     run, and since self.breakpoints (from which the subprocess
239        #     debugger is loaded) is updated during the save, the visible
240        #     breaks stay synched with the subprocess even if one of these
241        #     unexpected breakpoint deletions occurs.
242        breaks = self.breakpoints
243        filename = self.io.filename
244        try:
245            with open(self.breakpointPath,"r") as old_file:
246                lines = old_file.readlines()
247        except IOError:
248            lines = []
249        try:
250            with open(self.breakpointPath,"w") as new_file:
251                for line in lines:
252                    if not line.startswith(filename + '='):
253                        new_file.write(line)
254                self.update_breakpoints()
255                breaks = self.breakpoints
256                if breaks:
257                    new_file.write(filename + '=' + str(breaks) + '\n')
258        except IOError as err:
259            if not getattr(self.root, "breakpoint_error_displayed", False):
260                self.root.breakpoint_error_displayed = True
261                tkMessageBox.showerror(title='IDLE Error',
262                    message='Unable to update breakpoint list:\n%s'
263                        % str(err),
264                    parent=self.text)
265
266    def restore_file_breaks(self):
267        self.text.update()   # this enables setting "BREAK" tags to be visible
268        if self.io is None:
269            # can happen if IDLE closes due to the .update() call
270            return
271        filename = self.io.filename
272        if filename is None:
273            return
274        if os.path.isfile(self.breakpointPath):
275            lines = open(self.breakpointPath,"r").readlines()
276            for line in lines:
277                if line.startswith(filename + '='):
278                    breakpoint_linenumbers = eval(line[len(filename)+1:])
279                    for breakpoint_linenumber in breakpoint_linenumbers:
280                        self.set_breakpoint(breakpoint_linenumber)
281
282    def update_breakpoints(self):
283        "Retrieves all the breakpoints in the current window"
284        text = self.text
285        ranges = text.tag_ranges("BREAK")
286        linenumber_list = self.ranges_to_linenumbers(ranges)
287        self.breakpoints = linenumber_list
288
289    def ranges_to_linenumbers(self, ranges):
290        lines = []
291        for index in range(0, len(ranges), 2):
292            lineno = int(float(ranges[index].string))
293            end = int(float(ranges[index+1].string))
294            while lineno < end:
295                lines.append(lineno)
296                lineno += 1
297        return lines
298
299# XXX 13 Dec 2002 KBK Not used currently
300#    def saved_change_hook(self):
301#        "Extend base method - clear breaks if module is modified"
302#        if not self.get_saved():
303#            self.clear_file_breaks()
304#        EditorWindow.saved_change_hook(self)
305
306    def _close(self):
307        "Extend base method - clear breaks when module is closed"
308        self.clear_file_breaks()
309        EditorWindow._close(self)
310
311
312class PyShellFileList(FileList):
313    "Extend base class: IDLE supports a shell and breakpoints"
314
315    # override FileList's class variable, instances return PyShellEditorWindow
316    # instead of EditorWindow when new edit windows are created.
317    EditorWindow = PyShellEditorWindow
318
319    pyshell = None
320
321    def open_shell(self, event=None):
322        if self.pyshell:
323            self.pyshell.top.wakeup()
324        else:
325            self.pyshell = PyShell(self)
326            if self.pyshell:
327                if not self.pyshell.begin():
328                    return None
329        return self.pyshell
330
331
332class ModifiedColorDelegator(ColorDelegator):
333    "Extend base class: colorizer for the shell window itself"
334
335    def __init__(self):
336        ColorDelegator.__init__(self)
337        self.LoadTagDefs()
338
339    def recolorize_main(self):
340        self.tag_remove("TODO", "1.0", "iomark")
341        self.tag_add("SYNC", "1.0", "iomark")
342        ColorDelegator.recolorize_main(self)
343
344    def LoadTagDefs(self):
345        ColorDelegator.LoadTagDefs(self)
346        theme = idleConf.CurrentTheme()
347        self.tagdefs.update({
348            "stdin": {'background':None,'foreground':None},
349            "stdout": idleConf.GetHighlight(theme, "stdout"),
350            "stderr": idleConf.GetHighlight(theme, "stderr"),
351            "console": idleConf.GetHighlight(theme, "console"),
352        })
353
354    def removecolors(self):
355        # Don't remove shell color tags before "iomark"
356        for tag in self.tagdefs:
357            self.tag_remove(tag, "iomark", "end")
358
359class ModifiedUndoDelegator(UndoDelegator):
360    "Extend base class: forbid insert/delete before the I/O mark"
361
362    def insert(self, index, chars, tags=None):
363        try:
364            if self.delegate.compare(index, "<", "iomark"):
365                self.delegate.bell()
366                return
367        except TclError:
368            pass
369        UndoDelegator.insert(self, index, chars, tags)
370
371    def delete(self, index1, index2=None):
372        try:
373            if self.delegate.compare(index1, "<", "iomark"):
374                self.delegate.bell()
375                return
376        except TclError:
377            pass
378        UndoDelegator.delete(self, index1, index2)
379
380
381class MyRPCClient(rpc.RPCClient):
382
383    def handle_EOF(self):
384        "Override the base class - just re-raise EOFError"
385        raise EOFError
386
387
388class ModifiedInterpreter(InteractiveInterpreter):
389
390    def __init__(self, tkconsole):
391        self.tkconsole = tkconsole
392        locals = sys.modules['__main__'].__dict__
393        InteractiveInterpreter.__init__(self, locals=locals)
394        self.save_warnings_filters = None
395        self.restarting = False
396        self.subprocess_arglist = None
397        self.port = PORT
398        self.original_compiler_flags = self.compile.compiler.flags
399
400    _afterid = None
401    rpcclt = None
402    rpcpid = None
403
404    def spawn_subprocess(self):
405        if self.subprocess_arglist is None:
406            self.subprocess_arglist = self.build_subprocess_arglist()
407        args = self.subprocess_arglist
408        self.rpcpid = os.spawnv(os.P_NOWAIT, sys.executable, args)
409
410    def build_subprocess_arglist(self):
411        assert (self.port!=0), (
412            "Socket should have been assigned a port number.")
413        w = ['-W' + s for s in sys.warnoptions]
414        if 1/2 > 0: # account for new division
415            w.append('-Qnew')
416        # Maybe IDLE is installed and is being accessed via sys.path,
417        # or maybe it's not installed and the idle.py script is being
418        # run from the IDLE source directory.
419        del_exitf = idleConf.GetOption('main', 'General', 'delete-exitfunc',
420                                       default=False, type='bool')
421        if __name__ == 'idlelib.PyShell':
422            command = "__import__('idlelib.run').run.main(%r)" % (del_exitf,)
423        else:
424            command = "__import__('run').main(%r)" % (del_exitf,)
425        if sys.platform[:3] == 'win' and ' ' in sys.executable:
426            # handle embedded space in path by quoting the argument
427            decorated_exec = '"%s"' % sys.executable
428        else:
429            decorated_exec = sys.executable
430        return [decorated_exec] + w + ["-c", command, str(self.port)]
431
432    def start_subprocess(self):
433        addr = (HOST, self.port)
434        # GUI makes several attempts to acquire socket, listens for connection
435        for i in range(3):
436            time.sleep(i)
437            try:
438                self.rpcclt = MyRPCClient(addr)
439                break
440            except socket.error:
441                pass
442        else:
443            self.display_port_binding_error()
444            return None
445        # if PORT was 0, system will assign an 'ephemeral' port. Find it out:
446        self.port = self.rpcclt.listening_sock.getsockname()[1]
447        # if PORT was not 0, probably working with a remote execution server
448        if PORT != 0:
449            # To allow reconnection within the 2MSL wait (cf. Stevens TCP
450            # V1, 18.6),  set SO_REUSEADDR.  Note that this can be problematic
451            # on Windows since the implementation allows two active sockets on
452            # the same address!
453            self.rpcclt.listening_sock.setsockopt(socket.SOL_SOCKET,
454                                           socket.SO_REUSEADDR, 1)
455        self.spawn_subprocess()
456        #time.sleep(20) # test to simulate GUI not accepting connection
457        # Accept the connection from the Python execution server
458        self.rpcclt.listening_sock.settimeout(10)
459        try:
460            self.rpcclt.accept()
461        except socket.timeout:
462            self.display_no_subprocess_error()
463            return None
464        self.rpcclt.register("console", self.tkconsole)
465        self.rpcclt.register("stdin", self.tkconsole.stdin)
466        self.rpcclt.register("stdout", self.tkconsole.stdout)
467        self.rpcclt.register("stderr", self.tkconsole.stderr)
468        self.rpcclt.register("flist", self.tkconsole.flist)
469        self.rpcclt.register("linecache", linecache)
470        self.rpcclt.register("interp", self)
471        self.transfer_path(with_cwd=True)
472        self.poll_subprocess()
473        return self.rpcclt
474
475    def restart_subprocess(self, with_cwd=False, filename=''):
476        if self.restarting:
477            return self.rpcclt
478        self.restarting = True
479        # close only the subprocess debugger
480        debug = self.getdebugger()
481        if debug:
482            try:
483                # Only close subprocess debugger, don't unregister gui_adap!
484                RemoteDebugger.close_subprocess_debugger(self.rpcclt)
485            except:
486                pass
487        # Kill subprocess, spawn a new one, accept connection.
488        self.rpcclt.close()
489        self.unix_terminate()
490        console = self.tkconsole
491        was_executing = console.executing
492        console.executing = False
493        self.spawn_subprocess()
494        try:
495            self.rpcclt.accept()
496        except socket.timeout:
497            self.display_no_subprocess_error()
498            return None
499        self.transfer_path(with_cwd=with_cwd)
500        console.stop_readline()
501        # annotate restart in shell window and mark it
502        console.text.delete("iomark", "end-1c")
503        tag = 'RESTART: ' + (filename if filename else 'Shell')
504        halfbar = ((int(console.width) -len(tag) - 4) // 2) * '='
505        console.write("\n{0} {1} {0}".format(halfbar, tag))
506        console.text.mark_set("restart", "end-1c")
507        console.text.mark_gravity("restart", "left")
508        if not filename:
509            console.showprompt()
510        # restart subprocess debugger
511        if debug:
512            # Restarted debugger connects to current instance of debug GUI
513            RemoteDebugger.restart_subprocess_debugger(self.rpcclt)
514            # reload remote debugger breakpoints for all PyShellEditWindows
515            debug.load_breakpoints()
516        self.compile.compiler.flags = self.original_compiler_flags
517        self.restarting = False
518        return self.rpcclt
519
520    def __request_interrupt(self):
521        self.rpcclt.remotecall("exec", "interrupt_the_server", (), {})
522
523    def interrupt_subprocess(self):
524        threading.Thread(target=self.__request_interrupt).start()
525
526    def kill_subprocess(self):
527        if self._afterid is not None:
528            self.tkconsole.text.after_cancel(self._afterid)
529        try:
530            self.rpcclt.close()
531        except AttributeError:  # no socket
532            pass
533        self.unix_terminate()
534        self.tkconsole.executing = False
535        self.rpcclt = None
536
537    def unix_terminate(self):
538        "UNIX: make sure subprocess is terminated and collect status"
539        if hasattr(os, 'kill'):
540            try:
541                os.kill(self.rpcpid, SIGTERM)
542            except OSError:
543                # process already terminated:
544                return
545            else:
546                try:
547                    os.waitpid(self.rpcpid, 0)
548                except OSError:
549                    return
550
551    def transfer_path(self, with_cwd=False):
552        if with_cwd:        # Issue 13506
553            path = ['']     # include Current Working Directory
554            path.extend(sys.path)
555        else:
556            path = sys.path
557
558        self.runcommand("""if 1:
559        import sys as _sys
560        _sys.path = %r
561        del _sys
562        \n""" % (path,))
563
564    active_seq = None
565
566    def poll_subprocess(self):
567        clt = self.rpcclt
568        if clt is None:
569            return
570        try:
571            response = clt.pollresponse(self.active_seq, wait=0.05)
572        except (EOFError, IOError, KeyboardInterrupt):
573            # lost connection or subprocess terminated itself, restart
574            # [the KBI is from rpc.SocketIO.handle_EOF()]
575            if self.tkconsole.closing:
576                return
577            response = None
578            self.restart_subprocess()
579        if response:
580            self.tkconsole.resetoutput()
581            self.active_seq = None
582            how, what = response
583            console = self.tkconsole.console
584            if how == "OK":
585                if what is not None:
586                    print(repr(what), file=console)
587            elif how == "EXCEPTION":
588                if self.tkconsole.getvar("<<toggle-jit-stack-viewer>>"):
589                    self.remote_stack_viewer()
590            elif how == "ERROR":
591                errmsg = "PyShell.ModifiedInterpreter: Subprocess ERROR:\n"
592                print(errmsg, what, file=sys.__stderr__)
593                print(errmsg, what, file=console)
594            # we received a response to the currently active seq number:
595            try:
596                self.tkconsole.endexecuting()
597            except AttributeError:  # shell may have closed
598                pass
599        # Reschedule myself
600        if not self.tkconsole.closing:
601            self._afterid = self.tkconsole.text.after(
602                self.tkconsole.pollinterval, self.poll_subprocess)
603
604    debugger = None
605
606    def setdebugger(self, debugger):
607        self.debugger = debugger
608
609    def getdebugger(self):
610        return self.debugger
611
612    def open_remote_stack_viewer(self):
613        """Initiate the remote stack viewer from a separate thread.
614
615        This method is called from the subprocess, and by returning from this
616        method we allow the subprocess to unblock.  After a bit the shell
617        requests the subprocess to open the remote stack viewer which returns a
618        static object looking at the last exception.  It is queried through
619        the RPC mechanism.
620
621        """
622        self.tkconsole.text.after(300, self.remote_stack_viewer)
623        return
624
625    def remote_stack_viewer(self):
626        from idlelib import RemoteObjectBrowser
627        oid = self.rpcclt.remotequeue("exec", "stackviewer", ("flist",), {})
628        if oid is None:
629            self.tkconsole.root.bell()
630            return
631        item = RemoteObjectBrowser.StubObjectTreeItem(self.rpcclt, oid)
632        from idlelib.TreeWidget import ScrolledCanvas, TreeNode
633        top = Toplevel(self.tkconsole.root)
634        theme = idleConf.CurrentTheme()
635        background = idleConf.GetHighlight(theme, 'normal')['background']
636        sc = ScrolledCanvas(top, bg=background, highlightthickness=0)
637        sc.frame.pack(expand=1, fill="both")
638        node = TreeNode(sc.canvas, None, item)
639        node.expand()
640        # XXX Should GC the remote tree when closing the window
641
642    gid = 0
643
644    def execsource(self, source):
645        "Like runsource() but assumes complete exec source"
646        filename = self.stuffsource(source)
647        self.execfile(filename, source)
648
649    def execfile(self, filename, source=None):
650        "Execute an existing file"
651        if source is None:
652            source = open(filename, "r").read()
653        try:
654            code = compile(source, filename, "exec", dont_inherit=True)
655        except (OverflowError, SyntaxError):
656            self.tkconsole.resetoutput()
657            print('*** Error in script or command!\n'
658                  'Traceback (most recent call last):',
659                  file=self.tkconsole.stderr)
660            InteractiveInterpreter.showsyntaxerror(self, filename)
661            self.tkconsole.showprompt()
662        else:
663            self.runcode(code)
664
665    def runsource(self, source):
666        "Extend base class method: Stuff the source in the line cache first"
667        filename = self.stuffsource(source)
668        self.more = 0
669        self.save_warnings_filters = warnings.filters[:]
670        warnings.filterwarnings(action="error", category=SyntaxWarning)
671        if isinstance(source, unicode) and IOBinding.encoding != 'utf-8':
672            try:
673                source = '# -*- coding: %s -*-\n%s' % (
674                        IOBinding.encoding,
675                        source.encode(IOBinding.encoding))
676            except UnicodeError:
677                self.tkconsole.resetoutput()
678                self.write("Unsupported characters in input\n")
679                return
680        try:
681            # InteractiveInterpreter.runsource() calls its runcode() method,
682            # which is overridden (see below)
683            return InteractiveInterpreter.runsource(self, source, filename)
684        finally:
685            if self.save_warnings_filters is not None:
686                warnings.filters[:] = self.save_warnings_filters
687                self.save_warnings_filters = None
688
689    def stuffsource(self, source):
690        "Stuff source in the filename cache"
691        filename = "<pyshell#%d>" % self.gid
692        self.gid = self.gid + 1
693        lines = source.split("\n")
694        linecache.cache[filename] = len(source)+1, 0, lines, filename
695        return filename
696
697    def prepend_syspath(self, filename):
698        "Prepend sys.path with file's directory if not already included"
699        self.runcommand("""if 1:
700            _filename = %r
701            import sys as _sys
702            from os.path import dirname as _dirname
703            _dir = _dirname(_filename)
704            if not _dir in _sys.path:
705                _sys.path.insert(0, _dir)
706            del _filename, _sys, _dirname, _dir
707            \n""" % (filename,))
708
709    def showsyntaxerror(self, filename=None):
710        """Extend base class method: Add Colorizing
711
712        Color the offending position instead of printing it and pointing at it
713        with a caret.
714
715        """
716        text = self.tkconsole.text
717        stuff = self.unpackerror()
718        if stuff:
719            msg, lineno, offset, line = stuff
720            if lineno == 1:
721                pos = "iomark + %d chars" % (offset-1)
722            else:
723                pos = "iomark linestart + %d lines + %d chars" % \
724                      (lineno-1, offset-1)
725            text.tag_add("ERROR", pos)
726            text.see(pos)
727            char = text.get(pos)
728            if char and char in IDENTCHARS:
729                text.tag_add("ERROR", pos + " wordstart", pos)
730            self.tkconsole.resetoutput()
731            self.write("SyntaxError: %s\n" % str(msg))
732        else:
733            self.tkconsole.resetoutput()
734            InteractiveInterpreter.showsyntaxerror(self, filename)
735        self.tkconsole.showprompt()
736
737    def unpackerror(self):
738        type, value, tb = sys.exc_info()
739        ok = type is SyntaxError
740        if ok:
741            try:
742                msg, (dummy_filename, lineno, offset, line) = value
743                if not offset:
744                    offset = 0
745            except:
746                ok = 0
747        if ok:
748            return msg, lineno, offset, line
749        else:
750            return None
751
752    def showtraceback(self):
753        "Extend base class method to reset output properly"
754        self.tkconsole.resetoutput()
755        self.checklinecache()
756        InteractiveInterpreter.showtraceback(self)
757        if self.tkconsole.getvar("<<toggle-jit-stack-viewer>>"):
758            self.tkconsole.open_stack_viewer()
759
760    def checklinecache(self):
761        c = linecache.cache
762        for key in c.keys():
763            if key[:1] + key[-1:] != "<>":
764                del c[key]
765
766    def runcommand(self, code):
767        "Run the code without invoking the debugger"
768        # The code better not raise an exception!
769        if self.tkconsole.executing:
770            self.display_executing_dialog()
771            return 0
772        if self.rpcclt:
773            self.rpcclt.remotequeue("exec", "runcode", (code,), {})
774        else:
775            exec code in self.locals
776        return 1
777
778    def runcode(self, code):
779        "Override base class method"
780        if self.tkconsole.executing:
781            self.interp.restart_subprocess()
782        self.checklinecache()
783        if self.save_warnings_filters is not None:
784            warnings.filters[:] = self.save_warnings_filters
785            self.save_warnings_filters = None
786        debugger = self.debugger
787        try:
788            self.tkconsole.beginexecuting()
789            if not debugger and self.rpcclt is not None:
790                self.active_seq = self.rpcclt.asyncqueue("exec", "runcode",
791                                                        (code,), {})
792            elif debugger:
793                debugger.run(code, self.locals)
794            else:
795                exec code in self.locals
796        except SystemExit:
797            if not self.tkconsole.closing:
798                if tkMessageBox.askyesno(
799                    "Exit?",
800                    "Do you want to exit altogether?",
801                    default="yes",
802                    parent=self.tkconsole.text):
803                    raise
804                else:
805                    self.showtraceback()
806            else:
807                raise
808        except:
809            if use_subprocess:
810                print("IDLE internal error in runcode()",
811                      file=self.tkconsole.stderr)
812                self.showtraceback()
813                self.tkconsole.endexecuting()
814            else:
815                if self.tkconsole.canceled:
816                    self.tkconsole.canceled = False
817                    print("KeyboardInterrupt", file=self.tkconsole.stderr)
818                else:
819                    self.showtraceback()
820        finally:
821            if not use_subprocess:
822                try:
823                    self.tkconsole.endexecuting()
824                except AttributeError:  # shell may have closed
825                    pass
826
827    def write(self, s):
828        "Override base class method"
829        self.tkconsole.stderr.write(s)
830
831    def display_port_binding_error(self):
832        tkMessageBox.showerror(
833            "Port Binding Error",
834            "IDLE can't bind to a TCP/IP port, which is necessary to "
835            "communicate with its Python execution server.  This might be "
836            "because no networking is installed on this computer.  "
837            "Run IDLE with the -n command line switch to start without a "
838            "subprocess and refer to Help/IDLE Help 'Running without a "
839            "subprocess' for further details.",
840            parent=self.tkconsole.text)
841
842    def display_no_subprocess_error(self):
843        tkMessageBox.showerror(
844            "Subprocess Startup Error",
845            "IDLE's subprocess didn't make connection.  Either IDLE can't "
846            "start a subprocess or personal firewall software is blocking "
847            "the connection.",
848            parent=self.tkconsole.text)
849
850    def display_executing_dialog(self):
851        tkMessageBox.showerror(
852            "Already executing",
853            "The Python Shell window is already executing a command; "
854            "please wait until it is finished.",
855            parent=self.tkconsole.text)
856
857
858class PyShell(OutputWindow):
859
860    shell_title = "Python " + python_version() + " Shell"
861
862    # Override classes
863    ColorDelegator = ModifiedColorDelegator
864    UndoDelegator = ModifiedUndoDelegator
865
866    # Override menus
867    menu_specs = [
868        ("file", "_File"),
869        ("edit", "_Edit"),
870        ("debug", "_Debug"),
871        ("options", "_Options"),
872        ("windows", "_Window"),
873        ("help", "_Help"),
874    ]
875
876
877    # New classes
878    from idlelib.IdleHistory import History
879
880    def __init__(self, flist=None):
881        if use_subprocess:
882            ms = self.menu_specs
883            if ms[2][0] != "shell":
884                ms.insert(2, ("shell", "She_ll"))
885        self.interp = ModifiedInterpreter(self)
886        if flist is None:
887            root = Tk()
888            fixwordbreaks(root)
889            root.withdraw()
890            flist = PyShellFileList(root)
891        #
892        OutputWindow.__init__(self, flist, None, None)
893        #
894##        self.config(usetabs=1, indentwidth=8, context_use_ps1=1)
895        self.usetabs = True
896        # indentwidth must be 8 when using tabs.  See note in EditorWindow:
897        self.indentwidth = 8
898        self.context_use_ps1 = True
899        #
900        text = self.text
901        text.configure(wrap="char")
902        text.bind("<<newline-and-indent>>", self.enter_callback)
903        text.bind("<<plain-newline-and-indent>>", self.linefeed_callback)
904        text.bind("<<interrupt-execution>>", self.cancel_callback)
905        text.bind("<<end-of-file>>", self.eof_callback)
906        text.bind("<<open-stack-viewer>>", self.open_stack_viewer)
907        text.bind("<<toggle-debugger>>", self.toggle_debugger)
908        text.bind("<<toggle-jit-stack-viewer>>", self.toggle_jit_stack_viewer)
909        if use_subprocess:
910            text.bind("<<view-restart>>", self.view_restart_mark)
911            text.bind("<<restart-shell>>", self.restart_shell)
912        #
913        self.save_stdout = sys.stdout
914        self.save_stderr = sys.stderr
915        self.save_stdin = sys.stdin
916        from idlelib import IOBinding
917        self.stdin = PseudoInputFile(self, "stdin", IOBinding.encoding)
918        self.stdout = PseudoOutputFile(self, "stdout", IOBinding.encoding)
919        self.stderr = PseudoOutputFile(self, "stderr", IOBinding.encoding)
920        self.console = PseudoOutputFile(self, "console", IOBinding.encoding)
921        if not use_subprocess:
922            sys.stdout = self.stdout
923            sys.stderr = self.stderr
924            sys.stdin = self.stdin
925        #
926        self.history = self.History(self.text)
927        #
928        self.pollinterval = 50  # millisec
929
930    def get_standard_extension_names(self):
931        return idleConf.GetExtensions(shell_only=True)
932
933    reading = False
934    executing = False
935    canceled = False
936    endoffile = False
937    closing = False
938    _stop_readline_flag = False
939
940    def set_warning_stream(self, stream):
941        global warning_stream
942        warning_stream = stream
943
944    def get_warning_stream(self):
945        return warning_stream
946
947    def toggle_debugger(self, event=None):
948        if self.executing:
949            tkMessageBox.showerror("Don't debug now",
950                "You can only toggle the debugger when idle",
951                parent=self.text)
952            self.set_debugger_indicator()
953            return "break"
954        else:
955            db = self.interp.getdebugger()
956            if db:
957                self.close_debugger()
958            else:
959                self.open_debugger()
960
961    def set_debugger_indicator(self):
962        db = self.interp.getdebugger()
963        self.setvar("<<toggle-debugger>>", not not db)
964
965    def toggle_jit_stack_viewer(self, event=None):
966        pass # All we need is the variable
967
968    def close_debugger(self):
969        db = self.interp.getdebugger()
970        if db:
971            self.interp.setdebugger(None)
972            db.close()
973            if self.interp.rpcclt:
974                RemoteDebugger.close_remote_debugger(self.interp.rpcclt)
975            self.resetoutput()
976            self.console.write("[DEBUG OFF]\n")
977            sys.ps1 = ">>> "
978            self.showprompt()
979        self.set_debugger_indicator()
980
981    def open_debugger(self):
982        if self.interp.rpcclt:
983            dbg_gui = RemoteDebugger.start_remote_debugger(self.interp.rpcclt,
984                                                           self)
985        else:
986            dbg_gui = Debugger.Debugger(self)
987        self.interp.setdebugger(dbg_gui)
988        dbg_gui.load_breakpoints()
989        sys.ps1 = "[DEBUG ON]\n>>> "
990        self.showprompt()
991        self.set_debugger_indicator()
992
993    def beginexecuting(self):
994        "Helper for ModifiedInterpreter"
995        self.resetoutput()
996        self.executing = 1
997
998    def endexecuting(self):
999        "Helper for ModifiedInterpreter"
1000        self.executing = 0
1001        self.canceled = 0
1002        self.showprompt()
1003
1004    def close(self):
1005        "Extend EditorWindow.close()"
1006        if self.executing:
1007            response = tkMessageBox.askokcancel(
1008                "Kill?",
1009                "Your program is still running!\n Do you want to kill it?",
1010                default="ok",
1011                parent=self.text)
1012            if response is False:
1013                return "cancel"
1014        self.stop_readline()
1015        self.canceled = True
1016        self.closing = True
1017        return EditorWindow.close(self)
1018
1019    def _close(self):
1020        "Extend EditorWindow._close(), shut down debugger and execution server"
1021        self.close_debugger()
1022        if use_subprocess:
1023            self.interp.kill_subprocess()
1024        # Restore std streams
1025        sys.stdout = self.save_stdout
1026        sys.stderr = self.save_stderr
1027        sys.stdin = self.save_stdin
1028        # Break cycles
1029        self.interp = None
1030        self.console = None
1031        self.flist.pyshell = None
1032        self.history = None
1033        EditorWindow._close(self)
1034
1035    def ispythonsource(self, filename):
1036        "Override EditorWindow method: never remove the colorizer"
1037        return True
1038
1039    def short_title(self):
1040        return self.shell_title
1041
1042    COPYRIGHT = \
1043          'Type "copyright", "credits" or "license()" for more information.'
1044
1045    def begin(self):
1046        self.resetoutput()
1047        if use_subprocess:
1048            nosub = ''
1049            client = self.interp.start_subprocess()
1050            if not client:
1051                self.close()
1052                return False
1053        else:
1054            nosub = "==== No Subprocess ===="
1055        self.write("Python %s on %s\n%s\n%s" %
1056                   (sys.version, sys.platform, self.COPYRIGHT, nosub))
1057        self.text.focus_force()
1058        self.showprompt()
1059        import Tkinter
1060        Tkinter._default_root = None # 03Jan04 KBK What's this?
1061        return True
1062
1063    def stop_readline(self):
1064        if not self.reading:  # no nested mainloop to exit.
1065            return
1066        self._stop_readline_flag = True
1067        self.top.quit()
1068
1069    def readline(self):
1070        save = self.reading
1071        try:
1072            self.reading = 1
1073            self.top.mainloop()  # nested mainloop()
1074        finally:
1075            self.reading = save
1076        if self._stop_readline_flag:
1077            self._stop_readline_flag = False
1078            return ""
1079        line = self.text.get("iomark", "end-1c")
1080        if len(line) == 0:  # may be EOF if we quit our mainloop with Ctrl-C
1081            line = "\n"
1082        if isinstance(line, unicode):
1083            from idlelib import IOBinding
1084            try:
1085                line = line.encode(IOBinding.encoding)
1086            except UnicodeError:
1087                pass
1088        self.resetoutput()
1089        if self.canceled:
1090            self.canceled = 0
1091            if not use_subprocess:
1092                raise KeyboardInterrupt
1093        if self.endoffile:
1094            self.endoffile = 0
1095            line = ""
1096        return line
1097
1098    def isatty(self):
1099        return True
1100
1101    def cancel_callback(self, event=None):
1102        try:
1103            if self.text.compare("sel.first", "!=", "sel.last"):
1104                return # Active selection -- always use default binding
1105        except:
1106            pass
1107        if not (self.executing or self.reading):
1108            self.resetoutput()
1109            self.interp.write("KeyboardInterrupt\n")
1110            self.showprompt()
1111            return "break"
1112        self.endoffile = 0
1113        self.canceled = 1
1114        if (self.executing and self.interp.rpcclt):
1115            if self.interp.getdebugger():
1116                self.interp.restart_subprocess()
1117            else:
1118                self.interp.interrupt_subprocess()
1119        if self.reading:
1120            self.top.quit()  # exit the nested mainloop() in readline()
1121        return "break"
1122
1123    def eof_callback(self, event):
1124        if self.executing and not self.reading:
1125            return # Let the default binding (delete next char) take over
1126        if not (self.text.compare("iomark", "==", "insert") and
1127                self.text.compare("insert", "==", "end-1c")):
1128            return # Let the default binding (delete next char) take over
1129        if not self.executing:
1130            self.resetoutput()
1131            self.close()
1132        else:
1133            self.canceled = 0
1134            self.endoffile = 1
1135            self.top.quit()
1136        return "break"
1137
1138    def linefeed_callback(self, event):
1139        # Insert a linefeed without entering anything (still autoindented)
1140        if self.reading:
1141            self.text.insert("insert", "\n")
1142            self.text.see("insert")
1143        else:
1144            self.newline_and_indent_event(event)
1145        return "break"
1146
1147    def enter_callback(self, event):
1148        if self.executing and not self.reading:
1149            return # Let the default binding (insert '\n') take over
1150        # If some text is selected, recall the selection
1151        # (but only if this before the I/O mark)
1152        try:
1153            sel = self.text.get("sel.first", "sel.last")
1154            if sel:
1155                if self.text.compare("sel.last", "<=", "iomark"):
1156                    self.recall(sel, event)
1157                    return "break"
1158        except:
1159            pass
1160        # If we're strictly before the line containing iomark, recall
1161        # the current line, less a leading prompt, less leading or
1162        # trailing whitespace
1163        if self.text.compare("insert", "<", "iomark linestart"):
1164            # Check if there's a relevant stdin range -- if so, use it
1165            prev = self.text.tag_prevrange("stdin", "insert")
1166            if prev and self.text.compare("insert", "<", prev[1]):
1167                self.recall(self.text.get(prev[0], prev[1]), event)
1168                return "break"
1169            next = self.text.tag_nextrange("stdin", "insert")
1170            if next and self.text.compare("insert lineend", ">=", next[0]):
1171                self.recall(self.text.get(next[0], next[1]), event)
1172                return "break"
1173            # No stdin mark -- just get the current line, less any prompt
1174            indices = self.text.tag_nextrange("console", "insert linestart")
1175            if indices and \
1176               self.text.compare(indices[0], "<=", "insert linestart"):
1177                self.recall(self.text.get(indices[1], "insert lineend"), event)
1178            else:
1179                self.recall(self.text.get("insert linestart", "insert lineend"), event)
1180            return "break"
1181        # If we're between the beginning of the line and the iomark, i.e.
1182        # in the prompt area, move to the end of the prompt
1183        if self.text.compare("insert", "<", "iomark"):
1184            self.text.mark_set("insert", "iomark")
1185        # If we're in the current input and there's only whitespace
1186        # beyond the cursor, erase that whitespace first
1187        s = self.text.get("insert", "end-1c")
1188        if s and not s.strip():
1189            self.text.delete("insert", "end-1c")
1190        # If we're in the current input before its last line,
1191        # insert a newline right at the insert point
1192        if self.text.compare("insert", "<", "end-1c linestart"):
1193            self.newline_and_indent_event(event)
1194            return "break"
1195        # We're in the last line; append a newline and submit it
1196        self.text.mark_set("insert", "end-1c")
1197        if self.reading:
1198            self.text.insert("insert", "\n")
1199            self.text.see("insert")
1200        else:
1201            self.newline_and_indent_event(event)
1202        self.text.tag_add("stdin", "iomark", "end-1c")
1203        self.text.update_idletasks()
1204        if self.reading:
1205            self.top.quit() # Break out of recursive mainloop() in raw_input()
1206        else:
1207            self.runit()
1208        return "break"
1209
1210    def recall(self, s, event):
1211        # remove leading and trailing empty or whitespace lines
1212        s = re.sub(r'^\s*\n', '' , s)
1213        s = re.sub(r'\n\s*$', '', s)
1214        lines = s.split('\n')
1215        self.text.undo_block_start()
1216        try:
1217            self.text.tag_remove("sel", "1.0", "end")
1218            self.text.mark_set("insert", "end-1c")
1219            prefix = self.text.get("insert linestart", "insert")
1220            if prefix.rstrip().endswith(':'):
1221                self.newline_and_indent_event(event)
1222                prefix = self.text.get("insert linestart", "insert")
1223            self.text.insert("insert", lines[0].strip())
1224            if len(lines) > 1:
1225                orig_base_indent = re.search(r'^([ \t]*)', lines[0]).group(0)
1226                new_base_indent  = re.search(r'^([ \t]*)', prefix).group(0)
1227                for line in lines[1:]:
1228                    if line.startswith(orig_base_indent):
1229                        # replace orig base indentation with new indentation
1230                        line = new_base_indent + line[len(orig_base_indent):]
1231                    self.text.insert('insert', '\n'+line.rstrip())
1232        finally:
1233            self.text.see("insert")
1234            self.text.undo_block_stop()
1235
1236    def runit(self):
1237        line = self.text.get("iomark", "end-1c")
1238        # Strip off last newline and surrounding whitespace.
1239        # (To allow you to hit return twice to end a statement.)
1240        i = len(line)
1241        while i > 0 and line[i-1] in " \t":
1242            i = i-1
1243        if i > 0 and line[i-1] == "\n":
1244            i = i-1
1245        while i > 0 and line[i-1] in " \t":
1246            i = i-1
1247        line = line[:i]
1248        self.interp.runsource(line)
1249
1250    def open_stack_viewer(self, event=None):
1251        if self.interp.rpcclt:
1252            return self.interp.remote_stack_viewer()
1253        try:
1254            sys.last_traceback
1255        except:
1256            tkMessageBox.showerror("No stack trace",
1257                "There is no stack trace yet.\n"
1258                "(sys.last_traceback is not defined)",
1259                parent=self.text)
1260            return
1261        from idlelib.StackViewer import StackBrowser
1262        StackBrowser(self.root, self.flist)
1263
1264    def view_restart_mark(self, event=None):
1265        self.text.see("iomark")
1266        self.text.see("restart")
1267
1268    def restart_shell(self, event=None):
1269        "Callback for Run/Restart Shell Cntl-F6"
1270        self.interp.restart_subprocess(with_cwd=True)
1271
1272    def showprompt(self):
1273        self.resetoutput()
1274        try:
1275            s = str(sys.ps1)
1276        except:
1277            s = ""
1278        self.console.write(s)
1279        self.text.mark_set("insert", "end-1c")
1280        self.set_line_and_column()
1281        self.io.reset_undo()
1282
1283    def resetoutput(self):
1284        source = self.text.get("iomark", "end-1c")
1285        if self.history:
1286            self.history.store(source)
1287        if self.text.get("end-2c") != "\n":
1288            self.text.insert("end-1c", "\n")
1289        self.text.mark_set("iomark", "end-1c")
1290        self.set_line_and_column()
1291        sys.stdout.softspace = 0
1292
1293    def write(self, s, tags=()):
1294        try:
1295            self.text.mark_gravity("iomark", "right")
1296            OutputWindow.write(self, s, tags, "iomark")
1297            self.text.mark_gravity("iomark", "left")
1298        except:
1299            pass
1300        if self.canceled:
1301            self.canceled = 0
1302            if not use_subprocess:
1303                raise KeyboardInterrupt
1304
1305    def rmenu_check_cut(self):
1306        try:
1307            if self.text.compare('sel.first', '<', 'iomark'):
1308                return 'disabled'
1309        except TclError: # no selection, so the index 'sel.first' doesn't exist
1310            return 'disabled'
1311        return super(PyShell, self).rmenu_check_cut()
1312
1313    def rmenu_check_paste(self):
1314        if self.text.compare('insert', '<', 'iomark'):
1315            return 'disabled'
1316        return super(PyShell, self).rmenu_check_paste()
1317
1318class PseudoFile(io.TextIOBase):
1319
1320    def __init__(self, shell, tags, encoding=None):
1321        self.shell = shell
1322        self.tags = tags
1323        self.softspace = 0
1324        self._encoding = encoding
1325
1326    @property
1327    def encoding(self):
1328        return self._encoding
1329
1330    @property
1331    def name(self):
1332        return '<%s>' % self.tags
1333
1334    def isatty(self):
1335        return True
1336
1337
1338class PseudoOutputFile(PseudoFile):
1339
1340    def writable(self):
1341        return True
1342
1343    def write(self, s):
1344        if self.closed:
1345            raise ValueError("write to closed file")
1346        if type(s) not in (unicode, str, bytearray):
1347            # See issue #19481
1348            if isinstance(s, unicode):
1349                s = unicode.__getitem__(s, slice(None))
1350            elif isinstance(s, str):
1351                s = str.__str__(s)
1352            elif isinstance(s, bytearray):
1353                s = bytearray.__str__(s)
1354            else:
1355                raise TypeError('must be string, not ' + type(s).__name__)
1356        return self.shell.write(s, self.tags)
1357
1358
1359class PseudoInputFile(PseudoFile):
1360
1361    def __init__(self, shell, tags, encoding=None):
1362        PseudoFile.__init__(self, shell, tags, encoding)
1363        self._line_buffer = ''
1364
1365    def readable(self):
1366        return True
1367
1368    def read(self, size=-1):
1369        if self.closed:
1370            raise ValueError("read from closed file")
1371        if size is None:
1372            size = -1
1373        elif not isinstance(size, int):
1374            raise TypeError('must be int, not ' + type(size).__name__)
1375        result = self._line_buffer
1376        self._line_buffer = ''
1377        if size < 0:
1378            while True:
1379                line = self.shell.readline()
1380                if not line: break
1381                result += line
1382        else:
1383            while len(result) < size:
1384                line = self.shell.readline()
1385                if not line: break
1386                result += line
1387            self._line_buffer = result[size:]
1388            result = result[:size]
1389        return result
1390
1391    def readline(self, size=-1):
1392        if self.closed:
1393            raise ValueError("read from closed file")
1394        if size is None:
1395            size = -1
1396        elif not isinstance(size, int):
1397            raise TypeError('must be int, not ' + type(size).__name__)
1398        line = self._line_buffer or self.shell.readline()
1399        if size < 0:
1400            size = len(line)
1401        eol = line.find('\n', 0, size)
1402        if eol >= 0:
1403            size = eol + 1
1404        self._line_buffer = line[size:]
1405        return line[:size]
1406
1407    def close(self):
1408        self.shell.close()
1409
1410
1411def fix_x11_paste(root):
1412    "Make paste replace selection on x11.  See issue #5124."
1413    if root._windowingsystem == 'x11':
1414        for cls in 'Text', 'Entry', 'Spinbox':
1415            root.bind_class(
1416                cls,
1417                '<<Paste>>',
1418                'catch {%W delete sel.first sel.last}\n' +
1419                        root.bind_class(cls, '<<Paste>>'))
1420
1421
1422usage_msg = """\
1423
1424USAGE: idle  [-deins] [-t title] [file]*
1425       idle  [-dns] [-t title] (-c cmd | -r file) [arg]*
1426       idle  [-dns] [-t title] - [arg]*
1427
1428  -h         print this help message and exit
1429  -n         run IDLE without a subprocess (see Help/IDLE Help for details)
1430
1431The following options will override the IDLE 'settings' configuration:
1432
1433  -e         open an edit window
1434  -i         open a shell window
1435
1436The following options imply -i and will open a shell:
1437
1438  -c cmd     run the command in a shell, or
1439  -r file    run script from file
1440
1441  -d         enable the debugger
1442  -s         run $IDLESTARTUP or $PYTHONSTARTUP before anything else
1443  -t title   set title of shell window
1444
1445A default edit window will be bypassed when -c, -r, or - are used.
1446
1447[arg]* are passed to the command (-c) or script (-r) in sys.argv[1:].
1448
1449Examples:
1450
1451idle
1452        Open an edit window or shell depending on IDLE's configuration.
1453
1454idle foo.py foobar.py
1455        Edit the files, also open a shell if configured to start with shell.
1456
1457idle -est "Baz" foo.py
1458        Run $IDLESTARTUP or $PYTHONSTARTUP, edit foo.py, and open a shell
1459        window with the title "Baz".
1460
1461idle -c "import sys; print sys.argv" "foo"
1462        Open a shell window and run the command, passing "-c" in sys.argv[0]
1463        and "foo" in sys.argv[1].
1464
1465idle -d -s -r foo.py "Hello World"
1466        Open a shell window, run a startup script, enable the debugger, and
1467        run foo.py, passing "foo.py" in sys.argv[0] and "Hello World" in
1468        sys.argv[1].
1469
1470echo "import sys; print sys.argv" | idle - "foobar"
1471        Open a shell window, run the script piped in, passing '' in sys.argv[0]
1472        and "foobar" in sys.argv[1].
1473"""
1474
1475def main():
1476    global flist, root, use_subprocess
1477
1478    capture_warnings(True)
1479    use_subprocess = True
1480    enable_shell = False
1481    enable_edit = False
1482    debug = False
1483    cmd = None
1484    script = None
1485    startup = False
1486    try:
1487        opts, args = getopt.getopt(sys.argv[1:], "c:deihnr:st:")
1488    except getopt.error as msg:
1489        print("Error: %s\n%s" % (msg, usage_msg), file=sys.stderr)
1490        sys.exit(2)
1491    for o, a in opts:
1492        if o == '-c':
1493            cmd = a
1494            enable_shell = True
1495        if o == '-d':
1496            debug = True
1497            enable_shell = True
1498        if o == '-e':
1499            enable_edit = True
1500        if o == '-h':
1501            sys.stdout.write(usage_msg)
1502            sys.exit()
1503        if o == '-i':
1504            enable_shell = True
1505        if o == '-n':
1506            use_subprocess = False
1507        if o == '-r':
1508            script = a
1509            if os.path.isfile(script):
1510                pass
1511            else:
1512                print("No script file: ", script, file=sys.stderr)
1513                sys.exit()
1514            enable_shell = True
1515        if o == '-s':
1516            startup = True
1517            enable_shell = True
1518        if o == '-t':
1519            PyShell.shell_title = a
1520            enable_shell = True
1521    if args and args[0] == '-':
1522        cmd = sys.stdin.read()
1523        enable_shell = True
1524    # process sys.argv and sys.path:
1525    for i in range(len(sys.path)):
1526        sys.path[i] = os.path.abspath(sys.path[i])
1527    if args and args[0] == '-':
1528        sys.argv = [''] + args[1:]
1529    elif cmd:
1530        sys.argv = ['-c'] + args
1531    elif script:
1532        sys.argv = [script] + args
1533    elif args:
1534        enable_edit = True
1535        pathx = []
1536        for filename in args:
1537            pathx.append(os.path.dirname(filename))
1538        for dir in pathx:
1539            dir = os.path.abspath(dir)
1540            if dir not in sys.path:
1541                sys.path.insert(0, dir)
1542    else:
1543        dir = os.getcwd()
1544        if not dir in sys.path:
1545            sys.path.insert(0, dir)
1546    # check the IDLE settings configuration (but command line overrides)
1547    edit_start = idleConf.GetOption('main', 'General',
1548                                    'editor-on-startup', type='bool')
1549    enable_edit = enable_edit or edit_start
1550    enable_shell = enable_shell or not enable_edit
1551
1552    # start editor and/or shell windows:
1553    root = Tk(className="Idle")
1554    root.withdraw()
1555
1556    # set application icon
1557    icondir = os.path.join(os.path.dirname(__file__), 'Icons')
1558    if system() == 'Windows':
1559        iconfile = os.path.join(icondir, 'idle.ico')
1560        root.wm_iconbitmap(default=iconfile)
1561    elif TkVersion >= 8.5:
1562        ext = '.png' if TkVersion >= 8.6 else '.gif'
1563        iconfiles = [os.path.join(icondir, 'idle_%d%s' % (size, ext))
1564                     for size in (16, 32, 48)]
1565        icons = [PhotoImage(file=iconfile) for iconfile in iconfiles]
1566        root.tk.call('wm', 'iconphoto', str(root), "-default", *icons)
1567
1568    fixwordbreaks(root)
1569    fix_x11_paste(root)
1570    flist = PyShellFileList(root)
1571    macosxSupport.setupApp(root, flist)
1572
1573    if macosxSupport.isAquaTk():
1574        # There are some screwed up <2> class bindings for text
1575        # widgets defined in Tk which we need to do away with.
1576        # See issue #24801.
1577        root.unbind_class('Text', '<B2>')
1578        root.unbind_class('Text', '<B2-Motion>')
1579        root.unbind_class('Text', '<<PasteSelection>>')
1580
1581    if enable_edit:
1582        if not (cmd or script):
1583            for filename in args[:]:
1584                if flist.open(filename) is None:
1585                    # filename is a directory actually, disconsider it
1586                    args.remove(filename)
1587            if not args:
1588                flist.new()
1589
1590    if enable_shell:
1591        shell = flist.open_shell()
1592        if not shell:
1593            return # couldn't open shell
1594        if macosxSupport.isAquaTk() and flist.dict:
1595            # On OSX: when the user has double-clicked on a file that causes
1596            # IDLE to be launched the shell window will open just in front of
1597            # the file she wants to see. Lower the interpreter window when
1598            # there are open files.
1599            shell.top.lower()
1600    else:
1601        shell = flist.pyshell
1602
1603    # Handle remaining options. If any of these are set, enable_shell
1604    # was set also, so shell must be true to reach here.
1605    if debug:
1606        shell.open_debugger()
1607    if startup:
1608        filename = os.environ.get("IDLESTARTUP") or \
1609                   os.environ.get("PYTHONSTARTUP")
1610        if filename and os.path.isfile(filename):
1611            shell.interp.execfile(filename)
1612    if cmd or script:
1613        shell.interp.runcommand("""if 1:
1614            import sys as _sys
1615            _sys.argv = %r
1616            del _sys
1617            \n""" % (sys.argv,))
1618        if cmd:
1619            shell.interp.execsource(cmd)
1620        elif script:
1621            shell.interp.prepend_syspath(script)
1622            shell.interp.execfile(script)
1623    elif shell:
1624        # If there is a shell window and no cmd or script in progress,
1625        # check for problematic OS X Tk versions and print a warning
1626        # message in the IDLE shell window; this is less intrusive
1627        # than always opening a separate window.
1628        tkversionwarning = macosxSupport.tkVersionWarning(root)
1629        if tkversionwarning:
1630            shell.interp.runcommand("print('%s')" % tkversionwarning)
1631
1632    while flist.inversedict:  # keep IDLE running while files are open.
1633        root.mainloop()
1634    root.destroy()
1635    capture_warnings(False)
1636
1637if __name__ == "__main__":
1638    sys.modules['PyShell'] = sys.modules['__main__']
1639    main()
1640
1641capture_warnings(False)  # Make sure turned off; see issue 18081
1642