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