PyShell.py revision 73360a3e61274ffcc4c9fc3d09746bd6603e92a5
1#! /usr/bin/env python 2 3import os 4import os.path 5import sys 6import string 7import getopt 8import re 9import socket 10import time 11import threading 12import traceback 13import types 14import exceptions 15 16import linecache 17from code import InteractiveInterpreter 18 19try: 20 from Tkinter import * 21except ImportError: 22 print>>sys.__stderr__, "** IDLE can't import Tkinter. " \ 23 "Your Python may not be configured for Tk. **" 24 sys.exit(1) 25import tkMessageBox 26 27from EditorWindow import EditorWindow, fixwordbreaks 28from FileList import FileList 29from ColorDelegator import ColorDelegator 30from UndoDelegator import UndoDelegator 31from OutputWindow import OutputWindow 32from configHandler import idleConf 33import idlever 34 35import rpc 36import Debugger 37import RemoteDebugger 38 39IDENTCHARS = string.ascii_letters + string.digits + "_" 40LOCALHOST = '127.0.0.1' 41 42try: 43 from signal import SIGTERM 44except ImportError: 45 SIGTERM = 15 46 47# Change warnings module to write to sys.__stderr__ 48try: 49 import warnings 50except ImportError: 51 pass 52else: 53 def idle_showwarning(message, category, filename, lineno): 54 file = sys.__stderr__ 55 file.write(warnings.formatwarning(message, category, filename, lineno)) 56 warnings.showwarning = idle_showwarning 57 58def extended_linecache_checkcache(orig_checkcache=linecache.checkcache): 59 """Extend linecache.checkcache to preserve the <pyshell#...> entries 60 61 Rather than repeating the linecache code, patch it to save the pyshell# 62 entries, call the original linecache.checkcache(), and then restore the 63 saved entries. Assigning the orig_checkcache keyword arg freezes its value 64 at definition time to the (original) method linecache.checkcache(), i.e. 65 makes orig_checkcache lexical. 66 67 """ 68 cache = linecache.cache 69 save = {} 70 for filename in cache.keys(): 71 if filename[:1] + filename[-1:] == '<>': 72 save[filename] = cache[filename] 73 orig_checkcache() 74 cache.update(save) 75 76# Patch linecache.checkcache(): 77linecache.checkcache = extended_linecache_checkcache 78 79 80class PyShellEditorWindow(EditorWindow): 81 "Regular text edit window when a shell is present" 82 83 def __init__(self, *args): 84 self.breakpoints = [] 85 EditorWindow.__init__(self, *args) 86 self.text.bind("<<set-breakpoint-here>>", self.set_breakpoint_here) 87 self.text.bind("<<clear-breakpoint-here>>", self.clear_breakpoint_here) 88 self.text.bind("<<open-python-shell>>", self.flist.open_shell) 89 90 self.breakpointPath = os.path.join(idleConf.GetUserCfgDir(), 91 'breakpoints.lst') 92 # whenever a file is changed, restore breakpoints 93 if self.io.filename: self.restore_file_breaks() 94 def filename_changed_hook(old_hook=self.io.filename_change_hook, 95 self=self): 96 self.restore_file_breaks() 97 old_hook() 98 self.io.set_filename_change_hook(filename_changed_hook) 99 100 rmenu_specs = [("Set Breakpoint", "<<set-breakpoint-here>>"), 101 ("Clear Breakpoint", "<<clear-breakpoint-here>>")] 102 103 def set_breakpoint(self, lineno): 104 text = self.text 105 filename = self.io.filename 106 text.tag_add("BREAK", "%d.0" % lineno, "%d.0" % (lineno+1)) 107 try: 108 i = self.breakpoints.index(lineno) 109 except ValueError: # only add if missing, i.e. do once 110 self.breakpoints.append(lineno) 111 try: # update the subprocess debugger 112 debug = self.flist.pyshell.interp.debugger 113 debug.set_breakpoint_here(filename, lineno) 114 except: # but debugger may not be active right now.... 115 pass 116 117 def set_breakpoint_here(self, event=None): 118 text = self.text 119 filename = self.io.filename 120 if not filename: 121 text.bell() 122 return 123 lineno = int(float(text.index("insert"))) 124 self.set_breakpoint(lineno) 125 126 def clear_breakpoint_here(self, event=None): 127 text = self.text 128 filename = self.io.filename 129 if not filename: 130 text.bell() 131 return 132 lineno = int(float(text.index("insert"))) 133 try: 134 self.breakpoints.remove(lineno) 135 except: 136 pass 137 text.tag_remove("BREAK", "insert linestart",\ 138 "insert lineend +1char") 139 try: 140 debug = self.flist.pyshell.interp.debugger 141 debug.clear_breakpoint_here(filename, lineno) 142 except: 143 pass 144 145 def clear_file_breaks(self): 146 if self.breakpoints: 147 text = self.text 148 filename = self.io.filename 149 if not filename: 150 text.bell() 151 return 152 self.breakpoints = [] 153 text.tag_remove("BREAK", "1.0", END) 154 try: 155 debug = self.flist.pyshell.interp.debugger 156 debug.clear_file_breaks(filename) 157 except: 158 pass 159 160 def store_file_breaks(self): 161 "Save breakpoints when file is saved" 162 # XXX 13 Dec 2002 KBK Currently the file must be saved before it can 163 # be run. The breaks are saved at that time. If we introduce 164 # a temporary file save feature the save breaks functionality 165 # needs to be re-verified, since the breaks at the time the 166 # temp file is created may differ from the breaks at the last 167 # permanent save of the file. Currently, a break introduced 168 # after a save will be effective, but not persistent. 169 # This is necessary to keep the saved breaks synched with the 170 # saved file. 171 # 172 # Breakpoints are set as tagged ranges in the text. Certain 173 # kinds of edits cause these ranges to be deleted: Inserting 174 # or deleting a line just before a breakpoint, and certain 175 # deletions prior to a breakpoint. These issues need to be 176 # investigated and understood. It's not clear if they are 177 # Tk issues or IDLE issues, or whether they can actually 178 # be fixed. Since a modified file has to be saved before it is 179 # run, and since self.breakpoints (from which the subprocess 180 # debugger is loaded) is updated during the save, the visible 181 # breaks stay synched with the subprocess even if one of these 182 # unexpected breakpoint deletions occurs. 183 breaks = self.breakpoints 184 filename = self.io.filename 185 try: 186 lines = open(self.breakpointPath,"r").readlines() 187 except IOError: 188 lines = [] 189 new_file = open(self.breakpointPath,"w") 190 for line in lines: 191 if not line.startswith(filename + '='): 192 new_file.write(line) 193 self.update_breakpoints() 194 breaks = self.breakpoints 195 if breaks: 196 new_file.write(filename + '=' + str(breaks) + '\n') 197 new_file.close() 198 199 def restore_file_breaks(self): 200 self.text.update() # this enables setting "BREAK" tags to be visible 201 filename = self.io.filename 202 if filename is None: 203 return 204 if os.path.isfile(self.breakpointPath): 205 lines = open(self.breakpointPath,"r").readlines() 206 for line in lines: 207 if line.startswith(filename + '='): 208 breakpoint_linenumbers = eval(line[len(filename)+1:]) 209 for breakpoint_linenumber in breakpoint_linenumbers: 210 self.set_breakpoint(breakpoint_linenumber) 211 212 def update_breakpoints(self): 213 "Retrieves all the breakpoints in the current window" 214 text = self.text 215 ranges = text.tag_ranges("BREAK") 216 linenumber_list = self.ranges_to_linenumbers(ranges) 217 self.breakpoints = linenumber_list 218 219 def ranges_to_linenumbers(self, ranges): 220 lines = [] 221 for index in range(0, len(ranges), 2): 222 lineno = int(float(ranges[index])) 223 end = int(float(ranges[index+1])) 224 while lineno < end: 225 lines.append(lineno) 226 lineno += 1 227 return lines 228 229# XXX 13 Dec 2002 KBK Not used currently 230# def saved_change_hook(self): 231# "Extend base method - clear breaks if module is modified" 232# if not self.get_saved(): 233# self.clear_file_breaks() 234# EditorWindow.saved_change_hook(self) 235 236 def _close(self): 237 "Extend base method - clear breaks when module is closed" 238 self.clear_file_breaks() 239 EditorWindow._close(self) 240 241 242class PyShellFileList(FileList): 243 "Extend base class: file list when a shell is present" 244 245 EditorWindow = PyShellEditorWindow 246 247 pyshell = None 248 249 def open_shell(self, event=None): 250 if self.pyshell: 251 self.pyshell.wakeup() 252 else: 253 self.pyshell = PyShell(self) 254 if self.pyshell: 255 if not self.pyshell.begin(): 256 return None 257 return self.pyshell 258 259 260class ModifiedColorDelegator(ColorDelegator): 261 "Extend base class: colorizer for the shell window itself" 262 263 def __init__(self): 264 ColorDelegator.__init__(self) 265 self.LoadTagDefs() 266 267 def recolorize_main(self): 268 self.tag_remove("TODO", "1.0", "iomark") 269 self.tag_add("SYNC", "1.0", "iomark") 270 ColorDelegator.recolorize_main(self) 271 272 def LoadTagDefs(self): 273 ColorDelegator.LoadTagDefs(self) 274 theme = idleConf.GetOption('main','Theme','name') 275 self.tagdefs.update({ 276 "stdin": {'background':None,'foreground':None}, 277 "stdout": idleConf.GetHighlight(theme, "stdout"), 278 "stderr": idleConf.GetHighlight(theme, "stderr"), 279 "console": idleConf.GetHighlight(theme, "console"), 280 None: idleConf.GetHighlight(theme, "normal"), 281 }) 282 283class ModifiedUndoDelegator(UndoDelegator): 284 "Extend base class: forbid insert/delete before the I/O mark" 285 286 def insert(self, index, chars, tags=None): 287 try: 288 if self.delegate.compare(index, "<", "iomark"): 289 self.delegate.bell() 290 return 291 except TclError: 292 pass 293 UndoDelegator.insert(self, index, chars, tags) 294 295 def delete(self, index1, index2=None): 296 try: 297 if self.delegate.compare(index1, "<", "iomark"): 298 self.delegate.bell() 299 return 300 except TclError: 301 pass 302 UndoDelegator.delete(self, index1, index2) 303 304 305class MyRPCClient(rpc.RPCClient): 306 307 def handle_EOF(self): 308 "Override the base class - just re-raise EOFError" 309 raise EOFError 310 311 312class ModifiedInterpreter(InteractiveInterpreter): 313 314 def __init__(self, tkconsole): 315 self.tkconsole = tkconsole 316 locals = sys.modules['__main__'].__dict__ 317 InteractiveInterpreter.__init__(self, locals=locals) 318 self.save_warnings_filters = None 319 self.restarting = False 320 self.subprocess_arglist = self.build_subprocess_arglist() 321 322 port = 8833 323 rpcclt = None 324 rpcpid = None 325 326 def spawn_subprocess(self): 327 args = self.subprocess_arglist 328 self.rpcpid = os.spawnv(os.P_NOWAIT, sys.executable, args) 329 330 def build_subprocess_arglist(self): 331 w = ['-W' + s for s in sys.warnoptions] 332 # Maybe IDLE is installed and is being accessed via sys.path, 333 # or maybe it's not installed and the idle.py script is being 334 # run from the IDLE source directory. 335 del_exitf = idleConf.GetOption('main', 'General', 'delete-exitfunc', 336 default=False, type='bool') 337 if __name__ == 'idlelib.PyShell': 338 command = "__import__('idlelib.run').run.main(%r)" % (del_exitf,) 339 else: 340 command = "__import__('run').main(%r)" % (del_exitf,) 341 if sys.platform[:3] == 'win' and ' ' in sys.executable: 342 # handle embedded space in path by quoting the argument 343 decorated_exec = '"%s"' % sys.executable 344 else: 345 decorated_exec = sys.executable 346 return [decorated_exec] + w + ["-c", command, str(self.port)] 347 348 def start_subprocess(self): 349 # spawning first avoids passing a listening socket to the subprocess 350 self.spawn_subprocess() 351 #time.sleep(20) # test to simulate GUI not accepting connection 352 addr = (LOCALHOST, self.port) 353 # Idle starts listening for connection on localhost 354 for i in range(3): 355 time.sleep(i) 356 try: 357 self.rpcclt = MyRPCClient(addr) 358 break 359 except socket.error, err: 360 pass 361 else: 362 self.display_port_binding_error() 363 return None 364 # Accept the connection from the Python execution server 365 self.rpcclt.listening_sock.settimeout(10) 366 try: 367 self.rpcclt.accept() 368 except socket.timeout, err: 369 self.display_no_subprocess_error() 370 return None 371 self.rpcclt.register("stdin", self.tkconsole) 372 self.rpcclt.register("stdout", self.tkconsole.stdout) 373 self.rpcclt.register("stderr", self.tkconsole.stderr) 374 self.rpcclt.register("flist", self.tkconsole.flist) 375 self.rpcclt.register("linecache", linecache) 376 self.rpcclt.register("interp", self) 377 self.transfer_path() 378 self.poll_subprocess() 379 return self.rpcclt 380 381 def restart_subprocess(self): 382 if self.restarting: 383 return self.rpcclt 384 self.restarting = True 385 # close only the subprocess debugger 386 debug = self.getdebugger() 387 if debug: 388 try: 389 # Only close subprocess debugger, don't unregister gui_adap! 390 RemoteDebugger.close_subprocess_debugger(self.rpcclt) 391 except: 392 pass 393 # Kill subprocess, spawn a new one, accept connection. 394 self.rpcclt.close() 395 self.unix_terminate() 396 console = self.tkconsole 397 was_executing = console.executing 398 console.executing = False 399 self.spawn_subprocess() 400 try: 401 self.rpcclt.accept() 402 except socket.timeout, err: 403 self.display_no_subprocess_error() 404 return None 405 self.transfer_path() 406 # annotate restart in shell window and mark it 407 console.text.delete("iomark", "end-1c") 408 if was_executing: 409 console.write('\n') 410 console.showprompt() 411 halfbar = ((int(console.width) - 16) // 2) * '=' 412 console.write(halfbar + ' RESTART ' + halfbar) 413 console.text.mark_set("restart", "end-1c") 414 console.text.mark_gravity("restart", "left") 415 console.showprompt() 416 # restart subprocess debugger 417 if debug: 418 # Restarted debugger connects to current instance of debug GUI 419 gui = RemoteDebugger.restart_subprocess_debugger(self.rpcclt) 420 # reload remote debugger breakpoints for all PyShellEditWindows 421 debug.load_breakpoints() 422 self.restarting = False 423 return self.rpcclt 424 425 def __request_interrupt(self): 426 self.rpcclt.remotecall("exec", "interrupt_the_server", (), {}) 427 428 def interrupt_subprocess(self): 429 threading.Thread(target=self.__request_interrupt).start() 430 431 def kill_subprocess(self): 432 try: 433 self.rpcclt.close() 434 except AttributeError: # no socket 435 pass 436 self.unix_terminate() 437 self.tkconsole.executing = False 438 self.rpcclt = None 439 440 def unix_terminate(self): 441 "UNIX: make sure subprocess is terminated and collect status" 442 if hasattr(os, 'kill'): 443 try: 444 os.kill(self.rpcpid, SIGTERM) 445 except OSError: 446 # process already terminated: 447 return 448 else: 449 try: 450 os.waitpid(self.rpcpid, 0) 451 except OSError: 452 return 453 454 def transfer_path(self): 455 self.runcommand("""if 1: 456 import sys as _sys 457 _sys.path = %r 458 del _sys 459 _msg = 'Use File/Exit or your end-of-file key to quit IDLE' 460 __builtins__.quit = __builtins__.exit = _msg 461 del _msg 462 \n""" % (sys.path,)) 463 464 active_seq = None 465 466 def poll_subprocess(self): 467 clt = self.rpcclt 468 if clt is None: 469 return 470 try: 471 response = clt.pollresponse(self.active_seq, wait=0.05) 472 except (EOFError, IOError, KeyboardInterrupt): 473 # lost connection or subprocess terminated itself, restart 474 # [the KBI is from rpc.SocketIO.handle_EOF()] 475 if self.tkconsole.closing: 476 return 477 response = None 478 self.restart_subprocess() 479 if response: 480 self.tkconsole.resetoutput() 481 self.active_seq = None 482 how, what = response 483 console = self.tkconsole.console 484 if how == "OK": 485 if what is not None: 486 print >>console, repr(what) 487 elif how == "EXCEPTION": 488 if self.tkconsole.getvar("<<toggle-jit-stack-viewer>>"): 489 self.remote_stack_viewer() 490 elif how == "ERROR": 491 errmsg = "PyShell.ModifiedInterpreter: Subprocess ERROR:\n" 492 print >>sys.__stderr__, errmsg, what 493 print >>console, errmsg, what 494 # we received a response to the currently active seq number: 495 self.tkconsole.endexecuting() 496 # Reschedule myself 497 if not self.tkconsole.closing: 498 self.tkconsole.text.after(self.tkconsole.pollinterval, 499 self.poll_subprocess) 500 501 debugger = None 502 503 def setdebugger(self, debugger): 504 self.debugger = debugger 505 506 def getdebugger(self): 507 return self.debugger 508 509 def open_remote_stack_viewer(self): 510 """Initiate the remote stack viewer from a separate thread. 511 512 This method is called from the subprocess, and by returning from this 513 method we allow the subprocess to unblock. After a bit the shell 514 requests the subprocess to open the remote stack viewer which returns a 515 static object looking at the last exceptiopn. It is queried through 516 the RPC mechanism. 517 518 """ 519 self.tkconsole.text.after(300, self.remote_stack_viewer) 520 return 521 522 def remote_stack_viewer(self): 523 import RemoteObjectBrowser 524 oid = self.rpcclt.remotequeue("exec", "stackviewer", ("flist",), {}) 525 if oid is None: 526 self.tkconsole.root.bell() 527 return 528 item = RemoteObjectBrowser.StubObjectTreeItem(self.rpcclt, oid) 529 from TreeWidget import ScrolledCanvas, TreeNode 530 top = Toplevel(self.tkconsole.root) 531 theme = idleConf.GetOption('main','Theme','name') 532 background = idleConf.GetHighlight(theme, 'normal')['background'] 533 sc = ScrolledCanvas(top, bg=background, highlightthickness=0) 534 sc.frame.pack(expand=1, fill="both") 535 node = TreeNode(sc.canvas, None, item) 536 node.expand() 537 # XXX Should GC the remote tree when closing the window 538 539 gid = 0 540 541 def execsource(self, source): 542 "Like runsource() but assumes complete exec source" 543 filename = self.stuffsource(source) 544 self.execfile(filename, source) 545 546 def execfile(self, filename, source=None): 547 "Execute an existing file" 548 if source is None: 549 source = open(filename, "r").read() 550 try: 551 code = compile(source, filename, "exec") 552 except (OverflowError, SyntaxError): 553 self.tkconsole.resetoutput() 554 tkerr = self.tkconsole.stderr 555 print>>tkerr, '*** Error in script or command!\n' 556 print>>tkerr, 'Traceback (most recent call last):' 557 InteractiveInterpreter.showsyntaxerror(self, filename) 558 self.tkconsole.showprompt() 559 else: 560 self.runcode(code) 561 562 def runsource(self, source): 563 "Extend base class method: Stuff the source in the line cache first" 564 filename = self.stuffsource(source) 565 self.more = 0 566 self.save_warnings_filters = warnings.filters[:] 567 warnings.filterwarnings(action="error", category=SyntaxWarning) 568 if isinstance(source, types.UnicodeType): 569 import IOBinding 570 try: 571 source = source.encode(IOBinding.encoding) 572 except UnicodeError: 573 self.tkconsole.resetoutput() 574 self.write("Unsupported characters in input") 575 return 576 try: 577 return InteractiveInterpreter.runsource(self, source, filename) 578 finally: 579 if self.save_warnings_filters is not None: 580 warnings.filters[:] = self.save_warnings_filters 581 self.save_warnings_filters = None 582 583 def stuffsource(self, source): 584 "Stuff source in the filename cache" 585 filename = "<pyshell#%d>" % self.gid 586 self.gid = self.gid + 1 587 lines = source.split("\n") 588 linecache.cache[filename] = len(source)+1, 0, lines, filename 589 return filename 590 591 def prepend_syspath(self, filename): 592 "Prepend sys.path with file's directory if not already included" 593 self.runcommand("""if 1: 594 _filename = %r 595 import sys as _sys 596 from os.path import dirname as _dirname 597 _dir = _dirname(_filename) 598 if not _dir in _sys.path: 599 _sys.path.insert(0, _dir) 600 del _filename, _sys, _dirname, _dir 601 \n""" % (filename,)) 602 603 def showsyntaxerror(self, filename=None): 604 """Extend base class method: Add Colorizing 605 606 Color the offending position instead of printing it and pointing at it 607 with a caret. 608 609 """ 610 text = self.tkconsole.text 611 stuff = self.unpackerror() 612 if stuff: 613 msg, lineno, offset, line = stuff 614 if lineno == 1: 615 pos = "iomark + %d chars" % (offset-1) 616 else: 617 pos = "iomark linestart + %d lines + %d chars" % \ 618 (lineno-1, offset-1) 619 text.tag_add("ERROR", pos) 620 text.see(pos) 621 char = text.get(pos) 622 if char and char in IDENTCHARS: 623 text.tag_add("ERROR", pos + " wordstart", pos) 624 self.tkconsole.resetoutput() 625 self.write("SyntaxError: %s\n" % str(msg)) 626 else: 627 self.tkconsole.resetoutput() 628 InteractiveInterpreter.showsyntaxerror(self, filename) 629 self.tkconsole.showprompt() 630 631 def unpackerror(self): 632 type, value, tb = sys.exc_info() 633 ok = type is SyntaxError 634 if ok: 635 try: 636 msg, (dummy_filename, lineno, offset, line) = value 637 if not offset: 638 offset = 0 639 except: 640 ok = 0 641 if ok: 642 return msg, lineno, offset, line 643 else: 644 return None 645 646 def showtraceback(self): 647 "Extend base class method to reset output properly" 648 self.tkconsole.resetoutput() 649 self.checklinecache() 650 InteractiveInterpreter.showtraceback(self) 651 if self.tkconsole.getvar("<<toggle-jit-stack-viewer>>"): 652 self.tkconsole.open_stack_viewer() 653 654 def checklinecache(self): 655 c = linecache.cache 656 for key in c.keys(): 657 if key[:1] + key[-1:] != "<>": 658 del c[key] 659 660 def runcommand(self, code): 661 "Run the code without invoking the debugger" 662 # The code better not raise an exception! 663 if self.tkconsole.executing: 664 self.display_executing_dialog() 665 return 0 666 if self.rpcclt: 667 self.rpcclt.remotequeue("exec", "runcode", (code,), {}) 668 else: 669 exec code in self.locals 670 return 1 671 672 def runcode(self, code): 673 "Override base class method" 674 if self.tkconsole.executing: 675 self.interp.restart_subprocess() 676 self.checklinecache() 677 if self.save_warnings_filters is not None: 678 warnings.filters[:] = self.save_warnings_filters 679 self.save_warnings_filters = None 680 debugger = self.debugger 681 try: 682 self.tkconsole.beginexecuting() 683 try: 684 if not debugger and self.rpcclt is not None: 685 self.active_seq = self.rpcclt.asyncqueue("exec", "runcode", 686 (code,), {}) 687 elif debugger: 688 debugger.run(code, self.locals) 689 else: 690 exec code in self.locals 691 except SystemExit: 692 if tkMessageBox.askyesno( 693 "Exit?", 694 "Do you want to exit altogether?", 695 default="yes", 696 master=self.tkconsole.text): 697 raise 698 else: 699 self.showtraceback() 700 except: 701 self.showtraceback() 702 finally: 703 if not use_subprocess: 704 self.tkconsole.endexecuting() 705 706 def write(self, s): 707 "Override base class method" 708 self.tkconsole.stderr.write(s) 709 710 def display_port_binding_error(self): 711 tkMessageBox.showerror( 712 "Port Binding Error", 713 "IDLE can't bind TCP/IP port 8833, which is necessary to " 714 "communicate with its Python execution server. Either " 715 "no networking is installed on this computer or another " 716 "process (another IDLE?) is using the port. Run IDLE with the -n " 717 "command line switch to start without a subprocess and refer to " 718 "Help/IDLE Help 'Running without a subprocess' for further " 719 "details.", 720 master=self.tkconsole.text) 721 722 def display_no_subprocess_error(self): 723 tkMessageBox.showerror( 724 "Subprocess Startup Error", 725 "IDLE's subprocess didn't make connection. Either IDLE can't " 726 "start a subprocess or personal firewall software is blocking " 727 "the connection.", 728 master=self.tkconsole.text) 729 730 def display_executing_dialog(self): 731 tkMessageBox.showerror( 732 "Already executing", 733 "The Python Shell window is already executing a command; " 734 "please wait until it is finished.", 735 master=self.tkconsole.text) 736 737 738class PyShell(OutputWindow): 739 740 shell_title = "Python Shell" 741 742 # Override classes 743 ColorDelegator = ModifiedColorDelegator 744 UndoDelegator = ModifiedUndoDelegator 745 746 # Override menus 747 menu_specs = [ 748 ("file", "_File"), 749 ("edit", "_Edit"), 750 ("debug", "_Debug"), 751 ("options", "_Options"), 752 ("windows", "_Windows"), 753 ("help", "_Help"), 754 ] 755 756 # New classes 757 from IdleHistory import History 758 759 def __init__(self, flist=None): 760 if use_subprocess: 761 ms = self.menu_specs 762 if ms[2][0] != "shell": 763 ms.insert(2, ("shell", "_Shell")) 764 self.interp = ModifiedInterpreter(self) 765 if flist is None: 766 root = Tk() 767 fixwordbreaks(root) 768 root.withdraw() 769 flist = PyShellFileList(root) 770 # 771 OutputWindow.__init__(self, flist, None, None) 772 # 773 import __builtin__ 774 __builtin__.quit = __builtin__.exit = "To exit, type Ctrl-D." 775 # 776 self.config(usetabs=1, indentwidth=8, context_use_ps1=1) 777 # 778 text = self.text 779 text.configure(wrap="char") 780 text.bind("<<newline-and-indent>>", self.enter_callback) 781 text.bind("<<plain-newline-and-indent>>", self.linefeed_callback) 782 text.bind("<<interrupt-execution>>", self.cancel_callback) 783 text.bind("<<beginning-of-line>>", self.home_callback) 784 text.bind("<<end-of-file>>", self.eof_callback) 785 text.bind("<<open-stack-viewer>>", self.open_stack_viewer) 786 text.bind("<<toggle-debugger>>", self.toggle_debugger) 787 text.bind("<<open-python-shell>>", self.flist.open_shell) 788 text.bind("<<toggle-jit-stack-viewer>>", self.toggle_jit_stack_viewer) 789 if use_subprocess: 790 text.bind("<<view-restart>>", self.view_restart_mark) 791 text.bind("<<restart-shell>>", self.restart_shell) 792 # 793 self.save_stdout = sys.stdout 794 self.save_stderr = sys.stderr 795 self.save_stdin = sys.stdin 796 import IOBinding 797 self.stdout = PseudoFile(self, "stdout", IOBinding.encoding) 798 self.stderr = PseudoFile(self, "stderr", IOBinding.encoding) 799 self.console = PseudoFile(self, "console", IOBinding.encoding) 800 if not use_subprocess: 801 sys.stdout = self.stdout 802 sys.stderr = self.stderr 803 sys.stdin = self 804 # 805 self.history = self.History(self.text) 806 # 807 self.pollinterval = 50 # millisec 808 809 reading = False 810 executing = False 811 canceled = False 812 endoffile = False 813 closing = False 814 815 def toggle_debugger(self, event=None): 816 if self.executing: 817 tkMessageBox.showerror("Don't debug now", 818 "You can only toggle the debugger when idle", 819 master=self.text) 820 self.set_debugger_indicator() 821 return "break" 822 else: 823 db = self.interp.getdebugger() 824 if db: 825 self.close_debugger() 826 else: 827 self.open_debugger() 828 829 def set_debugger_indicator(self): 830 db = self.interp.getdebugger() 831 self.setvar("<<toggle-debugger>>", not not db) 832 833 def toggle_jit_stack_viewer(self, event=None): 834 pass # All we need is the variable 835 836 def close_debugger(self): 837 db = self.interp.getdebugger() 838 if db: 839 self.interp.setdebugger(None) 840 db.close() 841 if self.interp.rpcclt: 842 RemoteDebugger.close_remote_debugger(self.interp.rpcclt) 843 self.resetoutput() 844 self.console.write("[DEBUG OFF]\n") 845 sys.ps1 = ">>> " 846 self.showprompt() 847 self.set_debugger_indicator() 848 849 def open_debugger(self): 850 if self.interp.rpcclt: 851 dbg_gui = RemoteDebugger.start_remote_debugger(self.interp.rpcclt, 852 self) 853 else: 854 dbg_gui = Debugger.Debugger(self) 855 self.interp.setdebugger(dbg_gui) 856 dbg_gui.load_breakpoints() 857 sys.ps1 = "[DEBUG ON]\n>>> " 858 self.showprompt() 859 self.set_debugger_indicator() 860 861 def beginexecuting(self): 862 "Helper for ModifiedInterpreter" 863 self.resetoutput() 864 self.executing = 1 865 866 def endexecuting(self): 867 "Helper for ModifiedInterpreter" 868 self.executing = 0 869 self.canceled = 0 870 self.showprompt() 871 872 def close(self): 873 "Extend EditorWindow.close()" 874 if self.executing: 875 response = tkMessageBox.askokcancel( 876 "Kill?", 877 "The program is still running!\n Do you want to kill it?", 878 default="ok", 879 parent=self.text) 880 if response == False: 881 return "cancel" 882 self.closing = True 883 # Wait for poll_subprocess() rescheduling to stop 884 self.text.after(2 * self.pollinterval, self.close2) 885 886 def close2(self): 887 return EditorWindow.close(self) 888 889 def _close(self): 890 "Extend EditorWindow._close(), shut down debugger and execution server" 891 self.close_debugger() 892 if use_subprocess: 893 self.interp.kill_subprocess() 894 # Restore std streams 895 sys.stdout = self.save_stdout 896 sys.stderr = self.save_stderr 897 sys.stdin = self.save_stdin 898 # Break cycles 899 self.interp = None 900 self.console = None 901 self.flist.pyshell = None 902 self.history = None 903 EditorWindow._close(self) 904 905 def ispythonsource(self, filename): 906 "Override EditorWindow method: never remove the colorizer" 907 return True 908 909 def short_title(self): 910 return self.shell_title 911 912 COPYRIGHT = \ 913 'Type "copyright", "credits" or "license()" for more information.' 914 915 firewallmessage = """ 916 **************************************************************** 917 Personal firewall software may warn about the connection IDLE 918 makes to its subprocess using this computer's internal loopback 919 interface. This connection is not visible on any external 920 interface and no data is sent to or received from the Internet. 921 **************************************************************** 922 """ 923 924 def begin(self): 925 self.resetoutput() 926 if use_subprocess: 927 nosub = '' 928 client = self.interp.start_subprocess() 929 if not client: 930 self.close() 931 return False 932 else: 933 nosub = "==== No Subprocess ====" 934 self.write("Python %s on %s\n%s\n%s\nIDLE %s %s\n" % 935 (sys.version, sys.platform, self.COPYRIGHT, 936 self.firewallmessage, idlever.IDLE_VERSION, nosub)) 937 self.showprompt() 938 import Tkinter 939 Tkinter._default_root = None # 03Jan04 KBK What's this? 940 return True 941 942 def readline(self): 943 save = self.reading 944 try: 945 self.reading = 1 946 self.top.mainloop() 947 finally: 948 self.reading = save 949 line = self.text.get("iomark", "end-1c") 950 if isinstance(line, unicode): 951 import IOBinding 952 try: 953 line = line.encode(IOBinding.encoding) 954 except UnicodeError: 955 pass 956 self.resetoutput() 957 if self.canceled: 958 self.canceled = 0 959 raise KeyboardInterrupt 960 if self.endoffile: 961 self.endoffile = 0 962 return "" 963 return line 964 965 def isatty(self): 966 return True 967 968 def cancel_callback(self, event=None): 969 try: 970 if self.text.compare("sel.first", "!=", "sel.last"): 971 return # Active selection -- always use default binding 972 except: 973 pass 974 if not (self.executing or self.reading): 975 self.resetoutput() 976 self.interp.write("KeyboardInterrupt\n") 977 self.showprompt() 978 return "break" 979 self.endoffile = 0 980 self.canceled = 1 981 if self.reading: 982 self.top.quit() 983 elif (self.executing and self.interp.rpcclt): 984 if self.interp.getdebugger(): 985 self.interp.restart_subprocess() 986 else: 987 self.interp.interrupt_subprocess() 988 return "break" 989 990 def eof_callback(self, event): 991 if self.executing and not self.reading: 992 return # Let the default binding (delete next char) take over 993 if not (self.text.compare("iomark", "==", "insert") and 994 self.text.compare("insert", "==", "end-1c")): 995 return # Let the default binding (delete next char) take over 996 if not self.executing: 997 self.resetoutput() 998 self.close() 999 else: 1000 self.canceled = 0 1001 self.endoffile = 1 1002 self.top.quit() 1003 return "break" 1004 1005 def home_callback(self, event): 1006 if event.state != 0 and event.keysym == "Home": 1007 return # <Modifier-Home>; fall back to class binding 1008 if self.text.compare("iomark", "<=", "insert") and \ 1009 self.text.compare("insert linestart", "<=", "iomark"): 1010 self.text.mark_set("insert", "iomark") 1011 self.text.tag_remove("sel", "1.0", "end") 1012 self.text.see("insert") 1013 return "break" 1014 1015 def linefeed_callback(self, event): 1016 # Insert a linefeed without entering anything (still autoindented) 1017 if self.reading: 1018 self.text.insert("insert", "\n") 1019 self.text.see("insert") 1020 else: 1021 self.newline_and_indent_event(event) 1022 return "break" 1023 1024 def enter_callback(self, event): 1025 if self.executing and not self.reading: 1026 return # Let the default binding (insert '\n') take over 1027 # If some text is selected, recall the selection 1028 # (but only if this before the I/O mark) 1029 try: 1030 sel = self.text.get("sel.first", "sel.last") 1031 if sel: 1032 if self.text.compare("sel.last", "<=", "iomark"): 1033 self.recall(sel) 1034 return "break" 1035 except: 1036 pass 1037 # If we're strictly before the line containing iomark, recall 1038 # the current line, less a leading prompt, less leading or 1039 # trailing whitespace 1040 if self.text.compare("insert", "<", "iomark linestart"): 1041 # Check if there's a relevant stdin range -- if so, use it 1042 prev = self.text.tag_prevrange("stdin", "insert") 1043 if prev and self.text.compare("insert", "<", prev[1]): 1044 self.recall(self.text.get(prev[0], prev[1])) 1045 return "break" 1046 next = self.text.tag_nextrange("stdin", "insert") 1047 if next and self.text.compare("insert lineend", ">=", next[0]): 1048 self.recall(self.text.get(next[0], next[1])) 1049 return "break" 1050 # No stdin mark -- just get the current line, less any prompt 1051 line = self.text.get("insert linestart", "insert lineend") 1052 last_line_of_prompt = sys.ps1.split('\n')[-1] 1053 if line.startswith(last_line_of_prompt): 1054 line = line[len(last_line_of_prompt):] 1055 self.recall(line) 1056 return "break" 1057 # If we're between the beginning of the line and the iomark, i.e. 1058 # in the prompt area, move to the end of the prompt 1059 if self.text.compare("insert", "<", "iomark"): 1060 self.text.mark_set("insert", "iomark") 1061 # If we're in the current input and there's only whitespace 1062 # beyond the cursor, erase that whitespace first 1063 s = self.text.get("insert", "end-1c") 1064 if s and not s.strip(): 1065 self.text.delete("insert", "end-1c") 1066 # If we're in the current input before its last line, 1067 # insert a newline right at the insert point 1068 if self.text.compare("insert", "<", "end-1c linestart"): 1069 self.newline_and_indent_event(event) 1070 return "break" 1071 # We're in the last line; append a newline and submit it 1072 self.text.mark_set("insert", "end-1c") 1073 if self.reading: 1074 self.text.insert("insert", "\n") 1075 self.text.see("insert") 1076 else: 1077 self.newline_and_indent_event(event) 1078 self.text.tag_add("stdin", "iomark", "end-1c") 1079 self.text.update_idletasks() 1080 if self.reading: 1081 self.top.quit() # Break out of recursive mainloop() in raw_input() 1082 else: 1083 self.runit() 1084 return "break" 1085 1086 def recall(self, s): 1087 if self.history: 1088 self.history.recall(s) 1089 1090 def runit(self): 1091 line = self.text.get("iomark", "end-1c") 1092 # Strip off last newline and surrounding whitespace. 1093 # (To allow you to hit return twice to end a statement.) 1094 i = len(line) 1095 while i > 0 and line[i-1] in " \t": 1096 i = i-1 1097 if i > 0 and line[i-1] == "\n": 1098 i = i-1 1099 while i > 0 and line[i-1] in " \t": 1100 i = i-1 1101 line = line[:i] 1102 more = self.interp.runsource(line) 1103 1104 def open_stack_viewer(self, event=None): 1105 if self.interp.rpcclt: 1106 return self.interp.remote_stack_viewer() 1107 try: 1108 sys.last_traceback 1109 except: 1110 tkMessageBox.showerror("No stack trace", 1111 "There is no stack trace yet.\n" 1112 "(sys.last_traceback is not defined)", 1113 master=self.text) 1114 return 1115 from StackViewer import StackBrowser 1116 sv = StackBrowser(self.root, self.flist) 1117 1118 def view_restart_mark(self, event=None): 1119 self.text.see("iomark") 1120 self.text.see("restart") 1121 1122 def restart_shell(self, event=None): 1123 self.interp.restart_subprocess() 1124 1125 def showprompt(self): 1126 self.resetoutput() 1127 try: 1128 s = str(sys.ps1) 1129 except: 1130 s = "" 1131 self.console.write(s) 1132 self.text.mark_set("insert", "end-1c") 1133 self.set_line_and_column() 1134 self.io.reset_undo() 1135 1136 def resetoutput(self): 1137 source = self.text.get("iomark", "end-1c") 1138 if self.history: 1139 self.history.history_store(source) 1140 if self.text.get("end-2c") != "\n": 1141 self.text.insert("end-1c", "\n") 1142 self.text.mark_set("iomark", "end-1c") 1143 self.set_line_and_column() 1144 sys.stdout.softspace = 0 1145 1146 def write(self, s, tags=()): 1147 try: 1148 self.text.mark_gravity("iomark", "right") 1149 OutputWindow.write(self, s, tags, "iomark") 1150 self.text.mark_gravity("iomark", "left") 1151 except: 1152 pass 1153 if self.canceled: 1154 self.canceled = 0 1155 if not use_subprocess: 1156 raise KeyboardInterrupt 1157 1158class PseudoFile: 1159 1160 def __init__(self, shell, tags, encoding=None): 1161 self.shell = shell 1162 self.tags = tags 1163 self.softspace = 0 1164 self.encoding = encoding 1165 1166 def write(self, s): 1167 self.shell.write(s, self.tags) 1168 1169 def writelines(self, l): 1170 map(self.write, l) 1171 1172 def flush(self): 1173 pass 1174 1175 def isatty(self): 1176 return True 1177 1178 1179usage_msg = """\ 1180 1181USAGE: idle [-deins] [-t title] [file]* 1182 idle [-dns] [-t title] (-c cmd | -r file) [arg]* 1183 idle [-dns] [-t title] - [arg]* 1184 1185 -h print this help message and exit 1186 -n run IDLE without a subprocess (see Help/IDLE Help for details) 1187 1188The following options will override the IDLE 'settings' configuration: 1189 1190 -e open an edit window 1191 -i open a shell window 1192 1193The following options imply -i and will open a shell: 1194 1195 -c cmd run the command in a shell, or 1196 -r file run script from file 1197 1198 -d enable the debugger 1199 -s run $IDLESTARTUP or $PYTHONSTARTUP before anything else 1200 -t title set title of shell window 1201 1202A default edit window will be bypassed when -c, -r, or - are used. 1203 1204[arg]* are passed to the command (-c) or script (-r) in sys.argv[1:]. 1205 1206Examples: 1207 1208idle 1209 Open an edit window or shell depending on IDLE's configuration. 1210 1211idle foo.py foobar.py 1212 Edit the files, also open a shell if configured to start with shell. 1213 1214idle -est "Baz" foo.py 1215 Run $IDLESTARTUP or $PYTHONSTARTUP, edit foo.py, and open a shell 1216 window with the title "Baz". 1217 1218idle -c "import sys; print sys.argv" "foo" 1219 Open a shell window and run the command, passing "-c" in sys.argv[0] 1220 and "foo" in sys.argv[1]. 1221 1222idle -d -s -r foo.py "Hello World" 1223 Open a shell window, run a startup script, enable the debugger, and 1224 run foo.py, passing "foo.py" in sys.argv[0] and "Hello World" in 1225 sys.argv[1]. 1226 1227echo "import sys; print sys.argv" | idle - "foobar" 1228 Open a shell window, run the script piped in, passing '' in sys.argv[0] 1229 and "foobar" in sys.argv[1]. 1230""" 1231 1232def main(): 1233 global flist, root, use_subprocess 1234 1235 use_subprocess = True 1236 enable_shell = False 1237 enable_edit = False 1238 debug = False 1239 cmd = None 1240 script = None 1241 startup = False 1242 try: 1243 sys.ps1 1244 except AttributeError: 1245 sys.ps1 = '>>> ' 1246 try: 1247 opts, args = getopt.getopt(sys.argv[1:], "c:deihnr:st:") 1248 except getopt.error, msg: 1249 sys.stderr.write("Error: %s\n" % str(msg)) 1250 sys.stderr.write(usage_msg) 1251 sys.exit(2) 1252 for o, a in opts: 1253 if o == '-c': 1254 cmd = a 1255 enable_shell = True 1256 if o == '-d': 1257 debug = True 1258 enable_shell = True 1259 if o == '-e': 1260 enable_edit = True 1261 if o == '-h': 1262 sys.stdout.write(usage_msg) 1263 sys.exit() 1264 if o == '-i': 1265 enable_shell = True 1266 if o == '-n': 1267 use_subprocess = False 1268 if o == '-r': 1269 script = a 1270 if os.path.isfile(script): 1271 pass 1272 else: 1273 print "No script file: ", script 1274 sys.exit() 1275 enable_shell = True 1276 if o == '-s': 1277 startup = True 1278 enable_shell = True 1279 if o == '-t': 1280 PyShell.shell_title = a 1281 enable_shell = True 1282 if args and args[0] == '-': 1283 cmd = sys.stdin.read() 1284 enable_shell = True 1285 # process sys.argv and sys.path: 1286 for i in range(len(sys.path)): 1287 sys.path[i] = os.path.abspath(sys.path[i]) 1288 if args and args[0] == '-': 1289 sys.argv = [''] + args[1:] 1290 elif cmd: 1291 sys.argv = ['-c'] + args 1292 elif script: 1293 sys.argv = [script] + args 1294 elif args: 1295 enable_edit = True 1296 pathx = [] 1297 for filename in args: 1298 pathx.append(os.path.dirname(filename)) 1299 for dir in pathx: 1300 dir = os.path.abspath(dir) 1301 if not dir in sys.path: 1302 sys.path.insert(0, dir) 1303 else: 1304 dir = os.getcwd() 1305 if not dir in sys.path: 1306 sys.path.insert(0, dir) 1307 # check the IDLE settings configuration (but command line overrides) 1308 edit_start = idleConf.GetOption('main', 'General', 1309 'editor-on-startup', type='bool') 1310 enable_edit = enable_edit or edit_start 1311 enable_shell = enable_shell or not edit_start 1312 # start editor and/or shell windows: 1313 root = Tk(className="Idle") 1314 fixwordbreaks(root) 1315 root.withdraw() 1316 flist = PyShellFileList(root) 1317 if enable_edit: 1318 if not (cmd or script): 1319 for filename in args: 1320 flist.open(filename) 1321 if not args: 1322 flist.new() 1323 if enable_shell: 1324 if not flist.open_shell(): 1325 return # couldn't open shell 1326 shell = flist.pyshell 1327 # handle remaining options: 1328 if debug: 1329 shell.open_debugger() 1330 if startup: 1331 filename = os.environ.get("IDLESTARTUP") or \ 1332 os.environ.get("PYTHONSTARTUP") 1333 if filename and os.path.isfile(filename): 1334 shell.interp.execfile(filename) 1335 if shell and cmd or script: 1336 shell.interp.runcommand("""if 1: 1337 import sys as _sys 1338 _sys.argv = %r 1339 del _sys 1340 \n""" % (sys.argv,)) 1341 if cmd: 1342 shell.interp.execsource(cmd) 1343 elif script: 1344 shell.interp.prepend_syspath(script) 1345 shell.interp.execfile(script) 1346 root.mainloop() 1347 root.destroy() 1348 1349if __name__ == "__main__": 1350 sys.modules['PyShell'] = sys.modules['__main__'] 1351 main() 1352