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