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