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