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