Coverage for pyshell.py: 15%

1110 statements  

« prev     ^ index     » next       coverage.py v7.2.5, created at 2023-05-11 13:22 -0700

1#! /usr/bin/env python3 

2 

3import sys 

4if __name__ == "__main__": 4 ↛ 5line 4 didn't jump to line 5, because the condition on line 4 was never true

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': 16 ↛ 17line 16 didn't jump to line 17, because the condition on line 16 was never true

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: 81 ↛ 82line 81 didn't jump to line 82, because the condition on line 81 was never true1g

82 file = warning_stream 

83 try: 1g

84 file.write(idle_formatwarning( 1g

85 message, category, filename, lineno, line=line)) 

86 file.write(">>> ") 1g

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: 1ab

97 if _warnings_showwarning is None: 97 ↛ exitline 97 didn't return from function 'capture_warnings', because the condition on line 97 was never false1ab

98 _warnings_showwarning = warnings.showwarning 1ab

99 warnings.showwarning = idle_showwarning 1ab

100 else: 

101 if _warnings_showwarning is not None: 101 ↛ exitline 101 didn't return from function 'capture_warnings', because the condition on line 101 was never false1ab

102 warnings.showwarning = _warnings_showwarning 1ab

103 _warnings_showwarning = None 1ab

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 1cde

119 save = {} 1cde

120 for key in list(cache): 1cde

121 if key[:1] + key[-1:] == '<>': 121 ↛ 122line 121 didn't jump to line 122, because the condition on line 121 was never true1cde

122 save[key] = cache.pop(key) 

123 orig_checkcache(filename) 1cde

124 cache.update(save) 1cde

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, "r") 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, "r") 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'} =" 1fh

408 if width >= len(tag): 1fh

409 div, mod = divmod((width -len(tag)), 2) 1fh

410 return f"{(div+mod)*'='}{tag}{div*'='}" 1fh

411 else: 

412 return tag[:-2] # Remove ' ='. 1f

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 = "__import__('idlelib.run').run.main(%r)" % (del_exitf,) 

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""" % (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""" % (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("Error: %s\n%s" % (msg, 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""" % (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__": 1696 ↛ 1697line 1696 didn't jump to line 1697, because the condition on line 1696 was never true

1697 main() 

1698 

1699capture_warnings(False) # Make sure turned off; see issue 18081