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