PyShell.py 50 KB
Newer Older
David Scherer's avatar
David Scherer committed
1 2 3
#! /usr/bin/env python

import os
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
4
import os.path
David Scherer's avatar
David Scherer committed
5 6 7 8
import sys
import string
import getopt
import re
Chui Tey's avatar
Chui Tey committed
9 10
import socket
import time
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
11
import threading
Chui Tey's avatar
Chui Tey committed
12
import traceback
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
13
import types
14
import macosxSupport
David Scherer's avatar
David Scherer committed
15 16 17 18

import linecache
from code import InteractiveInterpreter

19 20 21
try:
    from Tkinter import *
except ImportError:
22 23
    print("** IDLE can't import Tkinter.  " \
                           "Your Python may not be configured for Tk. **", file=sys.__stderr__)
24
    sys.exit(1)
David Scherer's avatar
David Scherer committed
25 26 27 28 29 30
import tkMessageBox

from EditorWindow import EditorWindow, fixwordbreaks
from FileList import FileList
from ColorDelegator import ColorDelegator
from UndoDelegator import UndoDelegator
31
from OutputWindow import OutputWindow
32
from configHandler import idleConf
David Scherer's avatar
David Scherer committed
33 34
import idlever

Chui Tey's avatar
Chui Tey committed
35
import rpc
36
import Debugger
37
import RemoteDebugger
Chui Tey's avatar
Chui Tey committed
38

39
IDENTCHARS = string.ascii_letters + string.digits + "_"
40
LOCALHOST = '127.0.0.1'
41

42 43 44 45 46
try:
    from signal import SIGTERM
except ImportError:
    SIGTERM = 15

47 48 49 50 51 52
# Override warnings module to write to warning_stream.  Initialize to send IDLE
# internal warnings to the console.  ScriptBinding.check_syntax() will
# temporarily redirect the stream to the shell window to display warnings when
# checking user's code.
global warning_stream
warning_stream = sys.__stderr__
Chui Tey's avatar
Chui Tey committed
53 54 55 56 57 58
try:
    import warnings
except ImportError:
    pass
else:
    def idle_showwarning(message, category, filename, lineno):
59 60 61 62 63
        file = warning_stream
        try:
            file.write(warnings.formatwarning(message, category, filename, lineno))
        except IOError:
            pass  ## file (probably __stderr__) is invalid, warning dropped.
Chui Tey's avatar
Chui Tey committed
64
    warnings.showwarning = idle_showwarning
65 66 67 68 69 70 71 72 73 74
    def idle_formatwarning(message, category, filename, lineno):
        """Format warnings the IDLE way"""
        s = "\nWarning (from warnings module):\n"
        s += '  File \"%s\", line %s\n' % (filename, lineno)
        line = linecache.getline(filename, lineno).strip()
        if line:
            s += "    %s\n" % line
        s += "%s: %s\n>>> " % (category.__name__, message)
        return s
    warnings.formatwarning = idle_formatwarning
Chui Tey's avatar
Chui Tey committed
75

76 77
def extended_linecache_checkcache(filename=None,
                                  orig_checkcache=linecache.checkcache):
78 79
    """Extend linecache.checkcache to preserve the <pyshell#...> entries

80 81 82 83 84 85
    Rather than repeating the linecache code, patch it to save the
    <pyshell#...> entries, call the original linecache.checkcache()
    (which destroys them), and then restore the saved entries.

    orig_checkcache is bound at definition time to the original
    method, allowing it to be patched.
86 87

    """
David Scherer's avatar
David Scherer committed
88 89 90 91 92 93 94
    cache = linecache.cache
    save = {}
    for filename in cache.keys():
        if filename[:1] + filename[-1:] == '<>':
            save[filename] = cache[filename]
    orig_checkcache()
    cache.update(save)
95

96 97
# Patch linecache.checkcache():
linecache.checkcache = extended_linecache_checkcache
David Scherer's avatar
David Scherer committed
98

99

David Scherer's avatar
David Scherer committed
100
class PyShellEditorWindow(EditorWindow):
101
    "Regular text edit window in IDLE, supports breakpoints"
102

David Scherer's avatar
David Scherer committed
103
    def __init__(self, *args):
104
        self.breakpoints = []
105
        EditorWindow.__init__(self, *args)
David Scherer's avatar
David Scherer committed
106
        self.text.bind("<<set-breakpoint-here>>", self.set_breakpoint_here)
107
        self.text.bind("<<clear-breakpoint-here>>", self.clear_breakpoint_here)
David Scherer's avatar
David Scherer committed
108 109
        self.text.bind("<<open-python-shell>>", self.flist.open_shell)

Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
110 111
        self.breakpointPath = os.path.join(idleConf.GetUserCfgDir(),
                                           'breakpoints.lst')
112 113
        # whenever a file is changed, restore breakpoints
        if self.io.filename: self.restore_file_breaks()
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
114 115
        def filename_changed_hook(old_hook=self.io.filename_change_hook,
                                  self=self):
116 117 118 119
            self.restore_file_breaks()
            old_hook()
        self.io.set_filename_change_hook(filename_changed_hook)

120 121
    rmenu_specs = [("Set Breakpoint", "<<set-breakpoint-here>>"),
                   ("Clear Breakpoint", "<<clear-breakpoint-here>>")]
David Scherer's avatar
David Scherer committed
122

123
    def set_breakpoint(self, lineno):
124 125
        text = self.text
        filename = self.io.filename
126
        text.tag_add("BREAK", "%d.0" % lineno, "%d.0" % (lineno+1))
127 128
        try:
            i = self.breakpoints.index(lineno)
129
        except ValueError:  # only add if missing, i.e. do once
130 131 132 133 134 135
            self.breakpoints.append(lineno)
        try:    # update the subprocess debugger
            debug = self.flist.pyshell.interp.debugger
            debug.set_breakpoint_here(filename, lineno)
        except: # but debugger may not be active right now....
            pass
David Scherer's avatar
David Scherer committed
136

137 138 139 140 141 142 143 144 145
    def set_breakpoint_here(self, event=None):
        text = self.text
        filename = self.io.filename
        if not filename:
            text.bell()
            return
        lineno = int(float(text.index("insert")))
        self.set_breakpoint(lineno)

146
    def clear_breakpoint_here(self, event=None):
147 148 149 150
        text = self.text
        filename = self.io.filename
        if not filename:
            text.bell()
151
            return
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
        lineno = int(float(text.index("insert")))
        try:
            self.breakpoints.remove(lineno)
        except:
            pass
        text.tag_remove("BREAK", "insert linestart",\
                        "insert lineend +1char")
        try:
            debug = self.flist.pyshell.interp.debugger
            debug.clear_breakpoint_here(filename, lineno)
        except:
            pass

    def clear_file_breaks(self):
        if self.breakpoints:
            text = self.text
            filename = self.io.filename
            if not filename:
                text.bell()
                return
            self.breakpoints = []
            text.tag_remove("BREAK", "1.0", END)
            try:
                debug = self.flist.pyshell.interp.debugger
                debug.clear_file_breaks(filename)
            except:
                pass

180
    def store_file_breaks(self):
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
181 182 183 184 185 186
        "Save breakpoints when file is saved"
        # XXX 13 Dec 2002 KBK Currently the file must be saved before it can
        #     be run.  The breaks are saved at that time.  If we introduce
        #     a temporary file save feature the save breaks functionality
        #     needs to be re-verified, since the breaks at the time the
        #     temp file is created may differ from the breaks at the last
187 188 189 190
        #     permanent save of the file.  Currently, a break introduced
        #     after a save will be effective, but not persistent.
        #     This is necessary to keep the saved breaks synched with the
        #     saved file.
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
191 192 193 194 195 196 197 198 199 200 201 202 203 204
        #
        #     Breakpoints are set as tagged ranges in the text.  Certain
        #     kinds of edits cause these ranges to be deleted: Inserting
        #     or deleting a line just before a breakpoint, and certain
        #     deletions prior to a breakpoint.  These issues need to be
        #     investigated and understood.  It's not clear if they are
        #     Tk issues or IDLE issues, or whether they can actually
        #     be fixed.  Since a modified file has to be saved before it is
        #     run, and since self.breakpoints (from which the subprocess
        #     debugger is loaded) is updated during the save, the visible
        #     breaks stay synched with the subprocess even if one of these
        #     unexpected breakpoint deletions occurs.
        breaks = self.breakpoints
        filename = self.io.filename
205
        try:
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
206
            lines = open(self.breakpointPath,"r").readlines()
207
        except IOError:
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
208 209
            lines = []
        new_file = open(self.breakpointPath,"w")
210
        for line in lines:
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
211
            if not line.startswith(filename + '='):
212
                new_file.write(line)
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
213 214 215 216
        self.update_breakpoints()
        breaks = self.breakpoints
        if breaks:
            new_file.write(filename + '=' + str(breaks) + '\n')
217 218 219 220
        new_file.close()

    def restore_file_breaks(self):
        self.text.update()   # this enables setting "BREAK" tags to be visible
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
221 222 223
        filename = self.io.filename
        if filename is None:
            return
224
        if os.path.isfile(self.breakpointPath):
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
225
            lines = open(self.breakpointPath,"r").readlines()
226
            for line in lines:
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
227
                if line.startswith(filename + '='):
228
                    breakpoint_linenumbers = eval(line[len(filename)+1:])
229 230
                    for breakpoint_linenumber in breakpoint_linenumbers:
                        self.set_breakpoint(breakpoint_linenumber)
231

Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
232 233
    def update_breakpoints(self):
        "Retrieves all the breakpoints in the current window"
234
        text = self.text
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
235 236 237 238 239 240 241 242 243 244 245 246 247 248
        ranges = text.tag_ranges("BREAK")
        linenumber_list = self.ranges_to_linenumbers(ranges)
        self.breakpoints = linenumber_list

    def ranges_to_linenumbers(self, ranges):
        lines = []
        for index in range(0, len(ranges), 2):
            lineno = int(float(ranges[index]))
            end = int(float(ranges[index+1]))
            while lineno < end:
                lines.append(lineno)
                lineno += 1
        return lines

249
# XXX 13 Dec 2002 KBK Not used currently
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
250 251 252 253 254
#    def saved_change_hook(self):
#        "Extend base method - clear breaks if module is modified"
#        if not self.get_saved():
#            self.clear_file_breaks()
#        EditorWindow.saved_change_hook(self)
255 256 257 258 259

    def _close(self):
        "Extend base method - clear breaks when module is closed"
        self.clear_file_breaks()
        EditorWindow._close(self)
260

David Scherer's avatar
David Scherer committed
261 262

class PyShellFileList(FileList):
263
    "Extend base class: IDLE supports a shell and breakpoints"
David Scherer's avatar
David Scherer committed
264

265 266
    # override FileList's class variable, instances return PyShellEditorWindow
    # instead of EditorWindow when new edit windows are created.
David Scherer's avatar
David Scherer committed
267 268 269 270 271 272
    EditorWindow = PyShellEditorWindow

    pyshell = None

    def open_shell(self, event=None):
        if self.pyshell:
273
            self.pyshell.top.wakeup()
David Scherer's avatar
David Scherer committed
274 275
        else:
            self.pyshell = PyShell(self)
276 277 278
            if self.pyshell:
                if not self.pyshell.begin():
                    return None
David Scherer's avatar
David Scherer committed
279 280 281 282
        return self.pyshell


class ModifiedColorDelegator(ColorDelegator):
283
    "Extend base class: colorizer for the shell window itself"
284

285 286 287
    def __init__(self):
        ColorDelegator.__init__(self)
        self.LoadTagDefs()
David Scherer's avatar
David Scherer committed
288 289 290 291 292

    def recolorize_main(self):
        self.tag_remove("TODO", "1.0", "iomark")
        self.tag_add("SYNC", "1.0", "iomark")
        ColorDelegator.recolorize_main(self)
293

294 295 296 297 298 299 300 301 302 303
    def LoadTagDefs(self):
        ColorDelegator.LoadTagDefs(self)
        theme = idleConf.GetOption('main','Theme','name')
        self.tagdefs.update({
            "stdin": {'background':None,'foreground':None},
            "stdout": idleConf.GetHighlight(theme, "stdout"),
            "stderr": idleConf.GetHighlight(theme, "stderr"),
            "console": idleConf.GetHighlight(theme, "console"),
            None: idleConf.GetHighlight(theme, "normal"),
        })
David Scherer's avatar
David Scherer committed
304 305

class ModifiedUndoDelegator(UndoDelegator):
306
    "Extend base class: forbid insert/delete before the I/O mark"
David Scherer's avatar
David Scherer committed
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325

    def insert(self, index, chars, tags=None):
        try:
            if self.delegate.compare(index, "<", "iomark"):
                self.delegate.bell()
                return
        except TclError:
            pass
        UndoDelegator.insert(self, index, chars, tags)

    def delete(self, index1, index2=None):
        try:
            if self.delegate.compare(index1, "<", "iomark"):
                self.delegate.bell()
                return
        except TclError:
            pass
        UndoDelegator.delete(self, index1, index2)

Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
326 327 328 329 330 331 332

class MyRPCClient(rpc.RPCClient):

    def handle_EOF(self):
        "Override the base class - just re-raise EOFError"
        raise EOFError

333

David Scherer's avatar
David Scherer committed
334 335 336 337 338 339
class ModifiedInterpreter(InteractiveInterpreter):

    def __init__(self, tkconsole):
        self.tkconsole = tkconsole
        locals = sys.modules['__main__'].__dict__
        InteractiveInterpreter.__init__(self, locals=locals)
340
        self.save_warnings_filters = None
341
        self.restarting = False
342
        self.subprocess_arglist = self.build_subprocess_arglist()
David Scherer's avatar
David Scherer committed
343

Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
344
    port = 8833
Chui Tey's avatar
Chui Tey committed
345 346 347
    rpcclt = None
    rpcpid = None

348
    def spawn_subprocess(self):
349
        args = self.subprocess_arglist
350
        self.rpcpid = os.spawnv(os.P_NOWAIT, sys.executable, args)
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
351

352
    def build_subprocess_arglist(self):
353
        w = ['-W' + s for s in sys.warnoptions]
354 355
        if 1/2 > 0: # account for new division
            w.append('-Qnew')
356 357 358
        # Maybe IDLE is installed and is being accessed via sys.path,
        # or maybe it's not installed and the idle.py script is being
        # run from the IDLE source directory.
359 360
        del_exitf = idleConf.GetOption('main', 'General', 'delete-exitfunc',
                                       default=False, type='bool')
361
        if __name__ == 'idlelib.PyShell':
362
            command = "__import__('idlelib.run').run.main(%r)" % (del_exitf,)
363
        else:
364
            command = "__import__('run').main(%r)" % (del_exitf,)
365 366 367 368 369 370
        if sys.platform[:3] == 'win' and ' ' in sys.executable:
            # handle embedded space in path by quoting the argument
            decorated_exec = '"%s"' % sys.executable
        else:
            decorated_exec = sys.executable
        return [decorated_exec] + w + ["-c", command, str(self.port)]
371

Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
372
    def start_subprocess(self):
373 374 375
        # spawning first avoids passing a listening socket to the subprocess
        self.spawn_subprocess()
        #time.sleep(20) # test to simulate GUI not accepting connection
376
        addr = (LOCALHOST, self.port)
377
        # Idle starts listening for connection on localhost
378
        for i in range(3):
Chui Tey's avatar
Chui Tey committed
379 380
            time.sleep(i)
            try:
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
381
                self.rpcclt = MyRPCClient(addr)
Chui Tey's avatar
Chui Tey committed
382
                break
383
            except socket.error as err:
384
                pass
Chui Tey's avatar
Chui Tey committed
385
        else:
386 387
            self.display_port_binding_error()
            return None
388
        # Accept the connection from the Python execution server
389 390 391
        self.rpcclt.listening_sock.settimeout(10)
        try:
            self.rpcclt.accept()
392
        except socket.timeout as err:
393 394
            self.display_no_subprocess_error()
            return None
395 396 397
        self.rpcclt.register("stdin", self.tkconsole)
        self.rpcclt.register("stdout", self.tkconsole.stdout)
        self.rpcclt.register("stderr", self.tkconsole.stderr)
Chui Tey's avatar
Chui Tey committed
398
        self.rpcclt.register("flist", self.tkconsole.flist)
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
399
        self.rpcclt.register("linecache", linecache)
400
        self.rpcclt.register("interp", self)
401
        self.transfer_path()
Chui Tey's avatar
Chui Tey committed
402
        self.poll_subprocess()
403
        return self.rpcclt
Chui Tey's avatar
Chui Tey committed
404

Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
405
    def restart_subprocess(self):
406
        if self.restarting:
407
            return self.rpcclt
408
        self.restarting = True
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
409
        # close only the subprocess debugger
410 411
        debug = self.getdebugger()
        if debug:
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
412
            try:
413
                # Only close subprocess debugger, don't unregister gui_adap!
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
414 415 416 417
                RemoteDebugger.close_subprocess_debugger(self.rpcclt)
            except:
                pass
        # Kill subprocess, spawn a new one, accept connection.
418 419
        self.rpcclt.close()
        self.unix_terminate()
420
        console = self.tkconsole
421
        was_executing = console.executing
422
        console.executing = False
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
423
        self.spawn_subprocess()
424 425
        try:
            self.rpcclt.accept()
426
        except socket.timeout as err:
427 428
            self.display_no_subprocess_error()
            return None
429
        self.transfer_path()
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
430
        # annotate restart in shell window and mark it
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
431
        console.text.delete("iomark", "end-1c")
432 433 434
        if was_executing:
            console.write('\n')
            console.showprompt()
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
435 436 437 438
        halfbar = ((int(console.width) - 16) // 2) * '='
        console.write(halfbar + ' RESTART ' + halfbar)
        console.text.mark_set("restart", "end-1c")
        console.text.mark_gravity("restart", "left")
439
        console.showprompt()
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
440
        # restart subprocess debugger
441
        if debug:
442
            # Restarted debugger connects to current instance of debug GUI
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
443
            gui = RemoteDebugger.restart_subprocess_debugger(self.rpcclt)
444 445
            # reload remote debugger breakpoints for all PyShellEditWindows
            debug.load_breakpoints()
446
        self.restarting = False
447
        return self.rpcclt
448

Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
449
    def __request_interrupt(self):
450
        self.rpcclt.remotecall("exec", "interrupt_the_server", (), {})
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
451 452

    def interrupt_subprocess(self):
453
        threading.Thread(target=self.__request_interrupt).start()
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
454

455
    def kill_subprocess(self):
456 457 458 459
        try:
            self.rpcclt.close()
        except AttributeError:  # no socket
            pass
460 461 462
        self.unix_terminate()
        self.tkconsole.executing = False
        self.rpcclt = None
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
463

464 465 466 467 468 469 470 471 472 473 474 475 476
    def unix_terminate(self):
        "UNIX: make sure subprocess is terminated and collect status"
        if hasattr(os, 'kill'):
            try:
                os.kill(self.rpcpid, SIGTERM)
            except OSError:
                # process already terminated:
                return
            else:
                try:
                    os.waitpid(self.rpcpid, 0)
                except OSError:
                    return
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
477

478 479 480
    def transfer_path(self):
        self.runcommand("""if 1:
        import sys as _sys
481
        _sys.path = %r
482
        del _sys
483
        \n""" % (sys.path,))
484

Chui Tey's avatar
Chui Tey committed
485 486 487 488 489 490
    active_seq = None

    def poll_subprocess(self):
        clt = self.rpcclt
        if clt is None:
            return
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
491
        try:
492 493 494 495
            response = clt.pollresponse(self.active_seq, wait=0.05)
        except (EOFError, IOError, KeyboardInterrupt):
            # lost connection or subprocess terminated itself, restart
            # [the KBI is from rpc.SocketIO.handle_EOF()]
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
496 497
            if self.tkconsole.closing:
                return
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
498 499
            response = None
            self.restart_subprocess()
Chui Tey's avatar
Chui Tey committed
500 501 502 503
        if response:
            self.tkconsole.resetoutput()
            self.active_seq = None
            how, what = response
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
504
            console = self.tkconsole.console
Chui Tey's avatar
Chui Tey committed
505 506
            if how == "OK":
                if what is not None:
507
                    print(repr(what), file=console)
Chui Tey's avatar
Chui Tey committed
508 509 510 511
            elif how == "EXCEPTION":
                if self.tkconsole.getvar("<<toggle-jit-stack-viewer>>"):
                    self.remote_stack_viewer()
            elif how == "ERROR":
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
512
                errmsg = "PyShell.ModifiedInterpreter: Subprocess ERROR:\n"
513 514
                print(errmsg, what, file=sys.__stderr__)
                print(errmsg, what, file=console)
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
515
            # we received a response to the currently active seq number:
516 517 518 519
            try:
                self.tkconsole.endexecuting()
            except AttributeError:  # shell may have closed
                pass
520 521 522 523
        # Reschedule myself
        if not self.tkconsole.closing:
            self.tkconsole.text.after(self.tkconsole.pollinterval,
                                      self.poll_subprocess)
Chui Tey's avatar
Chui Tey committed
524

525 526 527 528 529 530 531 532
    debugger = None

    def setdebugger(self, debugger):
        self.debugger = debugger

    def getdebugger(self):
        return self.debugger

533 534 535 536 537 538 539 540 541 542 543 544 545
    def open_remote_stack_viewer(self):
        """Initiate the remote stack viewer from a separate thread.

        This method is called from the subprocess, and by returning from this
        method we allow the subprocess to unblock.  After a bit the shell
        requests the subprocess to open the remote stack viewer which returns a
        static object looking at the last exceptiopn.  It is queried through
        the RPC mechanism.

        """
        self.tkconsole.text.after(300, self.remote_stack_viewer)
        return

Chui Tey's avatar
Chui Tey committed
546 547
    def remote_stack_viewer(self):
        import RemoteObjectBrowser
548
        oid = self.rpcclt.remotequeue("exec", "stackviewer", ("flist",), {})
Chui Tey's avatar
Chui Tey committed
549 550 551 552 553 554
        if oid is None:
            self.tkconsole.root.bell()
            return
        item = RemoteObjectBrowser.StubObjectTreeItem(self.rpcclt, oid)
        from TreeWidget import ScrolledCanvas, TreeNode
        top = Toplevel(self.tkconsole.root)
555 556 557
        theme = idleConf.GetOption('main','Theme','name')
        background = idleConf.GetHighlight(theme, 'normal')['background']
        sc = ScrolledCanvas(top, bg=background, highlightthickness=0)
Chui Tey's avatar
Chui Tey committed
558 559 560 561 562
        sc.frame.pack(expand=1, fill="both")
        node = TreeNode(sc.canvas, None, item)
        node.expand()
        # XXX Should GC the remote tree when closing the window

David Scherer's avatar
David Scherer committed
563 564 565
    gid = 0

    def execsource(self, source):
566
        "Like runsource() but assumes complete exec source"
David Scherer's avatar
David Scherer committed
567 568 569 570
        filename = self.stuffsource(source)
        self.execfile(filename, source)

    def execfile(self, filename, source=None):
571
        "Execute an existing file"
David Scherer's avatar
David Scherer committed
572 573 574 575 576 577
        if source is None:
            source = open(filename, "r").read()
        try:
            code = compile(source, filename, "exec")
        except (OverflowError, SyntaxError):
            self.tkconsole.resetoutput()
578
            tkerr = self.tkconsole.stderr
579 580
            print('*** Error in script or command!\n', file=tkerr)
            print('Traceback (most recent call last):', file=tkerr)
David Scherer's avatar
David Scherer committed
581
            InteractiveInterpreter.showsyntaxerror(self, filename)
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
582
            self.tkconsole.showprompt()
David Scherer's avatar
David Scherer committed
583 584 585 586
        else:
            self.runcode(code)

    def runsource(self, source):
587
        "Extend base class method: Stuff the source in the line cache first"
David Scherer's avatar
David Scherer committed
588 589
        filename = self.stuffsource(source)
        self.more = 0
590 591
        self.save_warnings_filters = warnings.filters[:]
        warnings.filterwarnings(action="error", category=SyntaxWarning)
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
592 593 594 595 596 597
        if isinstance(source, types.UnicodeType):
            import IOBinding
            try:
                source = source.encode(IOBinding.encoding)
            except UnicodeError:
                self.tkconsole.resetoutput()
598
                self.write("Unsupported characters in input\n")
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
599
                return
600
        try:
601 602
            # InteractiveInterpreter.runsource() calls its runcode() method,
            # which is overridden (see below)
603 604 605 606 607
            return InteractiveInterpreter.runsource(self, source, filename)
        finally:
            if self.save_warnings_filters is not None:
                warnings.filters[:] = self.save_warnings_filters
                self.save_warnings_filters = None
David Scherer's avatar
David Scherer committed
608 609

    def stuffsource(self, source):
610
        "Stuff source in the filename cache"
David Scherer's avatar
David Scherer committed
611 612
        filename = "<pyshell#%d>" % self.gid
        self.gid = self.gid + 1
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
613
        lines = source.split("\n")
David Scherer's avatar
David Scherer committed
614 615
        linecache.cache[filename] = len(source)+1, 0, lines, filename
        return filename
616

617 618 619
    def prepend_syspath(self, filename):
        "Prepend sys.path with file's directory if not already included"
        self.runcommand("""if 1:
620
            _filename = %r
621 622 623 624 625 626
            import sys as _sys
            from os.path import dirname as _dirname
            _dir = _dirname(_filename)
            if not _dir in _sys.path:
                _sys.path.insert(0, _dir)
            del _filename, _sys, _dirname, _dir
627
            \n""" % (filename,))
628

David Scherer's avatar
David Scherer committed
629
    def showsyntaxerror(self, filename=None):
630 631 632 633 634 635
        """Extend base class method: Add Colorizing

        Color the offending position instead of printing it and pointing at it
        with a caret.

        """
David Scherer's avatar
David Scherer committed
636 637
        text = self.tkconsole.text
        stuff = self.unpackerror()
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
638 639 640 641 642 643 644 645 646 647 648 649
        if stuff:
            msg, lineno, offset, line = stuff
            if lineno == 1:
                pos = "iomark + %d chars" % (offset-1)
            else:
                pos = "iomark linestart + %d lines + %d chars" % \
                      (lineno-1, offset-1)
            text.tag_add("ERROR", pos)
            text.see(pos)
            char = text.get(pos)
            if char and char in IDENTCHARS:
                text.tag_add("ERROR", pos + " wordstart", pos)
David Scherer's avatar
David Scherer committed
650
            self.tkconsole.resetoutput()
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
651
            self.write("SyntaxError: %s\n" % str(msg))
David Scherer's avatar
David Scherer committed
652
        else:
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
653 654 655
            self.tkconsole.resetoutput()
            InteractiveInterpreter.showsyntaxerror(self, filename)
        self.tkconsole.showprompt()
David Scherer's avatar
David Scherer committed
656 657 658 659 660 661 662

    def unpackerror(self):
        type, value, tb = sys.exc_info()
        ok = type is SyntaxError
        if ok:
            try:
                msg, (dummy_filename, lineno, offset, line) = value
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
663 664
                if not offset:
                    offset = 0
David Scherer's avatar
David Scherer committed
665 666 667 668 669 670 671 672
            except:
                ok = 0
        if ok:
            return msg, lineno, offset, line
        else:
            return None

    def showtraceback(self):
673
        "Extend base class method to reset output properly"
David Scherer's avatar
David Scherer committed
674 675 676
        self.tkconsole.resetoutput()
        self.checklinecache()
        InteractiveInterpreter.showtraceback(self)
Chui Tey's avatar
Chui Tey committed
677 678
        if self.tkconsole.getvar("<<toggle-jit-stack-viewer>>"):
            self.tkconsole.open_stack_viewer()
David Scherer's avatar
David Scherer committed
679 680 681 682 683 684 685

    def checklinecache(self):
        c = linecache.cache
        for key in c.keys():
            if key[:1] + key[-1:] != "<>":
                del c[key]

Chui Tey's avatar
Chui Tey committed
686
    def runcommand(self, code):
687
        "Run the code without invoking the debugger"
Chui Tey's avatar
Chui Tey committed
688 689
        # The code better not raise an exception!
        if self.tkconsole.executing:
690
            self.display_executing_dialog()
Chui Tey's avatar
Chui Tey committed
691 692
            return 0
        if self.rpcclt:
693
            self.rpcclt.remotequeue("exec", "runcode", (code,), {})
Chui Tey's avatar
Chui Tey committed
694
        else:
695
            exec(code, self.locals)
Chui Tey's avatar
Chui Tey committed
696 697
        return 1

David Scherer's avatar
David Scherer committed
698
    def runcode(self, code):
699
        "Override base class method"
Chui Tey's avatar
Chui Tey committed
700
        if self.tkconsole.executing:
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
701
            self.interp.restart_subprocess()
Chui Tey's avatar
Chui Tey committed
702
        self.checklinecache()
703 704 705
        if self.save_warnings_filters is not None:
            warnings.filters[:] = self.save_warnings_filters
            self.save_warnings_filters = None
David Scherer's avatar
David Scherer committed
706 707
        debugger = self.debugger
        try:
708
            self.tkconsole.beginexecuting()
709 710 711 712 713 714 715 716 717 718 719 720 721 722
            if not debugger and self.rpcclt is not None:
                self.active_seq = self.rpcclt.asyncqueue("exec", "runcode",
                                                        (code,), {})
            elif debugger:
                debugger.run(code, self.locals)
            else:
                exec(code, self.locals)
        except SystemExit:
            if not self.tkconsole.closing:
                if tkMessageBox.askyesno(
                    "Exit?",
                    "Do you want to exit altogether?",
                    default="yes",
                    master=self.tkconsole.text):
723
                    raise
724
                else:
725
                    self.showtraceback()
726 727 728 729 730 731
            else:
                raise
        except:
            if use_subprocess:
                print("IDLE internal error in runcode()",
                      file=self.tkconsole.stderr)
David Scherer's avatar
David Scherer committed
732
                self.showtraceback()
733 734 735 736 737 738 739
                self.tkconsole.endexecuting()
            else:
                if self.tkconsole.canceled:
                    self.tkconsole.canceled = False
                    print("KeyboardInterrupt", file=self.tkconsole.stderr)
                else:
                    self.showtraceback()
740 741
        finally:
            if not use_subprocess:
742 743 744 745
                try:
                    self.tkconsole.endexecuting()
                except AttributeError:  # shell may have closed
                    pass
David Scherer's avatar
David Scherer committed
746 747

    def write(self, s):
748
        "Override base class method"
749
        self.tkconsole.stderr.write(s)
David Scherer's avatar
David Scherer committed
750

751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778
    def display_port_binding_error(self):
        tkMessageBox.showerror(
            "Port Binding Error",
            "IDLE can't bind TCP/IP port 8833, which is necessary to "
            "communicate with its Python execution server.  Either "
            "no networking is installed on this computer or another "
            "process (another IDLE?) is using the port.  Run IDLE with the -n "
            "command line switch to start without a subprocess and refer to "
            "Help/IDLE Help 'Running without a subprocess' for further "
            "details.",
            master=self.tkconsole.text)

    def display_no_subprocess_error(self):
        tkMessageBox.showerror(
            "Subprocess Startup Error",
            "IDLE's subprocess didn't make connection.  Either IDLE can't "
            "start a subprocess or personal firewall software is blocking "
            "the connection.",
            master=self.tkconsole.text)

    def display_executing_dialog(self):
        tkMessageBox.showerror(
            "Already executing",
            "The Python Shell window is already executing a command; "
            "please wait until it is finished.",
            master=self.tkconsole.text)


David Scherer's avatar
David Scherer committed
779 780 781 782 783 784 785 786
class PyShell(OutputWindow):

    shell_title = "Python Shell"

    # Override classes
    ColorDelegator = ModifiedColorDelegator
    UndoDelegator = ModifiedUndoDelegator

787
    # Override menus
788 789 790
    menu_specs = [
        ("file", "_File"),
        ("edit", "_Edit"),
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
791
        ("debug", "_Debug"),
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
792
        ("options", "_Options"),
793 794 795
        ("windows", "_Windows"),
        ("help", "_Help"),
    ]
David Scherer's avatar
David Scherer committed
796

797 798 799 800 801
    if macosxSupport.runningAsOSXApp():
        del menu_specs[-3]
        menu_specs[-2] = ("windows", "_Window")


David Scherer's avatar
David Scherer committed
802 803 804 805
    # New classes
    from IdleHistory import History

    def __init__(self, flist=None):
806
        if use_subprocess:
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
807 808
            ms = self.menu_specs
            if ms[2][0] != "shell":
809
                ms.insert(2, ("shell", "She_ll"))
David Scherer's avatar
David Scherer committed
810 811 812 813 814 815
        self.interp = ModifiedInterpreter(self)
        if flist is None:
            root = Tk()
            fixwordbreaks(root)
            root.withdraw()
            flist = PyShellFileList(root)
816
        #
David Scherer's avatar
David Scherer committed
817
        OutputWindow.__init__(self, flist, None, None)
818
        #
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
819 820 821 822 823
##        self.config(usetabs=1, indentwidth=8, context_use_ps1=1)
        self.usetabs = True
        # indentwidth must be 8 when using tabs.  See note in EditorWindow:
        self.indentwidth = 8
        self.context_use_ps1 = True
824
        #
David Scherer's avatar
David Scherer committed
825 826 827 828 829 830 831 832
        text = self.text
        text.configure(wrap="char")
        text.bind("<<newline-and-indent>>", self.enter_callback)
        text.bind("<<plain-newline-and-indent>>", self.linefeed_callback)
        text.bind("<<interrupt-execution>>", self.cancel_callback)
        text.bind("<<beginning-of-line>>", self.home_callback)
        text.bind("<<end-of-file>>", self.eof_callback)
        text.bind("<<open-stack-viewer>>", self.open_stack_viewer)
833
        text.bind("<<toggle-debugger>>", self.toggle_debugger)
David Scherer's avatar
David Scherer committed
834
        text.bind("<<toggle-jit-stack-viewer>>", self.toggle_jit_stack_viewer)
835 836 837
        if use_subprocess:
            text.bind("<<view-restart>>", self.view_restart_mark)
            text.bind("<<restart-shell>>", self.restart_shell)
838
        #
David Scherer's avatar
David Scherer committed
839 840 841
        self.save_stdout = sys.stdout
        self.save_stderr = sys.stderr
        self.save_stdin = sys.stdin
842 843 844 845
        import IOBinding
        self.stdout = PseudoFile(self, "stdout", IOBinding.encoding)
        self.stderr = PseudoFile(self, "stderr", IOBinding.encoding)
        self.console = PseudoFile(self, "console", IOBinding.encoding)
Chui Tey's avatar
Chui Tey committed
846 847 848 849
        if not use_subprocess:
            sys.stdout = self.stdout
            sys.stderr = self.stderr
            sys.stdin = self
850
        #
David Scherer's avatar
David Scherer committed
851
        self.history = self.History(self.text)
852
        #
853
        self.pollinterval = 50  # millisec
Chui Tey's avatar
Chui Tey committed
854

855 856 857
    def get_standard_extension_names(self):
        return idleConf.GetExtensions(shell_only=True)

Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
858 859 860 861 862
    reading = False
    executing = False
    canceled = False
    endoffile = False
    closing = False
David Scherer's avatar
David Scherer committed
863

864
    def set_warning_stream(self, stream):
865 866
        global warning_stream
        warning_stream = stream
867 868 869 870

    def get_warning_stream(self):
        return warning_stream

David Scherer's avatar
David Scherer committed
871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888
    def toggle_debugger(self, event=None):
        if self.executing:
            tkMessageBox.showerror("Don't debug now",
                "You can only toggle the debugger when idle",
                master=self.text)
            self.set_debugger_indicator()
            return "break"
        else:
            db = self.interp.getdebugger()
            if db:
                self.close_debugger()
            else:
                self.open_debugger()

    def set_debugger_indicator(self):
        db = self.interp.getdebugger()
        self.setvar("<<toggle-debugger>>", not not db)

Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
889
    def toggle_jit_stack_viewer(self, event=None):
David Scherer's avatar
David Scherer committed
890 891 892 893 894 895 896
        pass # All we need is the variable

    def close_debugger(self):
        db = self.interp.getdebugger()
        if db:
            self.interp.setdebugger(None)
            db.close()
897 898
            if self.interp.rpcclt:
                RemoteDebugger.close_remote_debugger(self.interp.rpcclt)
David Scherer's avatar
David Scherer committed
899 900 901 902 903 904 905
            self.resetoutput()
            self.console.write("[DEBUG OFF]\n")
            sys.ps1 = ">>> "
            self.showprompt()
        self.set_debugger_indicator()

    def open_debugger(self):
Chui Tey's avatar
Chui Tey committed
906
        if self.interp.rpcclt:
907 908 909 910 911 912
            dbg_gui = RemoteDebugger.start_remote_debugger(self.interp.rpcclt,
                                                           self)
        else:
            dbg_gui = Debugger.Debugger(self)
        self.interp.setdebugger(dbg_gui)
        dbg_gui.load_breakpoints()
Chui Tey's avatar
Chui Tey committed
913 914 915 916
        sys.ps1 = "[DEBUG ON]\n>>> "
        self.showprompt()
        self.set_debugger_indicator()

David Scherer's avatar
David Scherer committed
917
    def beginexecuting(self):
918
        "Helper for ModifiedInterpreter"
David Scherer's avatar
David Scherer committed
919 920 921 922
        self.resetoutput()
        self.executing = 1

    def endexecuting(self):
923
        "Helper for ModifiedInterpreter"
David Scherer's avatar
David Scherer committed
924 925
        self.executing = 0
        self.canceled = 0
Chui Tey's avatar
Chui Tey committed
926
        self.showprompt()
David Scherer's avatar
David Scherer committed
927 928

    def close(self):
929
        "Extend EditorWindow.close()"
David Scherer's avatar
David Scherer committed
930
        if self.executing:
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
931
            response = tkMessageBox.askokcancel(
David Scherer's avatar
David Scherer committed
932
                "Kill?",
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
933
                "The program is still running!\n Do you want to kill it?",
David Scherer's avatar
David Scherer committed
934
                default="ok",
935
                parent=self.text)
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
936
            if response == False:
David Scherer's avatar
David Scherer committed
937
                return "cancel"
938 939 940
        if self.reading:
            self.top.quit()
        self.canceled = True
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
941 942 943
        self.closing = True
        # Wait for poll_subprocess() rescheduling to stop
        self.text.after(2 * self.pollinterval, self.close2)
944 945 946

    def close2(self):
        return EditorWindow.close(self)
David Scherer's avatar
David Scherer committed
947 948

    def _close(self):
949
        "Extend EditorWindow._close(), shut down debugger and execution server"
David Scherer's avatar
David Scherer committed
950
        self.close_debugger()
951 952
        if use_subprocess:
            self.interp.kill_subprocess()
David Scherer's avatar
David Scherer committed
953 954 955 956 957 958 959 960 961
        # Restore std streams
        sys.stdout = self.save_stdout
        sys.stderr = self.save_stderr
        sys.stdin = self.save_stdin
        # Break cycles
        self.interp = None
        self.console = None
        self.flist.pyshell = None
        self.history = None
962
        EditorWindow._close(self)
David Scherer's avatar
David Scherer committed
963 964

    def ispythonsource(self, filename):
965
        "Override EditorWindow method: never remove the colorizer"
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
966
        return True
David Scherer's avatar
David Scherer committed
967 968 969 970

    def short_title(self):
        return self.shell_title

971
    COPYRIGHT = \
972
          'Type "copyright", "credits" or "license()" for more information.'
973

974 975 976 977 978 979 980 981 982
    firewallmessage = """
    ****************************************************************
    Personal firewall software may warn about the connection IDLE
    makes to its subprocess using this computer's internal loopback
    interface.  This connection is not visible on any external
    interface and no data is sent to or received from the Internet.
    ****************************************************************
    """

David Scherer's avatar
David Scherer committed
983 984
    def begin(self):
        self.resetoutput()
985 986
        if use_subprocess:
            nosub = ''
987 988 989
            client = self.interp.start_subprocess()
            if not client:
                self.close()
990
                return False
991 992
        else:
            nosub = "==== No Subprocess ===="
993
        self.write("Python %s on %s\n%s\n%s\nIDLE %s      %s\n" %
994
                   (sys.version, sys.platform, self.COPYRIGHT,
995
                    self.firewallmessage, idlever.IDLE_VERSION, nosub))
David Scherer's avatar
David Scherer committed
996 997
        self.showprompt()
        import Tkinter
998
        Tkinter._default_root = None # 03Jan04 KBK What's this?
999
        return True
David Scherer's avatar
David Scherer committed
1000 1001 1002 1003 1004

    def readline(self):
        save = self.reading
        try:
            self.reading = 1
1005
            self.top.mainloop()  # nested mainloop()
David Scherer's avatar
David Scherer committed
1006 1007 1008
        finally:
            self.reading = save
        line = self.text.get("iomark", "end-1c")
1009 1010
        if len(line) == 0:  # may be EOF if we quit our mainloop with Ctrl-C
            line = "\n"
1011
        if isinstance(line, str):
1012 1013 1014 1015 1016
            import IOBinding
            try:
                line = line.encode(IOBinding.encoding)
            except UnicodeError:
                pass
David Scherer's avatar
David Scherer committed
1017 1018 1019
        self.resetoutput()
        if self.canceled:
            self.canceled = 0
1020 1021
            if not use_subprocess:
                raise KeyboardInterrupt
David Scherer's avatar
David Scherer committed
1022 1023
        if self.endoffile:
            self.endoffile = 0
1024
            line = ""
David Scherer's avatar
David Scherer committed
1025 1026 1027
        return line

    def isatty(self):
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
1028
        return True
David Scherer's avatar
David Scherer committed
1029

Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
1030
    def cancel_callback(self, event=None):
David Scherer's avatar
David Scherer committed
1031 1032 1033 1034 1035 1036 1037
        try:
            if self.text.compare("sel.first", "!=", "sel.last"):
                return # Active selection -- always use default binding
        except:
            pass
        if not (self.executing or self.reading):
            self.resetoutput()
1038
            self.interp.write("KeyboardInterrupt\n")
David Scherer's avatar
David Scherer committed
1039 1040 1041
            self.showprompt()
            return "break"
        self.endoffile = 0
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
1042
        self.canceled = 1
1043
        if (self.executing and self.interp.rpcclt):
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
1044 1045 1046 1047
            if self.interp.getdebugger():
                self.interp.restart_subprocess()
            else:
                self.interp.interrupt_subprocess()
1048 1049
        if self.reading:
            self.top.quit()  # exit the nested mainloop() in readline()
David Scherer's avatar
David Scherer committed
1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082
        return "break"

    def eof_callback(self, event):
        if self.executing and not self.reading:
            return # Let the default binding (delete next char) take over
        if not (self.text.compare("iomark", "==", "insert") and
                self.text.compare("insert", "==", "end-1c")):
            return # Let the default binding (delete next char) take over
        if not self.executing:
            self.resetoutput()
            self.close()
        else:
            self.canceled = 0
            self.endoffile = 1
            self.top.quit()
        return "break"

    def home_callback(self, event):
        if event.state != 0 and event.keysym == "Home":
            return # <Modifier-Home>; fall back to class binding
        if self.text.compare("iomark", "<=", "insert") and \
           self.text.compare("insert linestart", "<=", "iomark"):
            self.text.mark_set("insert", "iomark")
            self.text.tag_remove("sel", "1.0", "end")
            self.text.see("insert")
            return "break"

    def linefeed_callback(self, event):
        # Insert a linefeed without entering anything (still autoindented)
        if self.reading:
            self.text.insert("insert", "\n")
            self.text.see("insert")
        else:
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
1083
            self.newline_and_indent_event(event)
David Scherer's avatar
David Scherer committed
1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094
        return "break"

    def enter_callback(self, event):
        if self.executing and not self.reading:
            return # Let the default binding (insert '\n') take over
        # If some text is selected, recall the selection
        # (but only if this before the I/O mark)
        try:
            sel = self.text.get("sel.first", "sel.last")
            if sel:
                if self.text.compare("sel.last", "<=", "iomark"):
1095
                    self.recall(sel, event)
David Scherer's avatar
David Scherer committed
1096 1097 1098 1099 1100 1101 1102 1103 1104 1105
                    return "break"
        except:
            pass
        # If we're strictly before the line containing iomark, recall
        # the current line, less a leading prompt, less leading or
        # trailing whitespace
        if self.text.compare("insert", "<", "iomark linestart"):
            # Check if there's a relevant stdin range -- if so, use it
            prev = self.text.tag_prevrange("stdin", "insert")
            if prev and self.text.compare("insert", "<", prev[1]):
1106
                self.recall(self.text.get(prev[0], prev[1]), event)
David Scherer's avatar
David Scherer committed
1107 1108 1109
                return "break"
            next = self.text.tag_nextrange("stdin", "insert")
            if next and self.text.compare("insert lineend", ">=", next[0]):
1110
                self.recall(self.text.get(next[0], next[1]), event)
David Scherer's avatar
David Scherer committed
1111
                return "break"
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
1112
            # No stdin mark -- just get the current line, less any prompt
1113 1114 1115 1116 1117 1118
            indices = self.text.tag_nextrange("console", "insert linestart")
            if indices and \
               self.text.compare(indices[0], "<=", "insert linestart"):
                self.recall(self.text.get(indices[1], "insert lineend"), event)
            else:
                self.recall(self.text.get("insert linestart", "insert lineend"), event)
David Scherer's avatar
David Scherer committed
1119
            return "break"
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
1120
        # If we're between the beginning of the line and the iomark, i.e.
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
1121
        # in the prompt area, move to the end of the prompt
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
1122
        if self.text.compare("insert", "<", "iomark"):
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
1123
            self.text.mark_set("insert", "iomark")
David Scherer's avatar
David Scherer committed
1124 1125 1126
        # If we're in the current input and there's only whitespace
        # beyond the cursor, erase that whitespace first
        s = self.text.get("insert", "end-1c")
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
1127
        if s and not s.strip():
David Scherer's avatar
David Scherer committed
1128 1129 1130 1131
            self.text.delete("insert", "end-1c")
        # If we're in the current input before its last line,
        # insert a newline right at the insert point
        if self.text.compare("insert", "<", "end-1c linestart"):
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
1132
            self.newline_and_indent_event(event)
David Scherer's avatar
David Scherer committed
1133 1134 1135 1136 1137 1138 1139
            return "break"
        # We're in the last line; append a newline and submit it
        self.text.mark_set("insert", "end-1c")
        if self.reading:
            self.text.insert("insert", "\n")
            self.text.see("insert")
        else:
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
1140
            self.newline_and_indent_event(event)
David Scherer's avatar
David Scherer committed
1141 1142 1143
        self.text.tag_add("stdin", "iomark", "end-1c")
        self.text.update_idletasks()
        if self.reading:
1144
            self.top.quit() # Break out of recursive mainloop()
David Scherer's avatar
David Scherer committed
1145 1146 1147 1148
        else:
            self.runit()
        return "break"

1149
    def recall(self, s, event):
1150 1151 1152 1153
        # remove leading and trailing empty or whitespace lines
        s = re.sub(r'^\s*\n', '' , s)
        s = re.sub(r'\n\s*$', '', s)
        lines = s.split('\n')
1154 1155 1156 1157
        self.text.undo_block_start()
        try:
            self.text.tag_remove("sel", "1.0", "end")
            self.text.mark_set("insert", "end-1c")
1158 1159
            prefix = self.text.get("insert linestart", "insert")
            if prefix.rstrip().endswith(':'):
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
1160
                self.newline_and_indent_event(event)
1161 1162
                prefix = self.text.get("insert linestart", "insert")
            self.text.insert("insert", lines[0].strip())
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
1163
            if len(lines) > 1:
1164 1165
                orig_base_indent = re.search(r'^([ \t]*)', lines[0]).group(0)
                new_base_indent  = re.search(r'^([ \t]*)', prefix).group(0)
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
1166
                for line in lines[1:]:
1167 1168 1169 1170
                    if line.startswith(orig_base_indent):
                        # replace orig base indentation with new indentation
                        line = new_base_indent + line[len(orig_base_indent):]
                    self.text.insert('insert', '\n'+line.rstrip())
1171 1172 1173
        finally:
            self.text.see("insert")
            self.text.undo_block_stop()
David Scherer's avatar
David Scherer committed
1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189

    def runit(self):
        line = self.text.get("iomark", "end-1c")
        # Strip off last newline and surrounding whitespace.
        # (To allow you to hit return twice to end a statement.)
        i = len(line)
        while i > 0 and line[i-1] in " \t":
            i = i-1
        if i > 0 and line[i-1] == "\n":
            i = i-1
        while i > 0 and line[i-1] in " \t":
            i = i-1
        line = line[:i]
        more = self.interp.runsource(line)

    def open_stack_viewer(self, event=None):
Chui Tey's avatar
Chui Tey committed
1190 1191
        if self.interp.rpcclt:
            return self.interp.remote_stack_viewer()
David Scherer's avatar
David Scherer committed
1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202
        try:
            sys.last_traceback
        except:
            tkMessageBox.showerror("No stack trace",
                "There is no stack trace yet.\n"
                "(sys.last_traceback is not defined)",
                master=self.text)
            return
        from StackViewer import StackBrowser
        sv = StackBrowser(self.root, self.flist)

Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
1203 1204 1205 1206 1207
    def view_restart_mark(self, event=None):
        self.text.see("iomark")
        self.text.see("restart")

    def restart_shell(self, event=None):
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
1208
        self.interp.restart_subprocess()
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
1209

David Scherer's avatar
David Scherer committed
1210 1211 1212 1213 1214 1215 1216 1217
    def showprompt(self):
        self.resetoutput()
        try:
            s = str(sys.ps1)
        except:
            s = ""
        self.console.write(s)
        self.text.mark_set("insert", "end-1c")
Chui Tey's avatar
Chui Tey committed
1218
        self.set_line_and_column()
1219
        self.io.reset_undo()
David Scherer's avatar
David Scherer committed
1220 1221 1222 1223 1224 1225 1226 1227

    def resetoutput(self):
        source = self.text.get("iomark", "end-1c")
        if self.history:
            self.history.history_store(source)
        if self.text.get("end-2c") != "\n":
            self.text.insert("end-1c", "\n")
        self.text.mark_set("iomark", "end-1c")
Chui Tey's avatar
Chui Tey committed
1228
        self.set_line_and_column()
David Scherer's avatar
David Scherer committed
1229 1230

    def write(self, s, tags=()):
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
1231 1232 1233 1234 1235 1236
        try:
            self.text.mark_gravity("iomark", "right")
            OutputWindow.write(self, s, tags, "iomark")
            self.text.mark_gravity("iomark", "left")
        except:
            pass
David Scherer's avatar
David Scherer committed
1237 1238
        if self.canceled:
            self.canceled = 0
1239 1240
            if not use_subprocess:
                raise KeyboardInterrupt
David Scherer's avatar
David Scherer committed
1241

1242
class PseudoFile(object):
David Scherer's avatar
David Scherer committed
1243

1244
    def __init__(self, shell, tags, encoding=None):
David Scherer's avatar
David Scherer committed
1245 1246
        self.shell = shell
        self.tags = tags
1247
        self.encoding = encoding
David Scherer's avatar
David Scherer committed
1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258

    def write(self, s):
        self.shell.write(s, self.tags)

    def writelines(self, l):
        map(self.write, l)

    def flush(self):
        pass

    def isatty(self):
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
1259
        return True
David Scherer's avatar
David Scherer committed
1260

1261

David Scherer's avatar
David Scherer committed
1262 1263
usage_msg = """\

1264 1265 1266
USAGE: idle  [-deins] [-t title] [file]*
       idle  [-dns] [-t title] (-c cmd | -r file) [arg]*
       idle  [-dns] [-t title] - [arg]*
1267

1268
  -h         print this help message and exit
1269
  -n         run IDLE without a subprocess (see Help/IDLE Help for details)
1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289

The following options will override the IDLE 'settings' configuration:

  -e         open an edit window
  -i         open a shell window

The following options imply -i and will open a shell:

  -c cmd     run the command in a shell, or
  -r file    run script from file

  -d         enable the debugger
  -s         run $IDLESTARTUP or $PYTHONSTARTUP before anything else
  -t title   set title of shell window

A default edit window will be bypassed when -c, -r, or - are used.

[arg]* are passed to the command (-c) or script (-r) in sys.argv[1:].

Examples:
David Scherer's avatar
David Scherer committed
1290

1291 1292
idle
        Open an edit window or shell depending on IDLE's configuration.
1293

1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312
idle foo.py foobar.py
        Edit the files, also open a shell if configured to start with shell.

idle -est "Baz" foo.py
        Run $IDLESTARTUP or $PYTHONSTARTUP, edit foo.py, and open a shell
        window with the title "Baz".

idle -c "import sys; print sys.argv" "foo"
        Open a shell window and run the command, passing "-c" in sys.argv[0]
        and "foo" in sys.argv[1].

idle -d -s -r foo.py "Hello World"
        Open a shell window, run a startup script, enable the debugger, and
        run foo.py, passing "foo.py" in sys.argv[0] and "Hello World" in
        sys.argv[1].

echo "import sys; print sys.argv" | idle - "foobar"
        Open a shell window, run the script piped in, passing '' in sys.argv[0]
        and "foobar" in sys.argv[1].
David Scherer's avatar
David Scherer committed
1313 1314
"""

1315
def main():
1316 1317
    global flist, root, use_subprocess

1318
    use_subprocess = True
1319 1320 1321
    enable_shell = False
    enable_edit = False
    debug = False
1322 1323
    cmd = None
    script = None
1324
    startup = False
1325
    try:
1326
        opts, args = getopt.getopt(sys.argv[1:], "c:deihnr:st:")
1327
    except getopt.error as msg:
1328 1329 1330 1331 1332 1333
        sys.stderr.write("Error: %s\n" % str(msg))
        sys.stderr.write(usage_msg)
        sys.exit(2)
    for o, a in opts:
        if o == '-c':
            cmd = a
1334
            enable_shell = True
1335
        if o == '-d':
1336 1337
            debug = True
            enable_shell = True
1338
        if o == '-e':
1339 1340 1341 1342 1343 1344
            enable_edit = True
        if o == '-h':
            sys.stdout.write(usage_msg)
            sys.exit()
        if o == '-i':
            enable_shell = True
1345 1346
        if o == '-n':
            use_subprocess = False
1347 1348
        if o == '-r':
            script = a
1349 1350 1351
            if os.path.isfile(script):
                pass
            else:
1352
                print("No script file: ", script)
1353 1354
                sys.exit()
            enable_shell = True
1355
        if o == '-s':
1356 1357
            startup = True
            enable_shell = True
1358 1359
        if o == '-t':
            PyShell.shell_title = a
1360 1361 1362 1363 1364
            enable_shell = True
    if args and args[0] == '-':
        cmd = sys.stdin.read()
        enable_shell = True
    # process sys.argv and sys.path:
1365 1366
    for i in range(len(sys.path)):
        sys.path[i] = os.path.abspath(sys.path[i])
1367 1368 1369 1370 1371 1372 1373 1374 1375
    if args and args[0] == '-':
        sys.argv = [''] + args[1:]
    elif cmd:
        sys.argv = ['-c'] + args
    elif script:
        sys.argv = [script] + args
    elif args:
        enable_edit = True
        pathx = []
1376 1377
        for filename in args:
            pathx.append(os.path.dirname(filename))
1378 1379 1380 1381
        for dir in pathx:
            dir = os.path.abspath(dir)
            if not dir in sys.path:
                sys.path.insert(0, dir)
1382 1383 1384 1385
    else:
        dir = os.getcwd()
        if not dir in sys.path:
            sys.path.insert(0, dir)
1386 1387
    # check the IDLE settings configuration (but command line overrides)
    edit_start = idleConf.GetOption('main', 'General',
1388
                                    'editor-on-startup', type='bool')
1389
    enable_edit = enable_edit or edit_start
1390
    enable_shell = enable_shell or not edit_start
1391
    # start editor and/or shell windows:
1392
    root = Tk(className="Idle")
1393

1394 1395 1396
    fixwordbreaks(root)
    root.withdraw()
    flist = PyShellFileList(root)
1397 1398
    macosxSupport.setupApp(root, flist)

1399 1400 1401 1402 1403 1404
    if enable_edit:
        if not (cmd or script):
            for filename in args:
                flist.open(filename)
            if not args:
                flist.new()
1405
    if enable_shell:
1406 1407
        shell = flist.open_shell()
        if not shell:
1408
            return # couldn't open shell
1409 1410 1411 1412 1413 1414 1415 1416

        if macosxSupport.runningAsOSXApp() and flist.dict:
            # On OSX: when the user has double-clicked on a file that causes
            # IDLE to be launched the shell window will open just in front of
            # the file she wants to see. Lower the interpreter window when
            # there are open files.
            shell.top.lower()

1417 1418 1419 1420
    shell = flist.pyshell
    # handle remaining options:
    if debug:
        shell.open_debugger()
1421 1422 1423 1424
    if startup:
        filename = os.environ.get("IDLESTARTUP") or \
                   os.environ.get("PYTHONSTARTUP")
        if filename and os.path.isfile(filename):
1425
            shell.interp.execfile(filename)
1426
    if shell and cmd or script:
1427 1428
        shell.interp.runcommand("""if 1:
            import sys as _sys
1429
            _sys.argv = %r
1430
            del _sys
1431
            \n""" % (sys.argv,))
1432 1433 1434
        if cmd:
            shell.interp.execsource(cmd)
        elif script:
1435
            shell.interp.prepend_syspath(script)
1436
            shell.interp.execfile(script)
1437

1438 1439 1440
    root.mainloop()
    root.destroy()

David Scherer's avatar
David Scherer committed
1441
if __name__ == "__main__":
1442
    sys.modules['PyShell'] = sys.modules['__main__']
David Scherer's avatar
David Scherer committed
1443
    main()