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

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

import linecache
from code import InteractiveInterpreter
19
from platform import python_version, system
David Scherer's avatar
David Scherer committed
20

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

29 30 31 32 33 34 35 36 37 38 39
from idlelib.EditorWindow import EditorWindow, fixwordbreaks
from idlelib.FileList import FileList
from idlelib.ColorDelegator import ColorDelegator
from idlelib.UndoDelegator import UndoDelegator
from idlelib.OutputWindow import OutputWindow
from idlelib.configHandler import idleConf
from idlelib import idlever
from idlelib import rpc
from idlelib import Debugger
from idlelib import RemoteDebugger
from idlelib import macosxSupport
Chui Tey's avatar
Chui Tey committed
40

41 42
HOST = '127.0.0.1' # python execution server on localhost loopback
PORT = 0  # someday pass in host, port for remote debug capability
43

44 45 46 47
# 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.
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
warning_stream = sys.__stderr__  # None, at least on Windows, if no console.
import warnings

def idle_formatwarning(message, category, filename, lineno, line=None):
    """Format warnings the IDLE way."""

    s = "\nWarning (from warnings module):\n"
    s += '  File \"%s\", line %s\n' % (filename, lineno)
    if line is None:
        line = linecache.getline(filename, lineno)
    line = line.strip()
    if line:
        s += "    %s\n" % line
    s += "%s: %s\n" % (category.__name__, message)
    return s

def idle_showwarning(
        message, category, filename, lineno, file=None, line=None):
    """Show Idle-format warning (after replacing warnings.showwarning).

    The differences are the formatter called, the file=None replacement,
    which can be None, the capture of the consequence AttributeError,
    and the output of a hard-coded prompt.
    """
    if file is None:
        file = warning_stream
    try:
        file.write(idle_formatwarning(
                message, category, filename, lineno, line=line))
        file.write(">>> ")
    except (AttributeError, OSError):
        pass  # if file (probably __stderr__) is invalid, skip warning.

_warnings_showwarning = None

def capture_warnings(capture):
    "Replace warning.showwarning with idle_showwarning, or reverse."

    global _warnings_showwarning
    if capture:
        if _warnings_showwarning is None:
            _warnings_showwarning = warnings.showwarning
            warnings.showwarning = idle_showwarning
    else:
        if _warnings_showwarning is not None:
            warnings.showwarning = _warnings_showwarning
            _warnings_showwarning = None

capture_warnings(True)
Chui Tey's avatar
Chui Tey committed
97

98 99
def extended_linecache_checkcache(filename=None,
                                  orig_checkcache=linecache.checkcache):
100 101
    """Extend linecache.checkcache to preserve the <pyshell#...> entries

102 103
    Rather than repeating the linecache code, patch it to save the
    <pyshell#...> entries, call the original linecache.checkcache()
104
    (skipping them), and then restore the saved entries.
105 106 107

    orig_checkcache is bound at definition time to the original
    method, allowing it to be patched.
108
    """
David Scherer's avatar
David Scherer committed
109 110
    cache = linecache.cache
    save = {}
111 112 113 114
    for key in list(cache):
        if key[:1] + key[-1:] == '<>':
            save[key] = cache.pop(key)
    orig_checkcache(filename)
David Scherer's avatar
David Scherer committed
115
    cache.update(save)
116

117 118
# Patch linecache.checkcache():
linecache.checkcache = extended_linecache_checkcache
David Scherer's avatar
David Scherer committed
119

120

David Scherer's avatar
David Scherer committed
121
class PyShellEditorWindow(EditorWindow):
122
    "Regular text edit window in IDLE, supports breakpoints"
123

David Scherer's avatar
David Scherer committed
124
    def __init__(self, *args):
125
        self.breakpoints = []
126
        EditorWindow.__init__(self, *args)
David Scherer's avatar
David Scherer committed
127
        self.text.bind("<<set-breakpoint-here>>", self.set_breakpoint_here)
128
        self.text.bind("<<clear-breakpoint-here>>", self.clear_breakpoint_here)
David Scherer's avatar
David Scherer committed
129 130
        self.text.bind("<<open-python-shell>>", self.flist.open_shell)

Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
131 132
        self.breakpointPath = os.path.join(idleConf.GetUserCfgDir(),
                                           'breakpoints.lst')
133
        # whenever a file is changed, restore breakpoints
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
134 135
        def filename_changed_hook(old_hook=self.io.filename_change_hook,
                                  self=self):
136 137 138
            self.restore_file_breaks()
            old_hook()
        self.io.set_filename_change_hook(filename_changed_hook)
139 140
        if self.io.filename:
            self.restore_file_breaks()
141
        self.color_breakpoint_text()
142

143 144 145 146 147 148 149 150
    rmenu_specs = [
        ("Cut", "<<cut>>", "rmenu_check_cut"),
        ("Copy", "<<copy>>", "rmenu_check_copy"),
        ("Paste", "<<paste>>", "rmenu_check_paste"),
        (None, None, None),
        ("Set Breakpoint", "<<set-breakpoint-here>>", None),
        ("Clear Breakpoint", "<<clear-breakpoint-here>>", None)
    ]
David Scherer's avatar
David Scherer committed
151

152 153
    def color_breakpoint_text(self, color=True):
        "Turn colorizing of breakpoint text on or off"
154 155 156
        if self.io is None:
            # possible due to update in restore_file_breaks
            return
157 158 159 160 161 162 163
        if color:
            theme = idleConf.GetOption('main','Theme','name')
            cfg = idleConf.GetHighlight(theme, "break")
        else:
            cfg = {'foreground': '', 'background': ''}
        self.text.tag_config('BREAK', cfg)

164
    def set_breakpoint(self, lineno):
165 166
        text = self.text
        filename = self.io.filename
167
        text.tag_add("BREAK", "%d.0" % lineno, "%d.0" % (lineno+1))
168 169
        try:
            i = self.breakpoints.index(lineno)
170
        except ValueError:  # only add if missing, i.e. do once
171 172 173 174 175 176
            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
177

178 179 180 181 182 183 184 185 186
    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)

187
    def clear_breakpoint_here(self, event=None):
188 189 190 191
        text = self.text
        filename = self.io.filename
        if not filename:
            text.bell()
192
            return
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
        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

221
    def store_file_breaks(self):
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
222 223 224 225 226 227
        "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
228 229 230 231
        #     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
232
        #
233 234
        #     Breakpoints are set as tagged ranges in the text.
        #     Since a modified file has to be saved before it is
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
235 236 237 238 239 240
        #     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
241
        try:
242 243
            with open(self.breakpointPath, "r") as fp:
                lines = fp.readlines()
244
        except OSError:
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
245
            lines = []
246 247 248 249 250 251 252 253 254
        try:
            with open(self.breakpointPath, "w") as new_file:
                for line in lines:
                    if not line.startswith(filename + '='):
                        new_file.write(line)
                self.update_breakpoints()
                breaks = self.breakpoints
                if breaks:
                    new_file.write(filename + '=' + str(breaks) + '\n')
255
        except OSError as err:
256 257 258 259 260 261
            if not getattr(self.root, "breakpoint_error_displayed", False):
                self.root.breakpoint_error_displayed = True
                tkMessageBox.showerror(title='IDLE Error',
                    message='Unable to update breakpoint list:\n%s'
                        % str(err),
                    parent=self.text)
262 263 264

    def restore_file_breaks(self):
        self.text.update()   # this enables setting "BREAK" tags to be visible
265 266 267
        if self.io is None:
            # can happen if IDLE closes due to the .update() call
            return
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
268 269 270
        filename = self.io.filename
        if filename is None:
            return
271
        if os.path.isfile(self.breakpointPath):
272 273
            with open(self.breakpointPath, "r") as fp:
                lines = fp.readlines()
274
            for line in lines:
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
275
                if line.startswith(filename + '='):
276
                    breakpoint_linenumbers = eval(line[len(filename)+1:])
277 278
                    for breakpoint_linenumber in breakpoint_linenumbers:
                        self.set_breakpoint(breakpoint_linenumber)
279

Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
280 281
    def update_breakpoints(self):
        "Retrieves all the breakpoints in the current window"
282
        text = self.text
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
283 284 285 286 287 288 289
        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):
290 291
            lineno = int(float(ranges[index].string))
            end = int(float(ranges[index+1].string))
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
292 293 294 295 296
            while lineno < end:
                lines.append(lineno)
                lineno += 1
        return lines

297
# XXX 13 Dec 2002 KBK Not used currently
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
298 299 300 301 302
#    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)
303 304 305 306 307

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

David Scherer's avatar
David Scherer committed
309 310

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

313 314
    # override FileList's class variable, instances return PyShellEditorWindow
    # instead of EditorWindow when new edit windows are created.
David Scherer's avatar
David Scherer committed
315 316 317 318 319 320
    EditorWindow = PyShellEditorWindow

    pyshell = None

    def open_shell(self, event=None):
        if self.pyshell:
321
            self.pyshell.top.wakeup()
David Scherer's avatar
David Scherer committed
322 323
        else:
            self.pyshell = PyShell(self)
324 325 326
            if self.pyshell:
                if not self.pyshell.begin():
                    return None
David Scherer's avatar
David Scherer committed
327 328 329 330
        return self.pyshell


class ModifiedColorDelegator(ColorDelegator):
331
    "Extend base class: colorizer for the shell window itself"
332

333 334 335
    def __init__(self):
        ColorDelegator.__init__(self)
        self.LoadTagDefs()
David Scherer's avatar
David Scherer committed
336 337 338 339 340

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

342 343 344 345 346 347 348 349 350
    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"),
        })
David Scherer's avatar
David Scherer committed
351

352 353 354 355 356
    def removecolors(self):
        # Don't remove shell color tags before "iomark"
        for tag in self.tagdefs:
            self.tag_remove(tag, "iomark", "end")

David Scherer's avatar
David Scherer committed
357
class ModifiedUndoDelegator(UndoDelegator):
358
    "Extend base class: forbid insert/delete before the I/O mark"
David Scherer's avatar
David Scherer committed
359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377

    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
378 379 380 381 382 383 384

class MyRPCClient(rpc.RPCClient):

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

385

David Scherer's avatar
David Scherer committed
386 387 388 389 390 391
class ModifiedInterpreter(InteractiveInterpreter):

    def __init__(self, tkconsole):
        self.tkconsole = tkconsole
        locals = sys.modules['__main__'].__dict__
        InteractiveInterpreter.__init__(self, locals=locals)
392
        self.save_warnings_filters = None
393
        self.restarting = False
394 395
        self.subprocess_arglist = None
        self.port = PORT
396
        self.original_compiler_flags = self.compile.compiler.flags
David Scherer's avatar
David Scherer committed
397

398
    _afterid = None
Chui Tey's avatar
Chui Tey committed
399
    rpcclt = None
400
    rpcsubproc = None
Chui Tey's avatar
Chui Tey committed
401

402
    def spawn_subprocess(self):
403
        if self.subprocess_arglist is None:
404
            self.subprocess_arglist = self.build_subprocess_arglist()
405
        self.rpcsubproc = subprocess.Popen(self.subprocess_arglist)
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
406

407
    def build_subprocess_arglist(self):
408 409
        assert (self.port!=0), (
            "Socket should have been assigned a port number.")
410 411 412 413
        w = ['-W' + s for s in sys.warnoptions]
        # 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.
414 415
        del_exitf = idleConf.GetOption('main', 'General', 'delete-exitfunc',
                                       default=False, type='bool')
416
        if __name__ == 'idlelib.PyShell':
417
            command = "__import__('idlelib.run').run.main(%r)" % (del_exitf,)
418
        else:
419
            command = "__import__('run').main(%r)" % (del_exitf,)
420
        return [sys.executable] + w + ["-c", command, str(self.port)]
421

Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
422
    def start_subprocess(self):
423 424
        addr = (HOST, self.port)
        # GUI makes several attempts to acquire socket, listens for connection
425
        for i in range(3):
Chui Tey's avatar
Chui Tey committed
426 427
            time.sleep(i)
            try:
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
428
                self.rpcclt = MyRPCClient(addr)
Chui Tey's avatar
Chui Tey committed
429
                break
430
            except OSError as err:
431
                pass
Chui Tey's avatar
Chui Tey committed
432
        else:
433 434
            self.display_port_binding_error()
            return None
435 436 437 438 439 440 441 442 443 444 445 446
        # if PORT was 0, system will assign an 'ephemeral' port. Find it out:
        self.port = self.rpcclt.listening_sock.getsockname()[1]
        # if PORT was not 0, probably working with a remote execution server
        if PORT != 0:
            # To allow reconnection within the 2MSL wait (cf. Stevens TCP
            # V1, 18.6),  set SO_REUSEADDR.  Note that this can be problematic
            # on Windows since the implementation allows two active sockets on
            # the same address!
            self.rpcclt.listening_sock.setsockopt(socket.SOL_SOCKET,
                                           socket.SO_REUSEADDR, 1)
        self.spawn_subprocess()
        #time.sleep(20) # test to simulate GUI not accepting connection
447
        # Accept the connection from the Python execution server
448 449 450
        self.rpcclt.listening_sock.settimeout(10)
        try:
            self.rpcclt.accept()
451
        except socket.timeout as err:
452 453
            self.display_no_subprocess_error()
            return None
454 455
        self.rpcclt.register("console", self.tkconsole)
        self.rpcclt.register("stdin", self.tkconsole.stdin)
456 457
        self.rpcclt.register("stdout", self.tkconsole.stdout)
        self.rpcclt.register("stderr", self.tkconsole.stderr)
Chui Tey's avatar
Chui Tey committed
458
        self.rpcclt.register("flist", self.tkconsole.flist)
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
459
        self.rpcclt.register("linecache", linecache)
460
        self.rpcclt.register("interp", self)
461
        self.transfer_path(with_cwd=True)
Chui Tey's avatar
Chui Tey committed
462
        self.poll_subprocess()
463
        return self.rpcclt
Chui Tey's avatar
Chui Tey committed
464

465
    def restart_subprocess(self, with_cwd=False):
466
        if self.restarting:
467
            return self.rpcclt
468
        self.restarting = True
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
469
        # close only the subprocess debugger
470 471
        debug = self.getdebugger()
        if debug:
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
472
            try:
473
                # Only close subprocess debugger, don't unregister gui_adap!
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
474 475 476 477
                RemoteDebugger.close_subprocess_debugger(self.rpcclt)
            except:
                pass
        # Kill subprocess, spawn a new one, accept connection.
478
        self.rpcclt.close()
479
        self.terminate_subprocess()
480
        console = self.tkconsole
481
        was_executing = console.executing
482
        console.executing = False
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
483
        self.spawn_subprocess()
484 485
        try:
            self.rpcclt.accept()
486
        except socket.timeout as err:
487 488
            self.display_no_subprocess_error()
            return None
489
        self.transfer_path(with_cwd=with_cwd)
490
        console.stop_readline()
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
491
        # annotate restart in shell window and mark it
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
492
        console.text.delete("iomark", "end-1c")
493 494 495
        if was_executing:
            console.write('\n')
            console.showprompt()
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
496 497 498 499
        halfbar = ((int(console.width) - 16) // 2) * '='
        console.write(halfbar + ' RESTART ' + halfbar)
        console.text.mark_set("restart", "end-1c")
        console.text.mark_gravity("restart", "left")
500
        console.showprompt()
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
501
        # restart subprocess debugger
502
        if debug:
503
            # Restarted debugger connects to current instance of debug GUI
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
504
            gui = RemoteDebugger.restart_subprocess_debugger(self.rpcclt)
505 506
            # reload remote debugger breakpoints for all PyShellEditWindows
            debug.load_breakpoints()
507
        self.compile.compiler.flags = self.original_compiler_flags
508
        self.restarting = False
509
        return self.rpcclt
510

Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
511
    def __request_interrupt(self):
512
        self.rpcclt.remotecall("exec", "interrupt_the_server", (), {})
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
513 514

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

517
    def kill_subprocess(self):
518 519
        if self._afterid is not None:
            self.tkconsole.text.after_cancel(self._afterid)
520 521 522 523
        try:
            self.rpcclt.listening_sock.close()
        except AttributeError:  # no socket
            pass
524 525 526 527
        try:
            self.rpcclt.close()
        except AttributeError:  # no socket
            pass
528
        self.terminate_subprocess()
529 530
        self.tkconsole.executing = False
        self.rpcclt = None
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
531

532 533 534 535 536 537 538 539
    def terminate_subprocess(self):
        "Make sure subprocess is terminated"
        try:
            self.rpcsubproc.kill()
        except OSError:
            # process already terminated
            return
        else:
540
            try:
541
                self.rpcsubproc.wait()
542 543
            except OSError:
                return
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
544

545 546 547 548 549 550
    def transfer_path(self, with_cwd=False):
        if with_cwd:        # Issue 13506
            path = ['']     # include Current Working Directory
            path.extend(sys.path)
        else:
            path = sys.path
Terry Jan Reedy's avatar
Terry Jan Reedy committed
551

552 553
        self.runcommand("""if 1:
        import sys as _sys
554
        _sys.path = %r
555
        del _sys
556
        \n""" % (path,))
557

Chui Tey's avatar
Chui Tey committed
558 559 560 561 562 563
    active_seq = None

    def poll_subprocess(self):
        clt = self.rpcclt
        if clt is None:
            return
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
564
        try:
565
            response = clt.pollresponse(self.active_seq, wait=0.05)
566
        except (EOFError, OSError, KeyboardInterrupt):
567 568
            # lost connection or subprocess terminated itself, restart
            # [the KBI is from rpc.SocketIO.handle_EOF()]
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
569 570
            if self.tkconsole.closing:
                return
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
571 572
            response = None
            self.restart_subprocess()
Chui Tey's avatar
Chui Tey committed
573 574 575 576
        if response:
            self.tkconsole.resetoutput()
            self.active_seq = None
            how, what = response
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
577
            console = self.tkconsole.console
Chui Tey's avatar
Chui Tey committed
578 579
            if how == "OK":
                if what is not None:
580
                    print(repr(what), file=console)
Chui Tey's avatar
Chui Tey committed
581 582 583 584
            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
585
                errmsg = "PyShell.ModifiedInterpreter: Subprocess ERROR:\n"
586 587
                print(errmsg, what, file=sys.__stderr__)
                print(errmsg, what, file=console)
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
588
            # we received a response to the currently active seq number:
589 590 591 592
            try:
                self.tkconsole.endexecuting()
            except AttributeError:  # shell may have closed
                pass
593 594
        # Reschedule myself
        if not self.tkconsole.closing:
595 596
            self._afterid = self.tkconsole.text.after(
                self.tkconsole.pollinterval, self.poll_subprocess)
Chui Tey's avatar
Chui Tey committed
597

598 599 600 601 602 603 604 605
    debugger = None

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

    def getdebugger(self):
        return self.debugger

606 607 608 609 610 611
    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
612
        static object looking at the last exception.  It is queried through
613 614 615 616 617 618
        the RPC mechanism.

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

Chui Tey's avatar
Chui Tey committed
619
    def remote_stack_viewer(self):
620
        from idlelib import RemoteObjectBrowser
621
        oid = self.rpcclt.remotequeue("exec", "stackviewer", ("flist",), {})
Chui Tey's avatar
Chui Tey committed
622 623 624 625
        if oid is None:
            self.tkconsole.root.bell()
            return
        item = RemoteObjectBrowser.StubObjectTreeItem(self.rpcclt, oid)
626
        from idlelib.TreeWidget import ScrolledCanvas, TreeNode
Chui Tey's avatar
Chui Tey committed
627
        top = Toplevel(self.tkconsole.root)
628 629 630
        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
631 632 633 634 635
        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
636 637 638
    gid = 0

    def execsource(self, source):
639
        "Like runsource() but assumes complete exec source"
David Scherer's avatar
David Scherer committed
640 641 642 643
        filename = self.stuffsource(source)
        self.execfile(filename, source)

    def execfile(self, filename, source=None):
644
        "Execute an existing file"
David Scherer's avatar
David Scherer committed
645
        if source is None:
646
            with tokenize.open(filename) as fp:
647
                source = fp.read()
David Scherer's avatar
David Scherer committed
648 649 650 651
        try:
            code = compile(source, filename, "exec")
        except (OverflowError, SyntaxError):
            self.tkconsole.resetoutput()
652 653 654
            print('*** Error in script or command!\n'
                 'Traceback (most recent call last):',
                  file=self.tkconsole.stderr)
David Scherer's avatar
David Scherer committed
655
            InteractiveInterpreter.showsyntaxerror(self, filename)
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
656
            self.tkconsole.showprompt()
David Scherer's avatar
David Scherer committed
657 658 659 660
        else:
            self.runcode(code)

    def runsource(self, source):
661
        "Extend base class method: Stuff the source in the line cache first"
David Scherer's avatar
David Scherer committed
662 663
        filename = self.stuffsource(source)
        self.more = 0
664 665
        self.save_warnings_filters = warnings.filters[:]
        warnings.filterwarnings(action="error", category=SyntaxWarning)
666 667 668
        # at the moment, InteractiveInterpreter expects str
        assert isinstance(source, str)
        #if isinstance(source, str):
669
        #    from idlelib import IOBinding
670 671 672 673 674 675
        #    try:
        #        source = source.encode(IOBinding.encoding)
        #    except UnicodeError:
        #        self.tkconsole.resetoutput()
        #        self.write("Unsupported characters in input\n")
        #        return
676
        try:
677 678
            # InteractiveInterpreter.runsource() calls its runcode() method,
            # which is overridden (see below)
679 680 681 682 683
            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
684 685

    def stuffsource(self, source):
686
        "Stuff source in the filename cache"
David Scherer's avatar
David Scherer committed
687 688
        filename = "<pyshell#%d>" % self.gid
        self.gid = self.gid + 1
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
689
        lines = source.split("\n")
David Scherer's avatar
David Scherer committed
690 691
        linecache.cache[filename] = len(source)+1, 0, lines, filename
        return filename
692

693 694 695
    def prepend_syspath(self, filename):
        "Prepend sys.path with file's directory if not already included"
        self.runcommand("""if 1:
696
            _filename = %r
697 698 699 700 701 702
            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
703
            \n""" % (filename,))
704

David Scherer's avatar
David Scherer committed
705
    def showsyntaxerror(self, filename=None):
706
        """Override Interactive Interpreter method: Use Colorizing
707 708 709 710 711

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

        """
712 713 714
        tkconsole = self.tkconsole
        text = tkconsole.text
        text.tag_remove("ERROR", "1.0", "end")
David Scherer's avatar
David Scherer committed
715
        type, value, tb = sys.exc_info()
716 717 718
        msg = getattr(value, 'msg', '') or value or "<no detail available>"
        lineno = getattr(value, 'lineno', '') or 1
        offset = getattr(value, 'offset', '') or 0
719 720 721 722
        if offset == 0:
            lineno += 1 #mark end of offending line
        if lineno == 1:
            pos = "iomark + %d chars" % (offset-1)
David Scherer's avatar
David Scherer committed
723
        else:
724 725 726 727 728 729
            pos = "iomark linestart + %d lines + %d chars" % \
                  (lineno-1, offset-1)
        tkconsole.colorize_syntax_error(text, pos)
        tkconsole.resetoutput()
        self.write("SyntaxError: %s\n" % msg)
        tkconsole.showprompt()
David Scherer's avatar
David Scherer committed
730 731

    def showtraceback(self):
732
        "Extend base class method to reset output properly"
David Scherer's avatar
David Scherer committed
733 734 735
        self.tkconsole.resetoutput()
        self.checklinecache()
        InteractiveInterpreter.showtraceback(self)
Chui Tey's avatar
Chui Tey committed
736 737
        if self.tkconsole.getvar("<<toggle-jit-stack-viewer>>"):
            self.tkconsole.open_stack_viewer()
David Scherer's avatar
David Scherer committed
738 739 740

    def checklinecache(self):
        c = linecache.cache
741
        for key in list(c.keys()):
David Scherer's avatar
David Scherer committed
742 743 744
            if key[:1] + key[-1:] != "<>":
                del c[key]

Chui Tey's avatar
Chui Tey committed
745
    def runcommand(self, code):
746
        "Run the code without invoking the debugger"
Chui Tey's avatar
Chui Tey committed
747 748
        # The code better not raise an exception!
        if self.tkconsole.executing:
749
            self.display_executing_dialog()
Chui Tey's avatar
Chui Tey committed
750 751
            return 0
        if self.rpcclt:
752
            self.rpcclt.remotequeue("exec", "runcode", (code,), {})
Chui Tey's avatar
Chui Tey committed
753
        else:
754
            exec(code, self.locals)
Chui Tey's avatar
Chui Tey committed
755 756
        return 1

David Scherer's avatar
David Scherer committed
757
    def runcode(self, code):
758
        "Override base class method"
Chui Tey's avatar
Chui Tey committed
759
        if self.tkconsole.executing:
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
760
            self.interp.restart_subprocess()
Chui Tey's avatar
Chui Tey committed
761
        self.checklinecache()
762 763 764
        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
765 766
        debugger = self.debugger
        try:
767
            self.tkconsole.beginexecuting()
768 769 770 771 772 773 774 775 776 777 778 779 780 781
            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):
782
                    raise
783
                else:
784
                    self.showtraceback()
785 786 787 788 789 790
            else:
                raise
        except:
            if use_subprocess:
                print("IDLE internal error in runcode()",
                      file=self.tkconsole.stderr)
David Scherer's avatar
David Scherer committed
791
                self.showtraceback()
792 793 794 795 796 797 798
                self.tkconsole.endexecuting()
            else:
                if self.tkconsole.canceled:
                    self.tkconsole.canceled = False
                    print("KeyboardInterrupt", file=self.tkconsole.stderr)
                else:
                    self.showtraceback()
799 800
        finally:
            if not use_subprocess:
801 802 803 804
                try:
                    self.tkconsole.endexecuting()
                except AttributeError:  # shell may have closed
                    pass
David Scherer's avatar
David Scherer committed
805 806

    def write(self, s):
807
        "Override base class method"
808
        return self.tkconsole.stderr.write(s)
David Scherer's avatar
David Scherer committed
809

810 811 812
    def display_port_binding_error(self):
        tkMessageBox.showerror(
            "Port Binding Error",
813 814 815 816 817 818
            "IDLE can't bind to a TCP/IP port, which is necessary to "
            "communicate with its Python execution server.  This might be "
            "because no networking is installed on this computer.  "
            "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.",
819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836
            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
837 838
class PyShell(OutputWindow):

839
    shell_title = "Python " + python_version() + " Shell"
David Scherer's avatar
David Scherer committed
840 841 842 843 844

    # Override classes
    ColorDelegator = ModifiedColorDelegator
    UndoDelegator = ModifiedUndoDelegator

845
    # Override menus
846 847 848
    menu_specs = [
        ("file", "_File"),
        ("edit", "_Edit"),
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
849
        ("debug", "_Debug"),
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
850
        ("options", "_Options"),
851 852 853
        ("windows", "_Windows"),
        ("help", "_Help"),
    ]
David Scherer's avatar
David Scherer committed
854

855
    if sys.platform == "darwin":
856 857 858
        menu_specs[-2] = ("windows", "_Window")


David Scherer's avatar
David Scherer committed
859
    # New classes
860
    from idlelib.IdleHistory import History
David Scherer's avatar
David Scherer committed
861 862

    def __init__(self, flist=None):
863
        if use_subprocess:
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
864 865
            ms = self.menu_specs
            if ms[2][0] != "shell":
866
                ms.insert(2, ("shell", "She_ll"))
David Scherer's avatar
David Scherer committed
867 868 869 870 871 872
        self.interp = ModifiedInterpreter(self)
        if flist is None:
            root = Tk()
            fixwordbreaks(root)
            root.withdraw()
            flist = PyShellFileList(root)
873
        #
David Scherer's avatar
David Scherer committed
874
        OutputWindow.__init__(self, flist, None, None)
875
        #
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
876 877 878 879 880
##        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
881
        #
David Scherer's avatar
David Scherer committed
882 883 884 885 886 887 888
        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("<<end-of-file>>", self.eof_callback)
        text.bind("<<open-stack-viewer>>", self.open_stack_viewer)
889
        text.bind("<<toggle-debugger>>", self.toggle_debugger)
David Scherer's avatar
David Scherer committed
890
        text.bind("<<toggle-jit-stack-viewer>>", self.toggle_jit_stack_viewer)
891 892 893
        if use_subprocess:
            text.bind("<<view-restart>>", self.view_restart_mark)
            text.bind("<<restart-shell>>", self.restart_shell)
894
        #
David Scherer's avatar
David Scherer committed
895 896 897
        self.save_stdout = sys.stdout
        self.save_stderr = sys.stderr
        self.save_stdin = sys.stdin
898
        from idlelib import IOBinding
899 900 901 902
        self.stdin = PseudoInputFile(self, "stdin", IOBinding.encoding)
        self.stdout = PseudoOutputFile(self, "stdout", IOBinding.encoding)
        self.stderr = PseudoOutputFile(self, "stderr", IOBinding.encoding)
        self.console = PseudoOutputFile(self, "console", IOBinding.encoding)
Chui Tey's avatar
Chui Tey committed
903 904
        if not use_subprocess:
            sys.stdout = self.stdout
905
            sys.stderr = self.stderr
906
            sys.stdin = self.stdin
907 908 909 910 911 912 913 914
        try:
            # page help() text to shell.
            import pydoc # import must be done here to capture i/o rebinding.
            # XXX KBK 27Dec07 use a textView someday, but must work w/o subproc
            pydoc.pager = pydoc.plainpager
        except:
            sys.stderr = sys.__stderr__
            raise
915
        #
David Scherer's avatar
David Scherer committed
916
        self.history = self.History(self.text)
917
        #
918
        self.pollinterval = 50  # millisec
Chui Tey's avatar
Chui Tey committed
919

920 921 922
    def get_standard_extension_names(self):
        return idleConf.GetExtensions(shell_only=True)

Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
923 924 925 926 927
    reading = False
    executing = False
    canceled = False
    endoffile = False
    closing = False
928
    _stop_readline_flag = False
David Scherer's avatar
David Scherer committed
929

930
    def set_warning_stream(self, stream):
931 932
        global warning_stream
        warning_stream = stream
933 934 935 936

    def get_warning_stream(self):
        return warning_stream

David Scherer's avatar
David Scherer committed
937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954
    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
955
    def toggle_jit_stack_viewer(self, event=None):
David Scherer's avatar
David Scherer committed
956 957 958 959 960 961 962
        pass # All we need is the variable

    def close_debugger(self):
        db = self.interp.getdebugger()
        if db:
            self.interp.setdebugger(None)
            db.close()
963 964
            if self.interp.rpcclt:
                RemoteDebugger.close_remote_debugger(self.interp.rpcclt)
David Scherer's avatar
David Scherer committed
965 966 967 968 969 970 971
            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
972
        if self.interp.rpcclt:
973 974 975 976 977 978
            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
979 980 981 982
        sys.ps1 = "[DEBUG ON]\n>>> "
        self.showprompt()
        self.set_debugger_indicator()

David Scherer's avatar
David Scherer committed
983
    def beginexecuting(self):
984
        "Helper for ModifiedInterpreter"
David Scherer's avatar
David Scherer committed
985 986 987 988
        self.resetoutput()
        self.executing = 1

    def endexecuting(self):
989
        "Helper for ModifiedInterpreter"
David Scherer's avatar
David Scherer committed
990 991
        self.executing = 0
        self.canceled = 0
Chui Tey's avatar
Chui Tey committed
992
        self.showprompt()
David Scherer's avatar
David Scherer committed
993 994

    def close(self):
995
        "Extend EditorWindow.close()"
David Scherer's avatar
David Scherer committed
996
        if self.executing:
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
997
            response = tkMessageBox.askokcancel(
David Scherer's avatar
David Scherer committed
998
                "Kill?",
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
999
                "The program is still running!\n Do you want to kill it?",
David Scherer's avatar
David Scherer committed
1000
                default="ok",
1001
                parent=self.text)
1002
            if response is False:
David Scherer's avatar
David Scherer committed
1003
                return "cancel"
1004
        self.stop_readline()
1005
        self.canceled = True
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
1006
        self.closing = True
1007
        return EditorWindow.close(self)
David Scherer's avatar
David Scherer committed
1008 1009

    def _close(self):
1010
        "Extend EditorWindow._close(), shut down debugger and execution server"
David Scherer's avatar
David Scherer committed
1011
        self.close_debugger()
1012 1013
        if use_subprocess:
            self.interp.kill_subprocess()
David Scherer's avatar
David Scherer committed
1014 1015 1016 1017 1018 1019 1020 1021 1022
        # 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
1023
        EditorWindow._close(self)
David Scherer's avatar
David Scherer committed
1024 1025

    def ispythonsource(self, filename):
1026
        "Override EditorWindow method: never remove the colorizer"
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
1027
        return True
David Scherer's avatar
David Scherer committed
1028 1029 1030 1031

    def short_title(self):
        return self.shell_title

1032
    COPYRIGHT = \
1033
          'Type "copyright", "credits" or "license()" for more information.'
1034

David Scherer's avatar
David Scherer committed
1035
    def begin(self):
1036
        self.text.mark_set("iomark", "insert")
David Scherer's avatar
David Scherer committed
1037
        self.resetoutput()
1038 1039
        if use_subprocess:
            nosub = ''
1040 1041 1042
            client = self.interp.start_subprocess()
            if not client:
                self.close()
1043
                return False
1044
        else:
1045
            nosub = ("==== No Subprocess ====\n\n" +
Andrew Svetlov's avatar
Andrew Svetlov committed
1046
                    "WARNING: Running IDLE without a Subprocess is deprecated\n" +
1047 1048
                    "and will be removed in a later version. See Help/IDLE Help\n" +
                    "for details.\n\n")
1049 1050
            sys.displayhook = rpc.displayhook

1051 1052
        self.write("Python %s on %s\n%s\n%s" %
                   (sys.version, sys.platform, self.COPYRIGHT, nosub))
David Scherer's avatar
David Scherer committed
1053
        self.showprompt()
1054 1055
        import tkinter
        tkinter._default_root = None # 03Jan04 KBK What's this?
1056
        return True
David Scherer's avatar
David Scherer committed
1057

1058 1059 1060 1061 1062 1063
    def stop_readline(self):
        if not self.reading:  # no nested mainloop to exit.
            return
        self._stop_readline_flag = True
        self.top.quit()

David Scherer's avatar
David Scherer committed
1064 1065 1066 1067
    def readline(self):
        save = self.reading
        try:
            self.reading = 1
1068
            self.top.mainloop()  # nested mainloop()
David Scherer's avatar
David Scherer committed
1069 1070
        finally:
            self.reading = save
1071 1072 1073
        if self._stop_readline_flag:
            self._stop_readline_flag = False
            return ""
David Scherer's avatar
David Scherer committed
1074
        line = self.text.get("iomark", "end-1c")
1075 1076
        if len(line) == 0:  # may be EOF if we quit our mainloop with Ctrl-C
            line = "\n"
David Scherer's avatar
David Scherer committed
1077 1078 1079
        self.resetoutput()
        if self.canceled:
            self.canceled = 0
1080 1081
            if not use_subprocess:
                raise KeyboardInterrupt
David Scherer's avatar
David Scherer committed
1082 1083
        if self.endoffile:
            self.endoffile = 0
1084
            line = ""
David Scherer's avatar
David Scherer committed
1085 1086 1087
        return line

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

Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
1090
    def cancel_callback(self, event=None):
David Scherer's avatar
David Scherer committed
1091 1092 1093 1094 1095 1096 1097
        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()
1098
            self.interp.write("KeyboardInterrupt\n")
David Scherer's avatar
David Scherer committed
1099 1100 1101
            self.showprompt()
            return "break"
        self.endoffile = 0
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
1102
        self.canceled = 1
1103
        if (self.executing and self.interp.rpcclt):
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
1104 1105 1106 1107
            if self.interp.getdebugger():
                self.interp.restart_subprocess()
            else:
                self.interp.interrupt_subprocess()
1108 1109
        if self.reading:
            self.top.quit()  # exit the nested mainloop() in readline()
David Scherer's avatar
David Scherer committed
1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132
        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 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
1133
            self.newline_and_indent_event(event)
David Scherer's avatar
David Scherer committed
1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144
        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"):
1145
                    self.recall(sel, event)
David Scherer's avatar
David Scherer committed
1146 1147 1148 1149 1150 1151 1152 1153 1154 1155
                    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]):
1156
                self.recall(self.text.get(prev[0], prev[1]), event)
David Scherer's avatar
David Scherer committed
1157 1158 1159
                return "break"
            next = self.text.tag_nextrange("stdin", "insert")
            if next and self.text.compare("insert lineend", ">=", next[0]):
1160
                self.recall(self.text.get(next[0], next[1]), event)
David Scherer's avatar
David Scherer committed
1161
                return "break"
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
1162
            # No stdin mark -- just get the current line, less any prompt
1163 1164 1165 1166 1167 1168
            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
1169
            return "break"
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
1170
        # If we're between the beginning of the line and the iomark, i.e.
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
1171
        # in the prompt area, move to the end of the prompt
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
1172
        if self.text.compare("insert", "<", "iomark"):
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
1173
            self.text.mark_set("insert", "iomark")
David Scherer's avatar
David Scherer committed
1174 1175 1176
        # 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
1177
        if s and not s.strip():
David Scherer's avatar
David Scherer committed
1178 1179 1180 1181
            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
1182
            self.newline_and_indent_event(event)
David Scherer's avatar
David Scherer committed
1183 1184 1185 1186 1187 1188 1189
            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
1190
            self.newline_and_indent_event(event)
David Scherer's avatar
David Scherer committed
1191 1192 1193
        self.text.tag_add("stdin", "iomark", "end-1c")
        self.text.update_idletasks()
        if self.reading:
1194
            self.top.quit() # Break out of recursive mainloop()
David Scherer's avatar
David Scherer committed
1195 1196 1197 1198
        else:
            self.runit()
        return "break"

1199
    def recall(self, s, event):
1200 1201 1202 1203
        # 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')
1204 1205 1206 1207
        self.text.undo_block_start()
        try:
            self.text.tag_remove("sel", "1.0", "end")
            self.text.mark_set("insert", "end-1c")
1208 1209
            prefix = self.text.get("insert linestart", "insert")
            if prefix.rstrip().endswith(':'):
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
1210
                self.newline_and_indent_event(event)
1211 1212
                prefix = self.text.get("insert linestart", "insert")
            self.text.insert("insert", lines[0].strip())
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
1213
            if len(lines) > 1:
1214 1215
                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
1216
                for line in lines[1:]:
1217 1218 1219 1220
                    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())
1221 1222 1223
        finally:
            self.text.see("insert")
            self.text.undo_block_stop()
David Scherer's avatar
David Scherer committed
1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239

    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
1240 1241
        if self.interp.rpcclt:
            return self.interp.remote_stack_viewer()
David Scherer's avatar
David Scherer committed
1242 1243 1244 1245 1246 1247 1248 1249
        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
1250
        from idlelib.StackViewer import StackBrowser
David Scherer's avatar
David Scherer committed
1251 1252
        sv = StackBrowser(self.root, self.flist)

Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
1253 1254 1255 1256 1257
    def view_restart_mark(self, event=None):
        self.text.see("iomark")
        self.text.see("restart")

    def restart_shell(self, event=None):
1258 1259
        "Callback for Run/Restart Shell Cntl-F6"
        self.interp.restart_subprocess(with_cwd=True)
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
1260

David Scherer's avatar
David Scherer committed
1261 1262 1263 1264 1265 1266 1267 1268
    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
1269
        self.set_line_and_column()
1270
        self.io.reset_undo()
David Scherer's avatar
David Scherer committed
1271 1272 1273 1274

    def resetoutput(self):
        source = self.text.get("iomark", "end-1c")
        if self.history:
1275
            self.history.store(source)
David Scherer's avatar
David Scherer committed
1276 1277 1278
        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
1279
        self.set_line_and_column()
David Scherer's avatar
David Scherer committed
1280 1281

    def write(self, s, tags=()):
1282 1283 1284 1285 1286 1287 1288 1289 1290 1291
        if isinstance(s, str) and len(s) and max(s) > '\uffff':
            # Tk doesn't support outputting non-BMP characters
            # Let's assume what printed string is not very long,
            # find first non-BMP character and construct informative
            # UnicodeEncodeError exception.
            for start, char in enumerate(s):
                if char > '\uffff':
                    break
            raise UnicodeEncodeError("UCS-2", char, start, start+1,
                                     'Non-BMP character not supported in Tk')
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
1292 1293
        try:
            self.text.mark_gravity("iomark", "right")
1294
            count = OutputWindow.write(self, s, tags, "iomark")
Kurt B. Kaiser's avatar
Kurt B. Kaiser committed
1295 1296
            self.text.mark_gravity("iomark", "left")
        except:
1297 1298
            raise ###pass  # ### 11Aug07 KBK if we are expecting exceptions
                           # let's find out what they are and be specific.
David Scherer's avatar
David Scherer committed
1299 1300
        if self.canceled:
            self.canceled = 0
1301 1302
            if not use_subprocess:
                raise KeyboardInterrupt
1303
        return count
David Scherer's avatar
David Scherer committed
1304

1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317
    def rmenu_check_cut(self):
        try:
            if self.text.compare('sel.first', '<', 'iomark'):
                return 'disabled'
        except TclError: # no selection, so the index 'sel.first' doesn't exist
            return 'disabled'
        return super().rmenu_check_cut()

    def rmenu_check_paste(self):
        if self.text.compare('insert','<','iomark'):
            return 'disabled'
        return super().rmenu_check_paste()

1318
class PseudoFile(io.TextIOBase):
David Scherer's avatar
David Scherer committed
1319

1320
    def __init__(self, shell, tags, encoding=None):
David Scherer's avatar
David Scherer committed
1321 1322
        self.shell = shell
        self.tags = tags
1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340
        self._encoding = encoding

    @property
    def encoding(self):
        return self._encoding

    @property
    def name(self):
        return '<%s>' % self.tags

    def isatty(self):
        return True


class PseudoOutputFile(PseudoFile):

    def writable(self):
        return True
David Scherer's avatar
David Scherer committed
1341 1342

    def write(self, s):
1343 1344
        if self.closed:
            raise ValueError("write to closed file")
1345 1346 1347 1348 1349
        if type(s) is not str:
            if not isinstance(s, str):
                raise TypeError('must be str, not ' + type(s).__name__)
            # See issue #19481
            s = str.__str__(s)
1350
        return self.shell.write(s, self.tags)
David Scherer's avatar
David Scherer committed
1351 1352


1353
class PseudoInputFile(PseudoFile):
David Scherer's avatar
David Scherer committed
1354

1355 1356 1357
    def __init__(self, shell, tags, encoding=None):
        PseudoFile.__init__(self, shell, tags, encoding)
        self._line_buffer = ''
David Scherer's avatar
David Scherer committed
1358

1359 1360
    def readable(self):
        return True
1361

1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394
    def read(self, size=-1):
        if self.closed:
            raise ValueError("read from closed file")
        if size is None:
            size = -1
        elif not isinstance(size, int):
            raise TypeError('must be int, not ' + type(size).__name__)
        result = self._line_buffer
        self._line_buffer = ''
        if size < 0:
            while True:
                line = self.shell.readline()
                if not line: break
                result += line
        else:
            while len(result) < size:
                line = self.shell.readline()
                if not line: break
                result += line
            self._line_buffer = result[size:]
            result = result[:size]
        return result

    def readline(self, size=-1):
        if self.closed:
            raise ValueError("read from closed file")
        if size is None:
            size = -1
        elif not isinstance(size, int):
            raise TypeError('must be int, not ' + type(size).__name__)
        line = self._line_buffer or self.shell.readline()
        if size < 0:
            size = len(line)
1395 1396 1397
        eol = line.find('\n', 0, size)
        if eol >= 0:
            size = eol + 1
1398 1399
        self._line_buffer = line[size:]
        return line[:size]
1400

1401 1402 1403
    def close(self):
        self.shell.close()

1404

David Scherer's avatar
David Scherer committed
1405 1406
usage_msg = """\

1407 1408 1409
USAGE: idle  [-deins] [-t title] [file]*
       idle  [-dns] [-t title] (-c cmd | -r file) [arg]*
       idle  [-dns] [-t title] - [arg]*
1410

1411
  -h         print this help message and exit
1412 1413
  -n         run IDLE without a subprocess (DEPRECATED,
             see Help/IDLE Help for details)
1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433

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
1434

1435 1436
idle
        Open an edit window or shell depending on IDLE's configuration.
1437

1438 1439 1440 1441 1442 1443 1444
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".

1445
idle -c "import sys; print(sys.argv)" "foo"
1446 1447 1448 1449 1450 1451 1452 1453
        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].

1454
echo "import sys; print(sys.argv)" | idle - "foobar"
1455 1456
        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
1457 1458
"""

1459
def main():
1460 1461
    global flist, root, use_subprocess

1462
    capture_warnings(True)
1463
    use_subprocess = True
1464
    enable_shell = False
1465 1466
    enable_edit = False
    debug = False
1467 1468
    cmd = None
    script = None
1469
    startup = False
1470
    try:
1471
        opts, args = getopt.getopt(sys.argv[1:], "c:deihnr:st:")
1472
    except getopt.error as msg:
1473
        print("Error: %s\n%s" % (msg, usage_msg), file=sys.stderr)
1474 1475 1476 1477
        sys.exit(2)
    for o, a in opts:
        if o == '-c':
            cmd = a
1478
            enable_shell = True
1479
        if o == '-d':
1480 1481
            debug = True
            enable_shell = True
1482
        if o == '-e':
1483 1484 1485 1486 1487 1488
            enable_edit = True
        if o == '-h':
            sys.stdout.write(usage_msg)
            sys.exit()
        if o == '-i':
            enable_shell = True
1489
        if o == '-n':
1490 1491
            print(" Warning: running IDLE without a subprocess is deprecated.",
                  file=sys.stderr)
1492
            use_subprocess = False
1493 1494
        if o == '-r':
            script = a
1495 1496 1497
            if os.path.isfile(script):
                pass
            else:
1498
                print("No script file: ", script)
1499 1500
                sys.exit()
            enable_shell = True
1501
        if o == '-s':
1502 1503
            startup = True
            enable_shell = True
1504 1505
        if o == '-t':
            PyShell.shell_title = a
1506 1507 1508 1509 1510
            enable_shell = True
    if args and args[0] == '-':
        cmd = sys.stdin.read()
        enable_shell = True
    # process sys.argv and sys.path:
1511 1512
    for i in range(len(sys.path)):
        sys.path[i] = os.path.abspath(sys.path[i])
1513 1514 1515 1516 1517 1518 1519 1520 1521
    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 = []
1522 1523
        for filename in args:
            pathx.append(os.path.dirname(filename))
1524 1525 1526 1527
        for dir in pathx:
            dir = os.path.abspath(dir)
            if not dir in sys.path:
                sys.path.insert(0, dir)
1528
    else:
1529 1530
        dir = os.getcwd()
        if dir not in sys.path:
1531
            sys.path.insert(0, dir)
1532 1533
    # check the IDLE settings configuration (but command line overrides)
    edit_start = idleConf.GetOption('main', 'General',
1534
                                    'editor-on-startup', type='bool')
1535
    enable_edit = enable_edit or edit_start
1536
    enable_shell = enable_shell or not enable_edit
1537
    # start editor and/or shell windows:
1538
    root = Tk(className="Idle")
1539

1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551
    # set application icon
    icondir = os.path.join(os.path.dirname(__file__), 'Icons')
    if system() == 'Windows':
        iconfile = os.path.join(icondir, 'idle.ico')
        root.wm_iconbitmap(default=iconfile)
    elif TkVersion >= 8.5:
        ext = '.png' if TkVersion >= 8.6 else '.gif'
        iconfiles = [os.path.join(icondir, 'idle_%d%s' % (size, ext))
                     for size in (16, 32, 48)]
        icons = [PhotoImage(file=iconfile) for iconfile in iconfiles]
        root.wm_iconphoto(True, *icons)

1552 1553 1554
    fixwordbreaks(root)
    root.withdraw()
    flist = PyShellFileList(root)
1555 1556
    macosxSupport.setupApp(root, flist)

1557 1558
    if enable_edit:
        if not (cmd or script):
1559 1560 1561 1562
            for filename in args[:]:
                if flist.open(filename) is None:
                    # filename is a directory actually, disconsider it
                    args.remove(filename)
1563 1564
            if not args:
                flist.new()
1565

1566
    if enable_shell:
1567 1568
        shell = flist.open_shell()
        if not shell:
1569
            return # couldn't open shell
1570
        if macosxSupport.isAquaTk() and flist.dict:
1571 1572 1573 1574 1575
            # 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()
1576 1577
    else:
        shell = flist.pyshell
1578

1579 1580
    # Handle remaining options. If any of these are set, enable_shell
    # was set also, so shell must be true to reach here.
1581 1582
    if debug:
        shell.open_debugger()
1583 1584 1585 1586
    if startup:
        filename = os.environ.get("IDLESTARTUP") or \
                   os.environ.get("PYTHONSTARTUP")
        if filename and os.path.isfile(filename):
1587
            shell.interp.execfile(filename)
1588
    if cmd or script:
1589 1590
        shell.interp.runcommand("""if 1:
            import sys as _sys
1591
            _sys.argv = %r
1592
            del _sys
1593
            \n""" % (sys.argv,))
1594 1595 1596
        if cmd:
            shell.interp.execsource(cmd)
        elif script:
1597
            shell.interp.prepend_syspath(script)
1598
            shell.interp.execfile(script)
1599 1600 1601 1602 1603 1604 1605 1606
    elif shell:
        # If there is a shell window and no cmd or script in progress,
        # check for problematic OS X Tk versions and print a warning
        # message in the IDLE shell window; this is less intrusive
        # than always opening a separate window.
        tkversionwarning = macosxSupport.tkVersionWarning(root)
        if tkversionwarning:
            shell.interp.runcommand("print('%s')" % tkversionwarning)
1607

1608 1609
    while flist.inversedict:  # keep IDLE running while files are open.
        root.mainloop()
1610
    root.destroy()
1611
    capture_warnings(False)
1612

David Scherer's avatar
David Scherer committed
1613
if __name__ == "__main__":
1614
    sys.modules['PyShell'] = sys.modules['__main__']
David Scherer's avatar
David Scherer committed
1615
    main()
1616 1617

capture_warnings(False)  # Make sure turned off; see issue 18081