PyShell.py revision f137e1df2c02f5782c5b0f18a2b3b649f255f63a
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 # Maybe IDLE is installed and is being accessed via sys.path, 355 # or maybe it's not installed and the idle.py script is being 356 # run from the IDLE source directory. 357 del_exitf = idleConf.GetOption('main', 'General', 'delete-exitfunc', 358 default=False, type='bool') 359 if __name__ == 'idlelib.PyShell': 360 command = "__import__('idlelib.run').run.main(%r)" % (del_exitf,) 361 else: 362 command = "__import__('run').main(%r)" % (del_exitf,) 363 if sys.platform[:3] == 'win' and ' ' in sys.executable: 364 # handle embedded space in path by quoting the argument 365 decorated_exec = '"%s"' % sys.executable 366 else: 367 decorated_exec = sys.executable 368 return [decorated_exec] + w + ["-c", command, str(self.port)] 369 370 def start_subprocess(self): 371 # spawning first avoids passing a listening socket to the subprocess 372 self.spawn_subprocess() 373 #time.sleep(20) # test to simulate GUI not accepting connection 374 addr = (LOCALHOST, self.port) 375 # Idle starts listening for connection on localhost 376 for i in range(3): 377 time.sleep(i) 378 try: 379 self.rpcclt = MyRPCClient(addr) 380 break 381 except socket.error, err: 382 pass 383 else: 384 self.display_port_binding_error() 385 return None 386 # Accept the connection from the Python execution server 387 self.rpcclt.listening_sock.settimeout(10) 388 try: 389 self.rpcclt.accept() 390 except socket.timeout, err: 391 self.display_no_subprocess_error() 392 return None 393 self.rpcclt.register("stdin", self.tkconsole) 394 self.rpcclt.register("stdout", self.tkconsole.stdout) 395 self.rpcclt.register("stderr", self.tkconsole.stderr) 396 self.rpcclt.register("flist", self.tkconsole.flist) 397 self.rpcclt.register("linecache", linecache) 398 self.rpcclt.register("interp", self) 399 self.transfer_path() 400 self.poll_subprocess() 401 return self.rpcclt 402 403 def restart_subprocess(self): 404 if self.restarting: 405 return self.rpcclt 406 self.restarting = True 407 # close only the subprocess debugger 408 debug = self.getdebugger() 409 if debug: 410 try: 411 # Only close subprocess debugger, don't unregister gui_adap! 412 RemoteDebugger.close_subprocess_debugger(self.rpcclt) 413 except: 414 pass 415 # Kill subprocess, spawn a new one, accept connection. 416 self.rpcclt.close() 417 self.unix_terminate() 418 console = self.tkconsole 419 was_executing = console.executing 420 console.executing = False 421 self.spawn_subprocess() 422 try: 423 self.rpcclt.accept() 424 except socket.timeout, err: 425 self.display_no_subprocess_error() 426 return None 427 self.transfer_path() 428 # annotate restart in shell window and mark it 429 console.text.delete("iomark", "end-1c") 430 if was_executing: 431 console.write('\n') 432 console.showprompt() 433 halfbar = ((int(console.width) - 16) // 2) * '=' 434 console.write(halfbar + ' RESTART ' + halfbar) 435 console.text.mark_set("restart", "end-1c") 436 console.text.mark_gravity("restart", "left") 437 console.showprompt() 438 # restart subprocess debugger 439 if debug: 440 # Restarted debugger connects to current instance of debug GUI 441 gui = RemoteDebugger.restart_subprocess_debugger(self.rpcclt) 442 # reload remote debugger breakpoints for all PyShellEditWindows 443 debug.load_breakpoints() 444 self.restarting = False 445 return self.rpcclt 446 447 def __request_interrupt(self): 448 self.rpcclt.remotecall("exec", "interrupt_the_server", (), {}) 449 450 def interrupt_subprocess(self): 451 threading.Thread(target=self.__request_interrupt).start() 452 453 def kill_subprocess(self): 454 try: 455 self.rpcclt.close() 456 except AttributeError: # no socket 457 pass 458 self.unix_terminate() 459 self.tkconsole.executing = False 460 self.rpcclt = None 461 462 def unix_terminate(self): 463 "UNIX: make sure subprocess is terminated and collect status" 464 if hasattr(os, 'kill'): 465 try: 466 os.kill(self.rpcpid, SIGTERM) 467 except OSError: 468 # process already terminated: 469 return 470 else: 471 try: 472 os.waitpid(self.rpcpid, 0) 473 except OSError: 474 return 475 476 def transfer_path(self): 477 self.runcommand("""if 1: 478 import sys as _sys 479 _sys.path = %r 480 del _sys 481 \n""" % (sys.path,)) 482 483 active_seq = None 484 485 def poll_subprocess(self): 486 clt = self.rpcclt 487 if clt is None: 488 return 489 try: 490 response = clt.pollresponse(self.active_seq, wait=0.05) 491 except (EOFError, IOError, KeyboardInterrupt): 492 # lost connection or subprocess terminated itself, restart 493 # [the KBI is from rpc.SocketIO.handle_EOF()] 494 if self.tkconsole.closing: 495 return 496 response = None 497 self.restart_subprocess() 498 if response: 499 self.tkconsole.resetoutput() 500 self.active_seq = None 501 how, what = response 502 console = self.tkconsole.console 503 if how == "OK": 504 if what is not None: 505 print >>console, repr(what) 506 elif how == "EXCEPTION": 507 if self.tkconsole.getvar("<<toggle-jit-stack-viewer>>"): 508 self.remote_stack_viewer() 509 elif how == "ERROR": 510 errmsg = "PyShell.ModifiedInterpreter: Subprocess ERROR:\n" 511 print >>sys.__stderr__, errmsg, what 512 print >>console, errmsg, what 513 # we received a response to the currently active seq number: 514 try: 515 self.tkconsole.endexecuting() 516 except AttributeError: # shell may have closed 517 pass 518 # Reschedule myself 519 if not self.tkconsole.closing: 520 self.tkconsole.text.after(self.tkconsole.pollinterval, 521 self.poll_subprocess) 522 523 debugger = None 524 525 def setdebugger(self, debugger): 526 self.debugger = debugger 527 528 def getdebugger(self): 529 return self.debugger 530 531 def open_remote_stack_viewer(self): 532 """Initiate the remote stack viewer from a separate thread. 533 534 This method is called from the subprocess, and by returning from this 535 method we allow the subprocess to unblock. After a bit the shell 536 requests the subprocess to open the remote stack viewer which returns a 537 static object looking at the last exceptiopn. It is queried through 538 the RPC mechanism. 539 540 """ 541 self.tkconsole.text.after(300, self.remote_stack_viewer) 542 return 543 544 def remote_stack_viewer(self): 545 import RemoteObjectBrowser 546 oid = self.rpcclt.remotequeue("exec", "stackviewer", ("flist",), {}) 547 if oid is None: 548 self.tkconsole.root.bell() 549 return 550 item = RemoteObjectBrowser.StubObjectTreeItem(self.rpcclt, oid) 551 from TreeWidget import ScrolledCanvas, TreeNode 552 top = Toplevel(self.tkconsole.root) 553 theme = idleConf.GetOption('main','Theme','name') 554 background = idleConf.GetHighlight(theme, 'normal')['background'] 555 sc = ScrolledCanvas(top, bg=background, highlightthickness=0) 556 sc.frame.pack(expand=1, fill="both") 557 node = TreeNode(sc.canvas, None, item) 558 node.expand() 559 # XXX Should GC the remote tree when closing the window 560 561 gid = 0 562 563 def execsource(self, source): 564 "Like runsource() but assumes complete exec source" 565 filename = self.stuffsource(source) 566 self.execfile(filename, source) 567 568 def execfile(self, filename, source=None): 569 "Execute an existing file" 570 if source is None: 571 source = open(filename, "r").read() 572 try: 573 code = compile(source, filename, "exec") 574 except (OverflowError, SyntaxError): 575 self.tkconsole.resetoutput() 576 tkerr = self.tkconsole.stderr 577 print>>tkerr, '*** Error in script or command!\n' 578 print>>tkerr, 'Traceback (most recent call last):' 579 InteractiveInterpreter.showsyntaxerror(self, filename) 580 self.tkconsole.showprompt() 581 else: 582 self.runcode(code) 583 584 def runsource(self, source): 585 "Extend base class method: Stuff the source in the line cache first" 586 filename = self.stuffsource(source) 587 self.more = 0 588 self.save_warnings_filters = warnings.filters[:] 589 warnings.filterwarnings(action="error", category=SyntaxWarning) 590 if isinstance(source, types.UnicodeType): 591 import IOBinding 592 try: 593 source = source.encode(IOBinding.encoding) 594 except UnicodeError: 595 self.tkconsole.resetoutput() 596 self.write("Unsupported characters in input\n") 597 return 598 try: 599 # InteractiveInterpreter.runsource() calls its runcode() method, 600 # which is overridden (see below) 601 return InteractiveInterpreter.runsource(self, source, filename) 602 finally: 603 if self.save_warnings_filters is not None: 604 warnings.filters[:] = self.save_warnings_filters 605 self.save_warnings_filters = None 606 607 def stuffsource(self, source): 608 "Stuff source in the filename cache" 609 filename = "<pyshell#%d>" % self.gid 610 self.gid = self.gid + 1 611 lines = source.split("\n") 612 linecache.cache[filename] = len(source)+1, 0, lines, filename 613 return filename 614 615 def prepend_syspath(self, filename): 616 "Prepend sys.path with file's directory if not already included" 617 self.runcommand("""if 1: 618 _filename = %r 619 import sys as _sys 620 from os.path import dirname as _dirname 621 _dir = _dirname(_filename) 622 if not _dir in _sys.path: 623 _sys.path.insert(0, _dir) 624 del _filename, _sys, _dirname, _dir 625 \n""" % (filename,)) 626 627 def showsyntaxerror(self, filename=None): 628 """Extend base class method: Add Colorizing 629 630 Color the offending position instead of printing it and pointing at it 631 with a caret. 632 633 """ 634 text = self.tkconsole.text 635 stuff = self.unpackerror() 636 if stuff: 637 msg, lineno, offset, line = stuff 638 if lineno == 1: 639 pos = "iomark + %d chars" % (offset-1) 640 else: 641 pos = "iomark linestart + %d lines + %d chars" % \ 642 (lineno-1, offset-1) 643 text.tag_add("ERROR", pos) 644 text.see(pos) 645 char = text.get(pos) 646 if char and char in IDENTCHARS: 647 text.tag_add("ERROR", pos + " wordstart", pos) 648 self.tkconsole.resetoutput() 649 self.write("SyntaxError: %s\n" % str(msg)) 650 else: 651 self.tkconsole.resetoutput() 652 InteractiveInterpreter.showsyntaxerror(self, filename) 653 self.tkconsole.showprompt() 654 655 def unpackerror(self): 656 type, value, tb = sys.exc_info() 657 ok = type is SyntaxError 658 if ok: 659 try: 660 msg, (dummy_filename, lineno, offset, line) = value 661 if not offset: 662 offset = 0 663 except: 664 ok = 0 665 if ok: 666 return msg, lineno, offset, line 667 else: 668 return None 669 670 def showtraceback(self): 671 "Extend base class method to reset output properly" 672 self.tkconsole.resetoutput() 673 self.checklinecache() 674 InteractiveInterpreter.showtraceback(self) 675 if self.tkconsole.getvar("<<toggle-jit-stack-viewer>>"): 676 self.tkconsole.open_stack_viewer() 677 678 def checklinecache(self): 679 c = linecache.cache 680 for key in c.keys(): 681 if key[:1] + key[-1:] != "<>": 682 del c[key] 683 684 def runcommand(self, code): 685 "Run the code without invoking the debugger" 686 # The code better not raise an exception! 687 if self.tkconsole.executing: 688 self.display_executing_dialog() 689 return 0 690 if self.rpcclt: 691 self.rpcclt.remotequeue("exec", "runcode", (code,), {}) 692 else: 693 exec code in self.locals 694 return 1 695 696 def runcode(self, code): 697 "Override base class method" 698 if self.tkconsole.executing: 699 self.interp.restart_subprocess() 700 self.checklinecache() 701 if self.save_warnings_filters is not None: 702 warnings.filters[:] = self.save_warnings_filters 703 self.save_warnings_filters = None 704 debugger = self.debugger 705 try: 706 self.tkconsole.beginexecuting() 707 try: 708 if not debugger and self.rpcclt is not None: 709 self.active_seq = self.rpcclt.asyncqueue("exec", "runcode", 710 (code,), {}) 711 elif debugger: 712 debugger.run(code, self.locals) 713 else: 714 exec code in self.locals 715 except SystemExit: 716 if not self.tkconsole.closing: 717 if tkMessageBox.askyesno( 718 "Exit?", 719 "Do you want to exit altogether?", 720 default="yes", 721 master=self.tkconsole.text): 722 raise 723 else: 724 self.showtraceback() 725 else: 726 raise 727 except: 728 if use_subprocess: 729 print >> self.tkconsole.stderr, \ 730 "IDLE internal error in runcode()" 731 self.showtraceback() 732 if use_subprocess: 733 self.tkconsole.endexecuting() 734 finally: 735 if not use_subprocess: 736 try: 737 self.tkconsole.endexecuting() 738 except AttributeError: # shell may have closed 739 pass 740 741 def write(self, s): 742 "Override base class method" 743 self.tkconsole.stderr.write(s) 744 745 def display_port_binding_error(self): 746 tkMessageBox.showerror( 747 "Port Binding Error", 748 "IDLE can't bind TCP/IP port 8833, which is necessary to " 749 "communicate with its Python execution server. Either " 750 "no networking is installed on this computer or another " 751 "process (another IDLE?) is using the port. Run IDLE with the -n " 752 "command line switch to start without a subprocess and refer to " 753 "Help/IDLE Help 'Running without a subprocess' for further " 754 "details.", 755 master=self.tkconsole.text) 756 757 def display_no_subprocess_error(self): 758 tkMessageBox.showerror( 759 "Subprocess Startup Error", 760 "IDLE's subprocess didn't make connection. Either IDLE can't " 761 "start a subprocess or personal firewall software is blocking " 762 "the connection.", 763 master=self.tkconsole.text) 764 765 def display_executing_dialog(self): 766 tkMessageBox.showerror( 767 "Already executing", 768 "The Python Shell window is already executing a command; " 769 "please wait until it is finished.", 770 master=self.tkconsole.text) 771 772 773class PyShell(OutputWindow): 774 775 shell_title = "Python Shell" 776 777 # Override classes 778 ColorDelegator = ModifiedColorDelegator 779 UndoDelegator = ModifiedUndoDelegator 780 781 # Override menus 782 menu_specs = [ 783 ("file", "_File"), 784 ("edit", "_Edit"), 785 ("debug", "_Debug"), 786 ("options", "_Options"), 787 ("windows", "_Windows"), 788 ("help", "_Help"), 789 ] 790 791 if macosxSupport.runningAsOSXApp(): 792 del menu_specs[-3] 793 menu_specs[-2] = ("windows", "_Window") 794 795 796 # New classes 797 from IdleHistory import History 798 799 def __init__(self, flist=None): 800 if use_subprocess: 801 ms = self.menu_specs 802 if ms[2][0] != "shell": 803 ms.insert(2, ("shell", "_Shell")) 804 self.interp = ModifiedInterpreter(self) 805 if flist is None: 806 root = Tk() 807 fixwordbreaks(root) 808 root.withdraw() 809 flist = PyShellFileList(root) 810 # 811 OutputWindow.__init__(self, flist, None, None) 812 # 813## self.config(usetabs=1, indentwidth=8, context_use_ps1=1) 814 self.usetabs = True 815 # indentwidth must be 8 when using tabs. See note in EditorWindow: 816 self.indentwidth = 8 817 self.context_use_ps1 = True 818 # 819 text = self.text 820 text.configure(wrap="char") 821 text.bind("<<newline-and-indent>>", self.enter_callback) 822 text.bind("<<plain-newline-and-indent>>", self.linefeed_callback) 823 text.bind("<<interrupt-execution>>", self.cancel_callback) 824 text.bind("<<beginning-of-line>>", self.home_callback) 825 text.bind("<<end-of-file>>", self.eof_callback) 826 text.bind("<<open-stack-viewer>>", self.open_stack_viewer) 827 text.bind("<<toggle-debugger>>", self.toggle_debugger) 828 text.bind("<<toggle-jit-stack-viewer>>", self.toggle_jit_stack_viewer) 829 if use_subprocess: 830 text.bind("<<view-restart>>", self.view_restart_mark) 831 text.bind("<<restart-shell>>", self.restart_shell) 832 # 833 self.save_stdout = sys.stdout 834 self.save_stderr = sys.stderr 835 self.save_stdin = sys.stdin 836 import IOBinding 837 self.stdout = PseudoFile(self, "stdout", IOBinding.encoding) 838 self.stderr = PseudoFile(self, "stderr", IOBinding.encoding) 839 self.console = PseudoFile(self, "console", IOBinding.encoding) 840 if not use_subprocess: 841 sys.stdout = self.stdout 842 sys.stderr = self.stderr 843 sys.stdin = self 844 # 845 self.history = self.History(self.text) 846 # 847 self.pollinterval = 50 # millisec 848 849 def get_standard_extension_names(self): 850 return idleConf.GetExtensions(shell_only=True) 851 852 reading = False 853 executing = False 854 canceled = False 855 endoffile = False 856 closing = False 857 858 def set_warning_stream(self, stream): 859 global warning_stream 860 warning_stream = stream 861 862 def get_warning_stream(self): 863 return warning_stream 864 865 def toggle_debugger(self, event=None): 866 if self.executing: 867 tkMessageBox.showerror("Don't debug now", 868 "You can only toggle the debugger when idle", 869 master=self.text) 870 self.set_debugger_indicator() 871 return "break" 872 else: 873 db = self.interp.getdebugger() 874 if db: 875 self.close_debugger() 876 else: 877 self.open_debugger() 878 879 def set_debugger_indicator(self): 880 db = self.interp.getdebugger() 881 self.setvar("<<toggle-debugger>>", not not db) 882 883 def toggle_jit_stack_viewer(self, event=None): 884 pass # All we need is the variable 885 886 def close_debugger(self): 887 db = self.interp.getdebugger() 888 if db: 889 self.interp.setdebugger(None) 890 db.close() 891 if self.interp.rpcclt: 892 RemoteDebugger.close_remote_debugger(self.interp.rpcclt) 893 self.resetoutput() 894 self.console.write("[DEBUG OFF]\n") 895 sys.ps1 = ">>> " 896 self.showprompt() 897 self.set_debugger_indicator() 898 899 def open_debugger(self): 900 if self.interp.rpcclt: 901 dbg_gui = RemoteDebugger.start_remote_debugger(self.interp.rpcclt, 902 self) 903 else: 904 dbg_gui = Debugger.Debugger(self) 905 self.interp.setdebugger(dbg_gui) 906 dbg_gui.load_breakpoints() 907 sys.ps1 = "[DEBUG ON]\n>>> " 908 self.showprompt() 909 self.set_debugger_indicator() 910 911 def beginexecuting(self): 912 "Helper for ModifiedInterpreter" 913 self.resetoutput() 914 self.executing = 1 915 916 def endexecuting(self): 917 "Helper for ModifiedInterpreter" 918 self.executing = 0 919 self.canceled = 0 920 self.showprompt() 921 922 def close(self): 923 "Extend EditorWindow.close()" 924 if self.executing: 925 response = tkMessageBox.askokcancel( 926 "Kill?", 927 "The program is still running!\n Do you want to kill it?", 928 default="ok", 929 parent=self.text) 930 if response == False: 931 return "cancel" 932 if self.reading: 933 self.top.quit() 934 self.canceled = True 935 self.closing = True 936 # Wait for poll_subprocess() rescheduling to stop 937 self.text.after(2 * self.pollinterval, self.close2) 938 939 def close2(self): 940 return EditorWindow.close(self) 941 942 def _close(self): 943 "Extend EditorWindow._close(), shut down debugger and execution server" 944 self.close_debugger() 945 if use_subprocess: 946 self.interp.kill_subprocess() 947 # Restore std streams 948 sys.stdout = self.save_stdout 949 sys.stderr = self.save_stderr 950 sys.stdin = self.save_stdin 951 # Break cycles 952 self.interp = None 953 self.console = None 954 self.flist.pyshell = None 955 self.history = None 956 EditorWindow._close(self) 957 958 def ispythonsource(self, filename): 959 "Override EditorWindow method: never remove the colorizer" 960 return True 961 962 def short_title(self): 963 return self.shell_title 964 965 COPYRIGHT = \ 966 'Type "copyright", "credits" or "license()" for more information.' 967 968 firewallmessage = """ 969 **************************************************************** 970 Personal firewall software may warn about the connection IDLE 971 makes to its subprocess using this computer's internal loopback 972 interface. This connection is not visible on any external 973 interface and no data is sent to or received from the Internet. 974 **************************************************************** 975 """ 976 977 def begin(self): 978 self.resetoutput() 979 if use_subprocess: 980 nosub = '' 981 client = self.interp.start_subprocess() 982 if not client: 983 self.close() 984 return False 985 else: 986 nosub = "==== No Subprocess ====" 987 self.write("Python %s on %s\n%s\n%s\nIDLE %s %s\n" % 988 (sys.version, sys.platform, self.COPYRIGHT, 989 self.firewallmessage, idlever.IDLE_VERSION, nosub)) 990 self.showprompt() 991 import Tkinter 992 Tkinter._default_root = None # 03Jan04 KBK What's this? 993 return True 994 995 def readline(self): 996 save = self.reading 997 try: 998 self.reading = 1 999 self.top.mainloop() # nested mainloop() 1000 finally: 1001 self.reading = save 1002 line = self.text.get("iomark", "end-1c") 1003 if len(line) == 0: # may be EOF if we quit our mainloop with Ctrl-C 1004 line = "\n" 1005 if isinstance(line, unicode): 1006 import IOBinding 1007 try: 1008 line = line.encode(IOBinding.encoding) 1009 except UnicodeError: 1010 pass 1011 self.resetoutput() 1012 if self.canceled: 1013 self.canceled = 0 1014 if not use_subprocess: 1015 raise KeyboardInterrupt 1016 if self.endoffile: 1017 self.endoffile = 0 1018 line = "" 1019 return line 1020 1021 def isatty(self): 1022 return True 1023 1024 def cancel_callback(self, event=None): 1025 try: 1026 if self.text.compare("sel.first", "!=", "sel.last"): 1027 return # Active selection -- always use default binding 1028 except: 1029 pass 1030 if not (self.executing or self.reading): 1031 self.resetoutput() 1032 self.interp.write("KeyboardInterrupt\n") 1033 self.showprompt() 1034 return "break" 1035 self.endoffile = 0 1036 self.canceled = 1 1037 if (self.executing and self.interp.rpcclt): 1038 if self.interp.getdebugger(): 1039 self.interp.restart_subprocess() 1040 else: 1041 self.interp.interrupt_subprocess() 1042 if self.reading: 1043 self.top.quit() # exit the nested mainloop() in readline() 1044 return "break" 1045 1046 def eof_callback(self, event): 1047 if self.executing and not self.reading: 1048 return # Let the default binding (delete next char) take over 1049 if not (self.text.compare("iomark", "==", "insert") and 1050 self.text.compare("insert", "==", "end-1c")): 1051 return # Let the default binding (delete next char) take over 1052 if not self.executing: 1053 self.resetoutput() 1054 self.close() 1055 else: 1056 self.canceled = 0 1057 self.endoffile = 1 1058 self.top.quit() 1059 return "break" 1060 1061 def home_callback(self, event): 1062 if event.state != 0 and event.keysym == "Home": 1063 return # <Modifier-Home>; fall back to class binding 1064 if self.text.compare("iomark", "<=", "insert") and \ 1065 self.text.compare("insert linestart", "<=", "iomark"): 1066 self.text.mark_set("insert", "iomark") 1067 self.text.tag_remove("sel", "1.0", "end") 1068 self.text.see("insert") 1069 return "break" 1070 1071 def linefeed_callback(self, event): 1072 # Insert a linefeed without entering anything (still autoindented) 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 return "break" 1079 1080 def enter_callback(self, event): 1081 if self.executing and not self.reading: 1082 return # Let the default binding (insert '\n') take over 1083 # If some text is selected, recall the selection 1084 # (but only if this before the I/O mark) 1085 try: 1086 sel = self.text.get("sel.first", "sel.last") 1087 if sel: 1088 if self.text.compare("sel.last", "<=", "iomark"): 1089 self.recall(sel, event) 1090 return "break" 1091 except: 1092 pass 1093 # If we're strictly before the line containing iomark, recall 1094 # the current line, less a leading prompt, less leading or 1095 # trailing whitespace 1096 if self.text.compare("insert", "<", "iomark linestart"): 1097 # Check if there's a relevant stdin range -- if so, use it 1098 prev = self.text.tag_prevrange("stdin", "insert") 1099 if prev and self.text.compare("insert", "<", prev[1]): 1100 self.recall(self.text.get(prev[0], prev[1]), event) 1101 return "break" 1102 next = self.text.tag_nextrange("stdin", "insert") 1103 if next and self.text.compare("insert lineend", ">=", next[0]): 1104 self.recall(self.text.get(next[0], next[1]), event) 1105 return "break" 1106 # No stdin mark -- just get the current line, less any prompt 1107 indices = self.text.tag_nextrange("console", "insert linestart") 1108 if indices and \ 1109 self.text.compare(indices[0], "<=", "insert linestart"): 1110 self.recall(self.text.get(indices[1], "insert lineend"), event) 1111 else: 1112 self.recall(self.text.get("insert linestart", "insert lineend"), event) 1113 return "break" 1114 # If we're between the beginning of the line and the iomark, i.e. 1115 # in the prompt area, move to the end of the prompt 1116 if self.text.compare("insert", "<", "iomark"): 1117 self.text.mark_set("insert", "iomark") 1118 # If we're in the current input and there's only whitespace 1119 # beyond the cursor, erase that whitespace first 1120 s = self.text.get("insert", "end-1c") 1121 if s and not s.strip(): 1122 self.text.delete("insert", "end-1c") 1123 # If we're in the current input before its last line, 1124 # insert a newline right at the insert point 1125 if self.text.compare("insert", "<", "end-1c linestart"): 1126 self.newline_and_indent_event(event) 1127 return "break" 1128 # We're in the last line; append a newline and submit it 1129 self.text.mark_set("insert", "end-1c") 1130 if self.reading: 1131 self.text.insert("insert", "\n") 1132 self.text.see("insert") 1133 else: 1134 self.newline_and_indent_event(event) 1135 self.text.tag_add("stdin", "iomark", "end-1c") 1136 self.text.update_idletasks() 1137 if self.reading: 1138 self.top.quit() # Break out of recursive mainloop() in raw_input() 1139 else: 1140 self.runit() 1141 return "break" 1142 1143 def recall(self, s, event): 1144 # remove leading and trailing empty or whitespace lines 1145 s = re.sub(r'^\s*\n', '' , s) 1146 s = re.sub(r'\n\s*$', '', s) 1147 lines = s.split('\n') 1148 self.text.undo_block_start() 1149 try: 1150 self.text.tag_remove("sel", "1.0", "end") 1151 self.text.mark_set("insert", "end-1c") 1152 prefix = self.text.get("insert linestart", "insert") 1153 if prefix.rstrip().endswith(':'): 1154 self.newline_and_indent_event(event) 1155 prefix = self.text.get("insert linestart", "insert") 1156 self.text.insert("insert", lines[0].strip()) 1157 if len(lines) > 1: 1158 orig_base_indent = re.search(r'^([ \t]*)', lines[0]).group(0) 1159 new_base_indent = re.search(r'^([ \t]*)', prefix).group(0) 1160 for line in lines[1:]: 1161 if line.startswith(orig_base_indent): 1162 # replace orig base indentation with new indentation 1163 line = new_base_indent + line[len(orig_base_indent):] 1164 self.text.insert('insert', '\n'+line.rstrip()) 1165 finally: 1166 self.text.see("insert") 1167 self.text.undo_block_stop() 1168 1169 def runit(self): 1170 line = self.text.get("iomark", "end-1c") 1171 # Strip off last newline and surrounding whitespace. 1172 # (To allow you to hit return twice to end a statement.) 1173 i = len(line) 1174 while i > 0 and line[i-1] in " \t": 1175 i = i-1 1176 if i > 0 and line[i-1] == "\n": 1177 i = i-1 1178 while i > 0 and line[i-1] in " \t": 1179 i = i-1 1180 line = line[:i] 1181 more = self.interp.runsource(line) 1182 1183 def open_stack_viewer(self, event=None): 1184 if self.interp.rpcclt: 1185 return self.interp.remote_stack_viewer() 1186 try: 1187 sys.last_traceback 1188 except: 1189 tkMessageBox.showerror("No stack trace", 1190 "There is no stack trace yet.\n" 1191 "(sys.last_traceback is not defined)", 1192 master=self.text) 1193 return 1194 from StackViewer import StackBrowser 1195 sv = StackBrowser(self.root, self.flist) 1196 1197 def view_restart_mark(self, event=None): 1198 self.text.see("iomark") 1199 self.text.see("restart") 1200 1201 def restart_shell(self, event=None): 1202 self.interp.restart_subprocess() 1203 1204 def showprompt(self): 1205 self.resetoutput() 1206 try: 1207 s = str(sys.ps1) 1208 except: 1209 s = "" 1210 self.console.write(s) 1211 self.text.mark_set("insert", "end-1c") 1212 self.set_line_and_column() 1213 self.io.reset_undo() 1214 1215 def resetoutput(self): 1216 source = self.text.get("iomark", "end-1c") 1217 if self.history: 1218 self.history.history_store(source) 1219 if self.text.get("end-2c") != "\n": 1220 self.text.insert("end-1c", "\n") 1221 self.text.mark_set("iomark", "end-1c") 1222 self.set_line_and_column() 1223 sys.stdout.softspace = 0 1224 1225 def write(self, s, tags=()): 1226 try: 1227 self.text.mark_gravity("iomark", "right") 1228 OutputWindow.write(self, s, tags, "iomark") 1229 self.text.mark_gravity("iomark", "left") 1230 except: 1231 pass 1232 if self.canceled: 1233 self.canceled = 0 1234 if not use_subprocess: 1235 raise KeyboardInterrupt 1236 1237class PseudoFile(object): 1238 1239 def __init__(self, shell, tags, encoding=None): 1240 self.shell = shell 1241 self.tags = tags 1242 self.softspace = 0 1243 self.encoding = encoding 1244 1245 def write(self, s): 1246 self.shell.write(s, self.tags) 1247 1248 def writelines(self, l): 1249 map(self.write, l) 1250 1251 def flush(self): 1252 pass 1253 1254 def isatty(self): 1255 return True 1256 1257 1258usage_msg = """\ 1259 1260USAGE: idle [-deins] [-t title] [file]* 1261 idle [-dns] [-t title] (-c cmd | -r file) [arg]* 1262 idle [-dns] [-t title] - [arg]* 1263 1264 -h print this help message and exit 1265 -n run IDLE without a subprocess (see Help/IDLE Help for details) 1266 1267The following options will override the IDLE 'settings' configuration: 1268 1269 -e open an edit window 1270 -i open a shell window 1271 1272The following options imply -i and will open a shell: 1273 1274 -c cmd run the command in a shell, or 1275 -r file run script from file 1276 1277 -d enable the debugger 1278 -s run $IDLESTARTUP or $PYTHONSTARTUP before anything else 1279 -t title set title of shell window 1280 1281A default edit window will be bypassed when -c, -r, or - are used. 1282 1283[arg]* are passed to the command (-c) or script (-r) in sys.argv[1:]. 1284 1285Examples: 1286 1287idle 1288 Open an edit window or shell depending on IDLE's configuration. 1289 1290idle foo.py foobar.py 1291 Edit the files, also open a shell if configured to start with shell. 1292 1293idle -est "Baz" foo.py 1294 Run $IDLESTARTUP or $PYTHONSTARTUP, edit foo.py, and open a shell 1295 window with the title "Baz". 1296 1297idle -c "import sys; print sys.argv" "foo" 1298 Open a shell window and run the command, passing "-c" in sys.argv[0] 1299 and "foo" in sys.argv[1]. 1300 1301idle -d -s -r foo.py "Hello World" 1302 Open a shell window, run a startup script, enable the debugger, and 1303 run foo.py, passing "foo.py" in sys.argv[0] and "Hello World" in 1304 sys.argv[1]. 1305 1306echo "import sys; print sys.argv" | idle - "foobar" 1307 Open a shell window, run the script piped in, passing '' in sys.argv[0] 1308 and "foobar" in sys.argv[1]. 1309""" 1310 1311def main(): 1312 global flist, root, use_subprocess 1313 1314 use_subprocess = True 1315 enable_shell = False 1316 enable_edit = False 1317 debug = False 1318 cmd = None 1319 script = None 1320 startup = False 1321 try: 1322 opts, args = getopt.getopt(sys.argv[1:], "c:deihnr:st:") 1323 except getopt.error, msg: 1324 sys.stderr.write("Error: %s\n" % str(msg)) 1325 sys.stderr.write(usage_msg) 1326 sys.exit(2) 1327 for o, a in opts: 1328 if o == '-c': 1329 cmd = a 1330 enable_shell = True 1331 if o == '-d': 1332 debug = True 1333 enable_shell = True 1334 if o == '-e': 1335 enable_edit = True 1336 if o == '-h': 1337 sys.stdout.write(usage_msg) 1338 sys.exit() 1339 if o == '-i': 1340 enable_shell = True 1341 if o == '-n': 1342 use_subprocess = False 1343 if o == '-r': 1344 script = a 1345 if os.path.isfile(script): 1346 pass 1347 else: 1348 print "No script file: ", script 1349 sys.exit() 1350 enable_shell = True 1351 if o == '-s': 1352 startup = True 1353 enable_shell = True 1354 if o == '-t': 1355 PyShell.shell_title = a 1356 enable_shell = True 1357 if args and args[0] == '-': 1358 cmd = sys.stdin.read() 1359 enable_shell = True 1360 # process sys.argv and sys.path: 1361 for i in range(len(sys.path)): 1362 sys.path[i] = os.path.abspath(sys.path[i]) 1363 if args and args[0] == '-': 1364 sys.argv = [''] + args[1:] 1365 elif cmd: 1366 sys.argv = ['-c'] + args 1367 elif script: 1368 sys.argv = [script] + args 1369 elif args: 1370 enable_edit = True 1371 pathx = [] 1372 for filename in args: 1373 pathx.append(os.path.dirname(filename)) 1374 for dir in pathx: 1375 dir = os.path.abspath(dir) 1376 if not dir in sys.path: 1377 sys.path.insert(0, dir) 1378 else: 1379 dir = os.getcwd() 1380 if not dir in sys.path: 1381 sys.path.insert(0, dir) 1382 # check the IDLE settings configuration (but command line overrides) 1383 edit_start = idleConf.GetOption('main', 'General', 1384 'editor-on-startup', type='bool') 1385 enable_edit = enable_edit or edit_start 1386 enable_shell = enable_shell or not edit_start 1387 # start editor and/or shell windows: 1388 root = Tk(className="Idle") 1389 1390 fixwordbreaks(root) 1391 root.withdraw() 1392 flist = PyShellFileList(root) 1393 macosxSupport.setupApp(root, flist) 1394 1395 if enable_edit: 1396 if not (cmd or script): 1397 for filename in args: 1398 flist.open(filename) 1399 if not args: 1400 flist.new() 1401 if enable_shell: 1402 shell = flist.open_shell() 1403 if not shell: 1404 return # couldn't open shell 1405 1406 if macosxSupport.runningAsOSXApp() and flist.dict: 1407 # On OSX: when the user has double-clicked on a file that causes 1408 # IDLE to be launched the shell window will open just in front of 1409 # the file she wants to see. Lower the interpreter window when 1410 # there are open files. 1411 shell.top.lower() 1412 1413 shell = flist.pyshell 1414 # handle remaining options: 1415 if debug: 1416 shell.open_debugger() 1417 if startup: 1418 filename = os.environ.get("IDLESTARTUP") or \ 1419 os.environ.get("PYTHONSTARTUP") 1420 if filename and os.path.isfile(filename): 1421 shell.interp.execfile(filename) 1422 if shell and cmd or script: 1423 shell.interp.runcommand("""if 1: 1424 import sys as _sys 1425 _sys.argv = %r 1426 del _sys 1427 \n""" % (sys.argv,)) 1428 if cmd: 1429 shell.interp.execsource(cmd) 1430 elif script: 1431 shell.interp.prepend_syspath(script) 1432 shell.interp.execfile(script) 1433 1434 root.mainloop() 1435 root.destroy() 1436 1437if __name__ == "__main__": 1438 sys.modules['PyShell'] = sys.modules['__main__'] 1439 main() 1440