python (3.11.7)
       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>>", self.set_breakpoint_event)
     137          self.text.bind("<<clear-breakpoint>>", self.clear_breakpoint_event)
     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>>", None),
     159          ("Clear Breakpoint", "<<clear-breakpoint>>", 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(filename, lineno)
     185          except: # but debugger may not be active right now....
     186              pass
     187  
     188      def set_breakpoint_event(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_event(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(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          "Remove keys other than '<pyshell#n>'."
     751          cache = linecache.cache
     752          for key in list(cache):  # Iterate list because mutate cache.
     753              if key[:1] + key[-1:] != "<>":
     754                  del cache[key]
     755  
     756      def runcommand(self, code):
     757          "Run the code without invoking the debugger"
     758          # The code better not raise an exception!
     759          if self.tkconsole.executing:
     760              self.display_executing_dialog()
     761              return 0
     762          if self.rpcclt:
     763              self.rpcclt.remotequeue("exec", "runcode", (code,), {})
     764          else:
     765              exec(code, self.locals)
     766          return 1
     767  
     768      def runcode(self, code):
     769          "Override base class method"
     770          if self.tkconsole.executing:
     771              self.restart_subprocess()
     772          self.checklinecache()
     773          debugger = self.debugger
     774          try:
     775              self.tkconsole.beginexecuting()
     776              if not debugger and self.rpcclt is not None:
     777                  self.active_seq = self.rpcclt.asyncqueue("exec", "runcode",
     778                                                          (code,), {})
     779              elif debugger:
     780                  debugger.run(code, self.locals)
     781              else:
     782                  exec(code, self.locals)
     783          except SystemExit:
     784              if not self.tkconsole.closing:
     785                  if messagebox.askyesno(
     786                      "Exit?",
     787                      "Do you want to exit altogether?",
     788                      default="yes",
     789                      parent=self.tkconsole.text):
     790                      raise
     791                  else:
     792                      self.showtraceback()
     793              else:
     794                  raise
     795          except:
     796              if use_subprocess:
     797                  print("IDLE internal error in runcode()",
     798                        file=self.tkconsole.stderr)
     799                  self.showtraceback()
     800                  self.tkconsole.endexecuting()
     801              else:
     802                  if self.tkconsole.canceled:
     803                      self.tkconsole.canceled = False
     804                      print("KeyboardInterrupt", file=self.tkconsole.stderr)
     805                  else:
     806                      self.showtraceback()
     807          finally:
     808              if not use_subprocess:
     809                  try:
     810                      self.tkconsole.endexecuting()
     811                  except AttributeError:  # shell may have closed
     812                      pass
     813  
     814      def write(self, s):
     815          "Override base class method"
     816          return self.tkconsole.stderr.write(s)
     817  
     818      def display_port_binding_error(self):
     819          messagebox.showerror(
     820              "Port Binding Error",
     821              "IDLE can't bind to a TCP/IP port, which is necessary to "
     822              "communicate with its Python execution server.  This might be "
     823              "because no networking is installed on this computer.  "
     824              "Run IDLE with the -n command line switch to start without a "
     825              "subprocess and refer to Help/IDLE Help 'Running without a "
     826              "subprocess' for further details.",
     827              parent=self.tkconsole.text)
     828  
     829      def display_no_subprocess_error(self):
     830          messagebox.showerror(
     831              "Subprocess Connection Error",
     832              "IDLE's subprocess didn't make connection.\n"
     833              "See the 'Startup failure' section of the IDLE doc, online at\n"
     834              "https://docs.python.org/3/library/idle.html#startup-failure",
     835              parent=self.tkconsole.text)
     836  
     837      def display_executing_dialog(self):
     838          messagebox.showerror(
     839              "Already executing",
     840              "The Python Shell window is already executing a command; "
     841              "please wait until it is finished.",
     842              parent=self.tkconsole.text)
     843  
     844  
     845  class ESC[4;38;5;81mPyShell(ESC[4;38;5;149mOutputWindow):
     846      from idlelib.squeezer import Squeezer
     847  
     848      shell_title = "IDLE Shell " + python_version()
     849  
     850      # Override classes
     851      ColorDelegator = ModifiedColorDelegator
     852      UndoDelegator = ModifiedUndoDelegator
     853  
     854      # Override menus
     855      menu_specs = [
     856          ("file", "_File"),
     857          ("edit", "_Edit"),
     858          ("debug", "_Debug"),
     859          ("options", "_Options"),
     860          ("window", "_Window"),
     861          ("help", "_Help"),
     862      ]
     863  
     864      # Extend right-click context menu
     865      rmenu_specs = OutputWindow.rmenu_specs + [
     866          ("Squeeze", "<<squeeze-current-text>>"),
     867      ]
     868      _idx = 1 + len(list(itertools.takewhile(
     869          lambda rmenu_item: rmenu_item[0] != "Copy", rmenu_specs)
     870      ))
     871      rmenu_specs.insert(_idx, ("Copy with prompts",
     872                                "<<copy-with-prompts>>",
     873                                "rmenu_check_copy"))
     874      del _idx
     875  
     876      allow_line_numbers = False
     877      user_input_insert_tags = "stdin"
     878  
     879      # New classes
     880      from idlelib.history import History
     881      from idlelib.sidebar import ShellSidebar
     882  
     883      def __init__(self, flist=None):
     884          if use_subprocess:
     885              ms = self.menu_specs
     886              if ms[2][0] != "shell":
     887                  ms.insert(2, ("shell", "She_ll"))
     888          self.interp = ModifiedInterpreter(self)
     889          if flist is None:
     890              root = Tk()
     891              fixwordbreaks(root)
     892              root.withdraw()
     893              flist = PyShellFileList(root)
     894  
     895          self.shell_sidebar = None  # initialized below
     896  
     897          OutputWindow.__init__(self, flist, None, None)
     898  
     899          self.usetabs = False
     900          # indentwidth must be 8 when using tabs.  See note in EditorWindow:
     901          self.indentwidth = 4
     902  
     903          self.sys_ps1 = sys.ps1 if hasattr(sys, 'ps1') else '>>>\n'
     904          self.prompt_last_line = self.sys_ps1.split('\n')[-1]
     905          self.prompt = self.sys_ps1  # Changes when debug active
     906  
     907          text = self.text
     908          text.configure(wrap="char")
     909          text.bind("<<newline-and-indent>>", self.enter_callback)
     910          text.bind("<<plain-newline-and-indent>>", self.linefeed_callback)
     911          text.bind("<<interrupt-execution>>", self.cancel_callback)
     912          text.bind("<<end-of-file>>", self.eof_callback)
     913          text.bind("<<open-stack-viewer>>", self.open_stack_viewer)
     914          text.bind("<<toggle-debugger>>", self.toggle_debugger)
     915          text.bind("<<toggle-jit-stack-viewer>>", self.toggle_jit_stack_viewer)
     916          text.bind("<<copy-with-prompts>>", self.copy_with_prompts_callback)
     917          if use_subprocess:
     918              text.bind("<<view-restart>>", self.view_restart_mark)
     919              text.bind("<<restart-shell>>", self.restart_shell)
     920          self.squeezer = self.Squeezer(self)
     921          text.bind("<<squeeze-current-text>>",
     922                    self.squeeze_current_text_event)
     923  
     924          self.save_stdout = sys.stdout
     925          self.save_stderr = sys.stderr
     926          self.save_stdin = sys.stdin
     927          from idlelib import iomenu
     928          self.stdin = StdInputFile(self, "stdin",
     929                                    iomenu.encoding, iomenu.errors)
     930          self.stdout = StdOutputFile(self, "stdout",
     931                                      iomenu.encoding, iomenu.errors)
     932          self.stderr = StdOutputFile(self, "stderr",
     933                                      iomenu.encoding, "backslashreplace")
     934          self.console = StdOutputFile(self, "console",
     935                                       iomenu.encoding, iomenu.errors)
     936          if not use_subprocess:
     937              sys.stdout = self.stdout
     938              sys.stderr = self.stderr
     939              sys.stdin = self.stdin
     940          try:
     941              # page help() text to shell.
     942              import pydoc # import must be done here to capture i/o rebinding.
     943              # XXX KBK 27Dec07 use text viewer someday, but must work w/o subproc
     944              pydoc.pager = pydoc.plainpager
     945          except:
     946              sys.stderr = sys.__stderr__
     947              raise
     948          #
     949          self.history = self.History(self.text)
     950          #
     951          self.pollinterval = 50  # millisec
     952  
     953          self.shell_sidebar = self.ShellSidebar(self)
     954  
     955          # Insert UserInputTaggingDelegator at the top of the percolator,
     956          # but make calls to text.insert() skip it.  This causes only insert
     957          # events generated in Tcl/Tk to go through this delegator.
     958          self.text.insert = self.per.top.insert
     959          self.per.insertfilter(UserInputTaggingDelegator())
     960  
     961      def ResetFont(self):
     962          super().ResetFont()
     963  
     964          if self.shell_sidebar is not None:
     965              self.shell_sidebar.update_font()
     966  
     967      def ResetColorizer(self):
     968          super().ResetColorizer()
     969  
     970          theme = idleConf.CurrentTheme()
     971          tag_colors = {
     972            "stdin": {'background': None, 'foreground': None},
     973            "stdout": idleConf.GetHighlight(theme, "stdout"),
     974            "stderr": idleConf.GetHighlight(theme, "stderr"),
     975            "console": idleConf.GetHighlight(theme, "normal"),
     976          }
     977          for tag, tag_colors_config in tag_colors.items():
     978              self.text.tag_configure(tag, **tag_colors_config)
     979  
     980          if self.shell_sidebar is not None:
     981              self.shell_sidebar.update_colors()
     982  
     983      def replace_event(self, event):
     984          replace.replace(self.text, insert_tags="stdin")
     985          return "break"
     986  
     987      def get_standard_extension_names(self):
     988          return idleConf.GetExtensions(shell_only=True)
     989  
     990      def get_prompt_text(self, first, last):
     991          """Return text between first and last with prompts added."""
     992          text = self.text.get(first, last)
     993          lineno_range = range(
     994              int(float(first)),
     995              int(float(last))
     996           )
     997          prompts = [
     998              self.shell_sidebar.line_prompts.get(lineno)
     999              for lineno in lineno_range
    1000          ]
    1001          return "\n".join(
    1002              line if prompt is None else f"{prompt} {line}"
    1003              for prompt, line in zip(prompts, text.splitlines())
    1004          ) + "\n"
    1005  
    1006  
    1007      def copy_with_prompts_callback(self, event=None):
    1008          """Copy selected lines to the clipboard, with prompts.
    1009  
    1010          This makes the copied text useful for doc-tests and interactive
    1011          shell code examples.
    1012  
    1013          This always copies entire lines, even if only part of the first
    1014          and/or last lines is selected.
    1015          """
    1016          text = self.text
    1017          selfirst = text.index('sel.first linestart')
    1018          if selfirst is None:  # Should not be possible.
    1019              return  # No selection, do nothing.
    1020          sellast = text.index('sel.last')
    1021          if sellast[-1] != '0':
    1022              sellast = text.index("sel.last+1line linestart")
    1023          text.clipboard_clear()
    1024          prompt_text = self.get_prompt_text(selfirst, sellast)
    1025          text.clipboard_append(prompt_text)
    1026  
    1027      reading = False
    1028      executing = False
    1029      canceled = False
    1030      endoffile = False
    1031      closing = False
    1032      _stop_readline_flag = False
    1033  
    1034      def set_warning_stream(self, stream):
    1035          global warning_stream
    1036          warning_stream = stream
    1037  
    1038      def get_warning_stream(self):
    1039          return warning_stream
    1040  
    1041      def toggle_debugger(self, event=None):
    1042          if self.executing:
    1043              messagebox.showerror("Don't debug now",
    1044                  "You can only toggle the debugger when idle",
    1045                  parent=self.text)
    1046              self.set_debugger_indicator()
    1047              return "break"
    1048          else:
    1049              db = self.interp.getdebugger()
    1050              if db:
    1051                  self.close_debugger()
    1052              else:
    1053                  self.open_debugger()
    1054  
    1055      def set_debugger_indicator(self):
    1056          db = self.interp.getdebugger()
    1057          self.setvar("<<toggle-debugger>>", not not db)
    1058  
    1059      def toggle_jit_stack_viewer(self, event=None):
    1060          pass # All we need is the variable
    1061  
    1062      def close_debugger(self):
    1063          db = self.interp.getdebugger()
    1064          if db:
    1065              self.interp.setdebugger(None)
    1066              db.close()
    1067              if self.interp.rpcclt:
    1068                  debugger_r.close_remote_debugger(self.interp.rpcclt)
    1069              self.resetoutput()
    1070              self.console.write("[DEBUG OFF]\n")
    1071              self.prompt = self.sys_ps1
    1072              self.showprompt()
    1073          self.set_debugger_indicator()
    1074  
    1075      def open_debugger(self):
    1076          if self.interp.rpcclt:
    1077              dbg_gui = debugger_r.start_remote_debugger(self.interp.rpcclt,
    1078                                                             self)
    1079          else:
    1080              dbg_gui = debugger.Debugger(self)
    1081          self.interp.setdebugger(dbg_gui)
    1082          dbg_gui.load_breakpoints()
    1083          self.prompt = "[DEBUG ON]\n" + self.sys_ps1
    1084          self.showprompt()
    1085          self.set_debugger_indicator()
    1086  
    1087      def debug_menu_postcommand(self):
    1088          state = 'disabled' if self.executing else 'normal'
    1089          self.update_menu_state('debug', '*tack*iewer', state)
    1090  
    1091      def beginexecuting(self):
    1092          "Helper for ModifiedInterpreter"
    1093          self.resetoutput()
    1094          self.executing = True
    1095  
    1096      def endexecuting(self):
    1097          "Helper for ModifiedInterpreter"
    1098          self.executing = False
    1099          self.canceled = False
    1100          self.showprompt()
    1101  
    1102      def close(self):
    1103          "Extend EditorWindow.close()"
    1104          if self.executing:
    1105              response = messagebox.askokcancel(
    1106                  "Kill?",
    1107                  "Your program is still running!\n Do you want to kill it?",
    1108                  default="ok",
    1109                  parent=self.text)
    1110              if response is False:
    1111                  return "cancel"
    1112          self.stop_readline()
    1113          self.canceled = True
    1114          self.closing = True
    1115          return EditorWindow.close(self)
    1116  
    1117      def _close(self):
    1118          "Extend EditorWindow._close(), shut down debugger and execution server"
    1119          self.close_debugger()
    1120          if use_subprocess:
    1121              self.interp.kill_subprocess()
    1122          # Restore std streams
    1123          sys.stdout = self.save_stdout
    1124          sys.stderr = self.save_stderr
    1125          sys.stdin = self.save_stdin
    1126          # Break cycles
    1127          self.interp = None
    1128          self.console = None
    1129          self.flist.pyshell = None
    1130          self.history = None
    1131          EditorWindow._close(self)
    1132  
    1133      def ispythonsource(self, filename):
    1134          "Override EditorWindow method: never remove the colorizer"
    1135          return True
    1136  
    1137      def short_title(self):
    1138          return self.shell_title
    1139  
    1140      COPYRIGHT = \
    1141            'Type "help", "copyright", "credits" or "license()" for more information.'
    1142  
    1143      def begin(self):
    1144          self.text.mark_set("iomark", "insert")
    1145          self.resetoutput()
    1146          if use_subprocess:
    1147              nosub = ''
    1148              client = self.interp.start_subprocess()
    1149              if not client:
    1150                  self.close()
    1151                  return False
    1152          else:
    1153              nosub = ("==== No Subprocess ====\n\n" +
    1154                      "WARNING: Running IDLE without a Subprocess is deprecated\n" +
    1155                      "and will be removed in a later version. See Help/IDLE Help\n" +
    1156                      "for details.\n\n")
    1157              sys.displayhook = rpc.displayhook
    1158  
    1159          self.write("Python %s on %s\n%s\n%s" %
    1160                     (sys.version, sys.platform, self.COPYRIGHT, nosub))
    1161          self.text.focus_force()
    1162          self.showprompt()
    1163          # User code should use separate default Tk root window
    1164          import tkinter
    1165          tkinter._support_default_root = True
    1166          tkinter._default_root = None
    1167          return True
    1168  
    1169      def stop_readline(self):
    1170          if not self.reading:  # no nested mainloop to exit.
    1171              return
    1172          self._stop_readline_flag = True
    1173          self.top.quit()
    1174  
    1175      def readline(self):
    1176          save = self.reading
    1177          try:
    1178              self.reading = True
    1179              self.top.mainloop()  # nested mainloop()
    1180          finally:
    1181              self.reading = save
    1182          if self._stop_readline_flag:
    1183              self._stop_readline_flag = False
    1184              return ""
    1185          line = self.text.get("iomark", "end-1c")
    1186          if len(line) == 0:  # may be EOF if we quit our mainloop with Ctrl-C
    1187              line = "\n"
    1188          self.resetoutput()
    1189          if self.canceled:
    1190              self.canceled = False
    1191              if not use_subprocess:
    1192                  raise KeyboardInterrupt
    1193          if self.endoffile:
    1194              self.endoffile = False
    1195              line = ""
    1196          return line
    1197  
    1198      def isatty(self):
    1199          return True
    1200  
    1201      def cancel_callback(self, event=None):
    1202          try:
    1203              if self.text.compare("sel.first", "!=", "sel.last"):
    1204                  return # Active selection -- always use default binding
    1205          except:
    1206              pass
    1207          if not (self.executing or self.reading):
    1208              self.resetoutput()
    1209              self.interp.write("KeyboardInterrupt\n")
    1210              self.showprompt()
    1211              return "break"
    1212          self.endoffile = False
    1213          self.canceled = True
    1214          if (self.executing and self.interp.rpcclt):
    1215              if self.interp.getdebugger():
    1216                  self.interp.restart_subprocess()
    1217              else:
    1218                  self.interp.interrupt_subprocess()
    1219          if self.reading:
    1220              self.top.quit()  # exit the nested mainloop() in readline()
    1221          return "break"
    1222  
    1223      def eof_callback(self, event):
    1224          if self.executing and not self.reading:
    1225              return # Let the default binding (delete next char) take over
    1226          if not (self.text.compare("iomark", "==", "insert") and
    1227                  self.text.compare("insert", "==", "end-1c")):
    1228              return # Let the default binding (delete next char) take over
    1229          if not self.executing:
    1230              self.resetoutput()
    1231              self.close()
    1232          else:
    1233              self.canceled = False
    1234              self.endoffile = True
    1235              self.top.quit()
    1236          return "break"
    1237  
    1238      def linefeed_callback(self, event):
    1239          # Insert a linefeed without entering anything (still autoindented)
    1240          if self.reading:
    1241              self.text.insert("insert", "\n")
    1242              self.text.see("insert")
    1243          else:
    1244              self.newline_and_indent_event(event)
    1245          return "break"
    1246  
    1247      def enter_callback(self, event):
    1248          if self.executing and not self.reading:
    1249              return # Let the default binding (insert '\n') take over
    1250          # If some text is selected, recall the selection
    1251          # (but only if this before the I/O mark)
    1252          try:
    1253              sel = self.text.get("sel.first", "sel.last")
    1254              if sel:
    1255                  if self.text.compare("sel.last", "<=", "iomark"):
    1256                      self.recall(sel, event)
    1257                      return "break"
    1258          except:
    1259              pass
    1260          # If we're strictly before the line containing iomark, recall
    1261          # the current line, less a leading prompt, less leading or
    1262          # trailing whitespace
    1263          if self.text.compare("insert", "<", "iomark linestart"):
    1264              # Check if there's a relevant stdin range -- if so, use it.
    1265              # Note: "stdin" blocks may include several successive statements,
    1266              # so look for "console" tags on the newline before each statement
    1267              # (and possibly on prompts).
    1268              prev = self.text.tag_prevrange("stdin", "insert")
    1269              if (
    1270                      prev and
    1271                      self.text.compare("insert", "<", prev[1]) and
    1272                      # The following is needed to handle empty statements.
    1273                      "console" not in self.text.tag_names("insert")
    1274              ):
    1275                  prev_cons = self.text.tag_prevrange("console", "insert")
    1276                  if prev_cons and self.text.compare(prev_cons[1], ">=", prev[0]):
    1277                      prev = (prev_cons[1], prev[1])
    1278                  next_cons = self.text.tag_nextrange("console", "insert")
    1279                  if next_cons and self.text.compare(next_cons[0], "<", prev[1]):
    1280                      prev = (prev[0], self.text.index(next_cons[0] + "+1c"))
    1281                  self.recall(self.text.get(prev[0], prev[1]), event)
    1282                  return "break"
    1283              next = self.text.tag_nextrange("stdin", "insert")
    1284              if next and self.text.compare("insert lineend", ">=", next[0]):
    1285                  next_cons = self.text.tag_nextrange("console", "insert lineend")
    1286                  if next_cons and self.text.compare(next_cons[0], "<", next[1]):
    1287                      next = (next[0], self.text.index(next_cons[0] + "+1c"))
    1288                  self.recall(self.text.get(next[0], next[1]), event)
    1289                  return "break"
    1290              # No stdin mark -- just get the current line, less any prompt
    1291              indices = self.text.tag_nextrange("console", "insert linestart")
    1292              if indices and \
    1293                 self.text.compare(indices[0], "<=", "insert linestart"):
    1294                  self.recall(self.text.get(indices[1], "insert lineend"), event)
    1295              else:
    1296                  self.recall(self.text.get("insert linestart", "insert lineend"), event)
    1297              return "break"
    1298          # If we're between the beginning of the line and the iomark, i.e.
    1299          # in the prompt area, move to the end of the prompt
    1300          if self.text.compare("insert", "<", "iomark"):
    1301              self.text.mark_set("insert", "iomark")
    1302          # If we're in the current input and there's only whitespace
    1303          # beyond the cursor, erase that whitespace first
    1304          s = self.text.get("insert", "end-1c")
    1305          if s and not s.strip():
    1306              self.text.delete("insert", "end-1c")
    1307          # If we're in the current input before its last line,
    1308          # insert a newline right at the insert point
    1309          if self.text.compare("insert", "<", "end-1c linestart"):
    1310              self.newline_and_indent_event(event)
    1311              return "break"
    1312          # We're in the last line; append a newline and submit it
    1313          self.text.mark_set("insert", "end-1c")
    1314          if self.reading:
    1315              self.text.insert("insert", "\n")
    1316              self.text.see("insert")
    1317          else:
    1318              self.newline_and_indent_event(event)
    1319          self.text.update_idletasks()
    1320          if self.reading:
    1321              self.top.quit() # Break out of recursive mainloop()
    1322          else:
    1323              self.runit()
    1324          return "break"
    1325  
    1326      def recall(self, s, event):
    1327          # remove leading and trailing empty or whitespace lines
    1328          s = re.sub(r'^\s*\n', '', s)
    1329          s = re.sub(r'\n\s*$', '', s)
    1330          lines = s.split('\n')
    1331          self.text.undo_block_start()
    1332          try:
    1333              self.text.tag_remove("sel", "1.0", "end")
    1334              self.text.mark_set("insert", "end-1c")
    1335              prefix = self.text.get("insert linestart", "insert")
    1336              if prefix.rstrip().endswith(':'):
    1337                  self.newline_and_indent_event(event)
    1338                  prefix = self.text.get("insert linestart", "insert")
    1339              self.text.insert("insert", lines[0].strip(),
    1340                               self.user_input_insert_tags)
    1341              if len(lines) > 1:
    1342                  orig_base_indent = re.search(r'^([ \t]*)', lines[0]).group(0)
    1343                  new_base_indent  = re.search(r'^([ \t]*)', prefix).group(0)
    1344                  for line in lines[1:]:
    1345                      if line.startswith(orig_base_indent):
    1346                          # replace orig base indentation with new indentation
    1347                          line = new_base_indent + line[len(orig_base_indent):]
    1348                      self.text.insert('insert', '\n' + line.rstrip(),
    1349                                       self.user_input_insert_tags)
    1350          finally:
    1351              self.text.see("insert")
    1352              self.text.undo_block_stop()
    1353  
    1354      _last_newline_re = re.compile(r"[ \t]*(\n[ \t]*)?\Z")
    1355      def runit(self):
    1356          index_before = self.text.index("end-2c")
    1357          line = self.text.get("iomark", "end-1c")
    1358          # Strip off last newline and surrounding whitespace.
    1359          # (To allow you to hit return twice to end a statement.)
    1360          line = self._last_newline_re.sub("", line)
    1361          input_is_complete = self.interp.runsource(line)
    1362          if not input_is_complete:
    1363              if self.text.get(index_before) == '\n':
    1364                  self.text.tag_remove(self.user_input_insert_tags, index_before)
    1365              self.shell_sidebar.update_sidebar()
    1366  
    1367      def open_stack_viewer(self, event=None):  # -n mode only
    1368          if self.interp.rpcclt:
    1369              return self.interp.remote_stack_viewer()
    1370  
    1371          from idlelib.stackviewer import StackBrowser
    1372          try:
    1373              StackBrowser(self.root, sys.last_value, self.flist)
    1374          except:
    1375              messagebox.showerror("No stack trace",
    1376                  "There is no stack trace yet.\n"
    1377                  "(sys.last_value is not defined)",
    1378                  parent=self.text)
    1379          return None
    1380  
    1381      def view_restart_mark(self, event=None):
    1382          self.text.see("iomark")
    1383          self.text.see("restart")
    1384  
    1385      def restart_shell(self, event=None):
    1386          "Callback for Run/Restart Shell Cntl-F6"
    1387          self.interp.restart_subprocess(with_cwd=True)
    1388  
    1389      def showprompt(self):
    1390          self.resetoutput()
    1391  
    1392          prompt = self.prompt
    1393          if self.sys_ps1 and prompt.endswith(self.sys_ps1):
    1394              prompt = prompt[:-len(self.sys_ps1)]
    1395          self.text.tag_add("console", "iomark-1c")
    1396          self.console.write(prompt)
    1397  
    1398          self.shell_sidebar.update_sidebar()
    1399          self.text.mark_set("insert", "end-1c")
    1400          self.set_line_and_column()
    1401          self.io.reset_undo()
    1402  
    1403      def show_warning(self, msg):
    1404          width = self.interp.tkconsole.width
    1405          wrapper = TextWrapper(width=width, tabsize=8, expand_tabs=True)
    1406          wrapped_msg = '\n'.join(wrapper.wrap(msg))
    1407          if not wrapped_msg.endswith('\n'):
    1408              wrapped_msg += '\n'
    1409          self.per.bottom.insert("iomark linestart", wrapped_msg, "stderr")
    1410  
    1411      def resetoutput(self):
    1412          source = self.text.get("iomark", "end-1c")
    1413          if self.history:
    1414              self.history.store(source)
    1415          if self.text.get("end-2c") != "\n":
    1416              self.text.insert("end-1c", "\n")
    1417          self.text.mark_set("iomark", "end-1c")
    1418          self.set_line_and_column()
    1419          self.ctip.remove_calltip_window()
    1420  
    1421      def write(self, s, tags=()):
    1422          try:
    1423              self.text.mark_gravity("iomark", "right")
    1424              count = OutputWindow.write(self, s, tags, "iomark")
    1425              self.text.mark_gravity("iomark", "left")
    1426          except:
    1427              raise ###pass  # ### 11Aug07 KBK if we are expecting exceptions
    1428                             # let's find out what they are and be specific.
    1429          if self.canceled:
    1430              self.canceled = False
    1431              if not use_subprocess:
    1432                  raise KeyboardInterrupt
    1433          return count
    1434  
    1435      def rmenu_check_cut(self):
    1436          try:
    1437              if self.text.compare('sel.first', '<', 'iomark'):
    1438                  return 'disabled'
    1439          except TclError: # no selection, so the index 'sel.first' doesn't exist
    1440              return 'disabled'
    1441          return super().rmenu_check_cut()
    1442  
    1443      def rmenu_check_paste(self):
    1444          if self.text.compare('insert','<','iomark'):
    1445              return 'disabled'
    1446          return super().rmenu_check_paste()
    1447  
    1448      def squeeze_current_text_event(self, event=None):
    1449          self.squeezer.squeeze_current_text()
    1450          self.shell_sidebar.update_sidebar()
    1451  
    1452      def on_squeezed_expand(self, index, text, tags):
    1453          self.shell_sidebar.update_sidebar()
    1454  
    1455  
    1456  def fix_x11_paste(root):
    1457      "Make paste replace selection on x11.  See issue #5124."
    1458      if root._windowingsystem == 'x11':
    1459          for cls in 'Text', 'Entry', 'Spinbox':
    1460              root.bind_class(
    1461                  cls,
    1462                  '<<Paste>>',
    1463                  'catch {%W delete sel.first sel.last}\n' +
    1464                          root.bind_class(cls, '<<Paste>>'))
    1465  
    1466  
    1467  usage_msg = """\
    1468  
    1469  USAGE: idle  [-deins] [-t title] [file]*
    1470         idle  [-dns] [-t title] (-c cmd | -r file) [arg]*
    1471         idle  [-dns] [-t title] - [arg]*
    1472  
    1473    -h         print this help message and exit
    1474    -n         run IDLE without a subprocess (DEPRECATED,
    1475               see Help/IDLE Help for details)
    1476  
    1477  The following options will override the IDLE 'settings' configuration:
    1478  
    1479    -e         open an edit window
    1480    -i         open a shell window
    1481  
    1482  The following options imply -i and will open a shell:
    1483  
    1484    -c cmd     run the command in a shell, or
    1485    -r file    run script from file
    1486  
    1487    -d         enable the debugger
    1488    -s         run $IDLESTARTUP or $PYTHONSTARTUP before anything else
    1489    -t title   set title of shell window
    1490  
    1491  A default edit window will be bypassed when -c, -r, or - are used.
    1492  
    1493  [arg]* are passed to the command (-c) or script (-r) in sys.argv[1:].
    1494  
    1495  Examples:
    1496  
    1497  idle
    1498          Open an edit window or shell depending on IDLE's configuration.
    1499  
    1500  idle foo.py foobar.py
    1501          Edit the files, also open a shell if configured to start with shell.
    1502  
    1503  idle -est "Baz" foo.py
    1504          Run $IDLESTARTUP or $PYTHONSTARTUP, edit foo.py, and open a shell
    1505          window with the title "Baz".
    1506  
    1507  idle -c "import sys; print(sys.argv)" "foo"
    1508          Open a shell window and run the command, passing "-c" in sys.argv[0]
    1509          and "foo" in sys.argv[1].
    1510  
    1511  idle -d -s -r foo.py "Hello World"
    1512          Open a shell window, run a startup script, enable the debugger, and
    1513          run foo.py, passing "foo.py" in sys.argv[0] and "Hello World" in
    1514          sys.argv[1].
    1515  
    1516  echo "import sys; print(sys.argv)" | idle - "foobar"
    1517          Open a shell window, run the script piped in, passing '' in sys.argv[0]
    1518          and "foobar" in sys.argv[1].
    1519  """
    1520  
    1521  def main():
    1522      import getopt
    1523      from platform import system
    1524      from idlelib import testing  # bool value
    1525      from idlelib import macosx
    1526  
    1527      global flist, root, use_subprocess
    1528  
    1529      capture_warnings(True)
    1530      use_subprocess = True
    1531      enable_shell = False
    1532      enable_edit = False
    1533      debug = False
    1534      cmd = None
    1535      script = None
    1536      startup = False
    1537      try:
    1538          opts, args = getopt.getopt(sys.argv[1:], "c:deihnr:st:")
    1539      except getopt.error as msg:
    1540          print(f"Error: {msg}\n{usage_msg}", file=sys.stderr)
    1541          sys.exit(2)
    1542      for o, a in opts:
    1543          if o == '-c':
    1544              cmd = a
    1545              enable_shell = True
    1546          if o == '-d':
    1547              debug = True
    1548              enable_shell = True
    1549          if o == '-e':
    1550              enable_edit = True
    1551          if o == '-h':
    1552              sys.stdout.write(usage_msg)
    1553              sys.exit()
    1554          if o == '-i':
    1555              enable_shell = True
    1556          if o == '-n':
    1557              print(" Warning: running IDLE without a subprocess is deprecated.",
    1558                    file=sys.stderr)
    1559              use_subprocess = False
    1560          if o == '-r':
    1561              script = a
    1562              if os.path.isfile(script):
    1563                  pass
    1564              else:
    1565                  print("No script file: ", script)
    1566                  sys.exit()
    1567              enable_shell = True
    1568          if o == '-s':
    1569              startup = True
    1570              enable_shell = True
    1571          if o == '-t':
    1572              PyShell.shell_title = a
    1573              enable_shell = True
    1574      if args and args[0] == '-':
    1575          cmd = sys.stdin.read()
    1576          enable_shell = True
    1577      # process sys.argv and sys.path:
    1578      for i in range(len(sys.path)):
    1579          sys.path[i] = os.path.abspath(sys.path[i])
    1580      if args and args[0] == '-':
    1581          sys.argv = [''] + args[1:]
    1582      elif cmd:
    1583          sys.argv = ['-c'] + args
    1584      elif script:
    1585          sys.argv = [script] + args
    1586      elif args:
    1587          enable_edit = True
    1588          pathx = []
    1589          for filename in args:
    1590              pathx.append(os.path.dirname(filename))
    1591          for dir in pathx:
    1592              dir = os.path.abspath(dir)
    1593              if not dir in sys.path:
    1594                  sys.path.insert(0, dir)
    1595      else:
    1596          dir = os.getcwd()
    1597          if dir not in sys.path:
    1598              sys.path.insert(0, dir)
    1599      # check the IDLE settings configuration (but command line overrides)
    1600      edit_start = idleConf.GetOption('main', 'General',
    1601                                      'editor-on-startup', type='bool')
    1602      enable_edit = enable_edit or edit_start
    1603      enable_shell = enable_shell or not enable_edit
    1604  
    1605      # Setup root.  Don't break user code run in IDLE process.
    1606      # Don't change environment when testing.
    1607      if use_subprocess and not testing:
    1608          NoDefaultRoot()
    1609      root = Tk(className="Idle")
    1610      root.withdraw()
    1611      from idlelib.run import fix_scaling
    1612      fix_scaling(root)
    1613  
    1614      # set application icon
    1615      icondir = os.path.join(os.path.dirname(__file__), 'Icons')
    1616      if system() == 'Windows':
    1617          iconfile = os.path.join(icondir, 'idle.ico')
    1618          root.wm_iconbitmap(default=iconfile)
    1619      elif not macosx.isAquaTk():
    1620          if TkVersion >= 8.6:
    1621              ext = '.png'
    1622              sizes = (16, 32, 48, 256)
    1623          else:
    1624              ext = '.gif'
    1625              sizes = (16, 32, 48)
    1626          iconfiles = [os.path.join(icondir, 'idle_%d%s' % (size, ext))
    1627                       for size in sizes]
    1628          icons = [PhotoImage(master=root, file=iconfile)
    1629                   for iconfile in iconfiles]
    1630          root.wm_iconphoto(True, *icons)
    1631  
    1632      # start editor and/or shell windows:
    1633      fixwordbreaks(root)
    1634      fix_x11_paste(root)
    1635      flist = PyShellFileList(root)
    1636      macosx.setupApp(root, flist)
    1637  
    1638      if enable_edit:
    1639          if not (cmd or script):
    1640              for filename in args[:]:
    1641                  if flist.open(filename) is None:
    1642                      # filename is a directory actually, disconsider it
    1643                      args.remove(filename)
    1644              if not args:
    1645                  flist.new()
    1646  
    1647      if enable_shell:
    1648          shell = flist.open_shell()
    1649          if not shell:
    1650              return # couldn't open shell
    1651          if macosx.isAquaTk() and flist.dict:
    1652              # On OSX: when the user has double-clicked on a file that causes
    1653              # IDLE to be launched the shell window will open just in front of
    1654              # the file she wants to see. Lower the interpreter window when
    1655              # there are open files.
    1656              shell.top.lower()
    1657      else:
    1658          shell = flist.pyshell
    1659  
    1660      # Handle remaining options. If any of these are set, enable_shell
    1661      # was set also, so shell must be true to reach here.
    1662      if debug:
    1663          shell.open_debugger()
    1664      if startup:
    1665          filename = os.environ.get("IDLESTARTUP") or \
    1666                     os.environ.get("PYTHONSTARTUP")
    1667          if filename and os.path.isfile(filename):
    1668              shell.interp.execfile(filename)
    1669      if cmd or script:
    1670          shell.interp.runcommand("""if 1:
    1671              import sys as _sys
    1672              _sys.argv = {!r}
    1673              del _sys
    1674              \n""".format(sys.argv))
    1675          if cmd:
    1676              shell.interp.execsource(cmd)
    1677          elif script:
    1678              shell.interp.prepend_syspath(script)
    1679              shell.interp.execfile(script)
    1680      elif shell:
    1681          # If there is a shell window and no cmd or script in progress,
    1682          # check for problematic issues and print warning message(s) in
    1683          # the IDLE shell window; this is less intrusive than always
    1684          # opening a separate window.
    1685  
    1686          # Warn if the "Prefer tabs when opening documents" system
    1687          # preference is set to "Always".
    1688          prefer_tabs_preference_warning = macosx.preferTabsPreferenceWarning()
    1689          if prefer_tabs_preference_warning:
    1690              shell.show_warning(prefer_tabs_preference_warning)
    1691  
    1692      while flist.inversedict:  # keep IDLE running while files are open.
    1693          root.mainloop()
    1694      root.destroy()
    1695      capture_warnings(False)
    1696  
    1697  
    1698  if __name__ == "__main__":
    1699      main()
    1700  
    1701  capture_warnings(False)  # Make sure turned off; see issue 18081