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