Coverage for pyshell.py: 15%
1110 statements
« prev ^ index » next coverage.py v7.2.5, created at 2023-05-11 13:22 -0700
« prev ^ index » next coverage.py v7.2.5, created at 2023-05-11 13:22 -0700
1#! /usr/bin/env python3
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__']
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)
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
24from tkinter import messagebox
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
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
54# Default for testing; defaults to True in main() for running.
55use_subprocess = False
57HOST = '127.0.0.1' # python execution server on localhost loopback
58PORT = 0 # someday pass in host, port for remote debug capability
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
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.
73def idle_showwarning(
74 message, category, filename, lineno, file=None, line=None):
75 """Show Idle-format warning (after replacing warnings.showwarning).
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.
90_warnings_showwarning = None
92def capture_warnings(capture):
93 "Replace warning.showwarning with idle_showwarning, or reverse."
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
105capture_warnings(True)
107def extended_linecache_checkcache(filename=None,
108 orig_checkcache=linecache.checkcache):
109 """Extend linecache.checkcache to preserve the <pyshell#...> entries
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.
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
126# Patch linecache.checkcache():
127linecache.checkcache = extended_linecache_checkcache
130class PyShellEditorWindow(EditorWindow):
131 "Regular text edit window in IDLE, supports breakpoints"
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)
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()
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 ]
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)
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
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)
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
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
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)
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)
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
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
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)
314 def _close(self):
315 "Extend base method - clear breaks when module is closed"
316 self.clear_file_breaks()
317 EditorWindow._close(self)
320class PyShellFileList(FileList):
321 "Extend base class: IDLE supports a shell and breakpoints"
323 # override FileList's class variable, instances return PyShellEditorWindow
324 # instead of EditorWindow when new edit windows are created.
325 EditorWindow = PyShellEditorWindow
327 pyshell = None
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
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)
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")
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)
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)
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
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)
395class MyRPCClient(rpc.RPCClient):
397 def handle_EOF(self):
398 "Override the base class - just re-raise EOFError"
399 raise EOFError
401def restart_line(width, filename): # See bpo-38141.
402 """Return width long restart line formatted with filename.
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
415class ModifiedInterpreter(InteractiveInterpreter):
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
426 _afterid = None
427 rpcclt = None
428 rpcsubproc = None
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)
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)]
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
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
534 def __request_interrupt(self):
535 self.rpcclt.remotecall("exec", "interrupt_the_server", (), {})
537 def interrupt_subprocess(self):
538 threading.Thread(target=self.__request_interrupt).start()
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
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
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
575 self.runcommand("""if 1:
576 import sys as _sys
577 _sys.path = %r
578 del _sys
579 \n""" % (path,))
581 active_seq = None
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)
621 debugger = None
623 def setdebugger(self, debugger):
624 self.debugger = debugger
626 def getdebugger(self):
627 return self.debugger
629 def open_remote_stack_viewer(self):
630 """Initiate the remote stack viewer from a separate thread.
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.
638 """
639 self.tkconsole.text.after(300, self.remote_stack_viewer)
640 return
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
659 gid = 0
661 def execsource(self, source):
662 "Like runsource() but assumes complete exec source"
663 filename = self.stuffsource(source)
664 self.execfile(filename, source)
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)
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)
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
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,))
715 def showsyntaxerror(self, filename=None):
716 """Override Interactive Interpreter method: Use Colorizing
718 Color the offending position instead of printing it and pointing at it
719 with a caret.
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()
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()
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]
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
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
813 def write(self, s):
814 "Override base class method"
815 return self.tkconsole.stderr.write(s)
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)
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)
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)
844class PyShell(OutputWindow):
845 from idlelib.squeezer import Squeezer
847 shell_title = "IDLE Shell " + python_version()
849 # Override classes
850 ColorDelegator = ModifiedColorDelegator
851 UndoDelegator = ModifiedUndoDelegator
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 ]
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
875 allow_line_numbers = False
876 user_input_insert_tags = "stdin"
878 # New classes
879 from idlelib.history import History
880 from idlelib.sidebar import ShellSidebar
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)
894 self.shell_sidebar = None # initialized below
896 OutputWindow.__init__(self, flist, None, None)
898 self.usetabs = False
899 # indentwidth must be 8 when using tabs. See note in EditorWindow:
900 self.indentwidth = 4
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
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)
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
952 self.shell_sidebar = self.ShellSidebar(self)
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())
960 def ResetFont(self):
961 super().ResetFont()
963 if self.shell_sidebar is not None:
964 self.shell_sidebar.update_font()
966 def ResetColorizer(self):
967 super().ResetColorizer()
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)
979 if self.shell_sidebar is not None:
980 self.shell_sidebar.update_colors()
982 def replace_event(self, event):
983 replace.replace(self.text, insert_tags="stdin")
984 return "break"
986 def get_standard_extension_names(self):
987 return idleConf.GetExtensions(shell_only=True)
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"
1006 def copy_with_prompts_callback(self, event=None):
1007 """Copy selected lines to the clipboard, with prompts.
1009 This makes the copied text useful for doc-tests and interactive
1010 shell code examples.
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)
1026 reading = False
1027 executing = False
1028 canceled = False
1029 endoffile = False
1030 closing = False
1031 _stop_readline_flag = False
1033 def set_warning_stream(self, stream):
1034 global warning_stream
1035 warning_stream = stream
1037 def get_warning_stream(self):
1038 return warning_stream
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()
1054 def set_debugger_indicator(self):
1055 db = self.interp.getdebugger()
1056 self.setvar("<<toggle-debugger>>", not not db)
1058 def toggle_jit_stack_viewer(self, event=None):
1059 pass # All we need is the variable
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()
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()
1086 def debug_menu_postcommand(self):
1087 state = 'disabled' if self.executing else 'normal'
1088 self.update_menu_state('debug', '*tack*iewer', state)
1090 def beginexecuting(self):
1091 "Helper for ModifiedInterpreter"
1092 self.resetoutput()
1093 self.executing = True
1095 def endexecuting(self):
1096 "Helper for ModifiedInterpreter"
1097 self.executing = False
1098 self.canceled = False
1099 self.showprompt()
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)
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)
1132 def ispythonsource(self, filename):
1133 "Override EditorWindow method: never remove the colorizer"
1134 return True
1136 def short_title(self):
1137 return self.shell_title
1139 COPYRIGHT = \
1140 'Type "help", "copyright", "credits" or "license()" for more information.'
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
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
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()
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
1197 def isatty(self):
1198 return True
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"
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"
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"
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"
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()
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()
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)
1380 def view_restart_mark(self, event=None):
1381 self.text.see("iomark")
1382 self.text.see("restart")
1384 def restart_shell(self, event=None):
1385 "Callback for Run/Restart Shell Cntl-F6"
1386 self.interp.restart_subprocess(with_cwd=True)
1388 def showprompt(self):
1389 self.resetoutput()
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)
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()
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")
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()
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
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()
1442 def rmenu_check_paste(self):
1443 if self.text.compare('insert','<','iomark'):
1444 return 'disabled'
1445 return super().rmenu_check_paste()
1447 def squeeze_current_text_event(self, event=None):
1448 self.squeezer.squeeze_current_text()
1449 self.shell_sidebar.update_sidebar()
1451 def on_squeezed_expand(self, index, text, tags):
1452 self.shell_sidebar.update_sidebar()
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>>'))
1466usage_msg = """\
1468USAGE: idle [-deins] [-t title] [file]*
1469 idle [-dns] [-t title] (-c cmd | -r file) [arg]*
1470 idle [-dns] [-t title] - [arg]*
1472 -h print this help message and exit
1473 -n run IDLE without a subprocess (DEPRECATED,
1474 see Help/IDLE Help for details)
1476The following options will override the IDLE 'settings' configuration:
1478 -e open an edit window
1479 -i open a shell window
1481The following options imply -i and will open a shell:
1483 -c cmd run the command in a shell, or
1484 -r file run script from file
1486 -d enable the debugger
1487 -s run $IDLESTARTUP or $PYTHONSTARTUP before anything else
1488 -t title set title of shell window
1490A default edit window will be bypassed when -c, -r, or - are used.
1492[arg]* are passed to the command (-c) or script (-r) in sys.argv[1:].
1494Examples:
1496idle
1497 Open an edit window or shell depending on IDLE's configuration.
1499idle foo.py foobar.py
1500 Edit the files, also open a shell if configured to start with shell.
1502idle -est "Baz" foo.py
1503 Run $IDLESTARTUP or $PYTHONSTARTUP, edit foo.py, and open a shell
1504 window with the title "Baz".
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].
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].
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"""
1520def main():
1521 import getopt
1522 from platform import system
1523 from idlelib import testing # bool value
1524 from idlelib import macosx
1526 global flist, root, use_subprocess
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
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)
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)
1631 # start editor and/or shell windows:
1632 fixwordbreaks(root)
1633 fix_x11_paste(root)
1634 flist = PyShellFileList(root)
1635 macosx.setupApp(root, flist)
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()
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
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.
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)
1691 while flist.inversedict: # keep IDLE running while files are open.
1692 root.mainloop()
1693 root.destroy()
1694 capture_warnings(False)
1696if __name__ == "__main__": 1696 ↛ 1697line 1696 didn't jump to line 1697, because the condition on line 1696 was never true
1697 main()
1699capture_warnings(False) # Make sure turned off; see issue 18081