diff options
Diffstat (limited to 'Lib/idlelib/PyShell.py')
| -rwxr-xr-x[-rw-r--r--] | Lib/idlelib/PyShell.py | 197 | 
1 files changed, 136 insertions, 61 deletions
| diff --git a/Lib/idlelib/PyShell.py b/Lib/idlelib/PyShell.py index 865472e..2e5ebb2 100644..100755 --- a/Lib/idlelib/PyShell.py +++ b/Lib/idlelib/PyShell.py @@ -16,6 +16,7 @@ import io  import linecache  from code import InteractiveInterpreter +from platform import python_version, system  try:      from tkinter import * @@ -44,35 +45,55 @@ PORT = 0  # someday pass in host, port for remote debug capability  # internal warnings to the console.  ScriptBinding.check_syntax() will  # temporarily redirect the stream to the shell window to display warnings when  # checking user's code. -global warning_stream -warning_stream = sys.__stderr__ -try: -    import warnings -except ImportError: -    pass -else: -    def idle_showwarning(message, category, filename, lineno, -                         file=None, line=None): -        if file is None: -            file = warning_stream -        try: -            file.write(warnings.formatwarning(message, category, filename, -                                              lineno, line=line)) -        except IOError: -            pass  ## file (probably __stderr__) is invalid, warning dropped. -    warnings.showwarning = idle_showwarning -    def idle_formatwarning(message, category, filename, lineno, line=None): -        """Format warnings the IDLE way""" -        s = "\nWarning (from warnings module):\n" -        s += '  File \"%s\", line %s\n' % (filename, lineno) -        if line is None: -            line = linecache.getline(filename, lineno) -        line = line.strip() -        if line: -            s += "    %s\n" % line -        s += "%s: %s\n>>> " % (category.__name__, message) -        return s -    warnings.formatwarning = idle_formatwarning +warning_stream = sys.__stderr__  # None, at least on Windows, if no console. +import warnings + +def idle_formatwarning(message, category, filename, lineno, line=None): +    """Format warnings the IDLE way.""" + +    s = "\nWarning (from warnings module):\n" +    s += '  File \"%s\", line %s\n' % (filename, lineno) +    if line is None: +        line = linecache.getline(filename, lineno) +    line = line.strip() +    if line: +        s += "    %s\n" % line +    s += "%s: %s\n" % (category.__name__, message) +    return s + +def idle_showwarning( +        message, category, filename, lineno, file=None, line=None): +    """Show Idle-format warning (after replacing warnings.showwarning). + +    The differences are the formatter called, the file=None replacement, +    which can be None, the capture of the consequence AttributeError, +    and the output of a hard-coded prompt. +    """ +    if file is None: +        file = warning_stream +    try: +        file.write(idle_formatwarning( +                message, category, filename, lineno, line=line)) +        file.write(">>> ") +    except (AttributeError, OSError): +        pass  # if file (probably __stderr__) is invalid, skip warning. + +_warnings_showwarning = None + +def capture_warnings(capture): +    "Replace warning.showwarning with idle_showwarning, or reverse." + +    global _warnings_showwarning +    if capture: +        if _warnings_showwarning is None: +            _warnings_showwarning = warnings.showwarning +            warnings.showwarning = idle_showwarning +    else: +        if _warnings_showwarning is not None: +            warnings.showwarning = _warnings_showwarning +            _warnings_showwarning = None + +capture_warnings(True)  def extended_linecache_checkcache(filename=None,                                    orig_checkcache=linecache.checkcache): @@ -110,12 +131,13 @@ class PyShellEditorWindow(EditorWindow):          self.breakpointPath = os.path.join(idleConf.GetUserCfgDir(),                                             'breakpoints.lst')          # whenever a file is changed, restore breakpoints -        if self.io.filename: self.restore_file_breaks()          def filename_changed_hook(old_hook=self.io.filename_change_hook,                                    self=self):              self.restore_file_breaks()              old_hook()          self.io.set_filename_change_hook(filename_changed_hook) +        if self.io.filename: +            self.restore_file_breaks()      rmenu_specs = [          ("Cut", "<<cut>>", "rmenu_check_cut"), @@ -211,7 +233,7 @@ class PyShellEditorWindow(EditorWindow):          try:              with open(self.breakpointPath, "r") as fp:                  lines = fp.readlines() -        except IOError: +        except OSError:              lines = []          try:              with open(self.breakpointPath, "w") as new_file: @@ -222,7 +244,7 @@ class PyShellEditorWindow(EditorWindow):                  breaks = self.breakpoints                  if breaks:                      new_file.write(filename + '=' + str(breaks) + '\n') -        except IOError as err: +        except OSError as err:              if not getattr(self.root, "breakpoint_error_displayed", False):                  self.root.breakpoint_error_displayed = True                  tkMessageBox.showerror(title='IDLE Error', @@ -232,6 +254,9 @@ class PyShellEditorWindow(EditorWindow):      def restore_file_breaks(self):          self.text.update()   # this enables setting "BREAK" tags to be visible +        if self.io is None: +            # can happen if IDLE closes due to the .update() call +            return          filename = self.io.filename          if filename is None:              return @@ -362,6 +387,7 @@ class ModifiedInterpreter(InteractiveInterpreter):          self.port = PORT          self.original_compiler_flags = self.compile.compiler.flags +    _afterid = None      rpcclt = None      rpcsubproc = None @@ -453,6 +479,7 @@ class ModifiedInterpreter(InteractiveInterpreter):              self.display_no_subprocess_error()              return None          self.transfer_path(with_cwd=with_cwd) +        console.stop_readline()          # annotate restart in shell window and mark it          console.text.delete("iomark", "end-1c")          if was_executing: @@ -480,6 +507,12 @@ class ModifiedInterpreter(InteractiveInterpreter):          threading.Thread(target=self.__request_interrupt).start()      def kill_subprocess(self): +        if self._afterid is not None: +            self.tkconsole.text.after_cancel(self._afterid) +        try: +            self.rpcclt.listening_sock.close() +        except AttributeError:  # no socket +            pass          try:              self.rpcclt.close()          except AttributeError:  # no socket @@ -522,7 +555,7 @@ class ModifiedInterpreter(InteractiveInterpreter):              return          try:              response = clt.pollresponse(self.active_seq, wait=0.05) -        except (EOFError, IOError, KeyboardInterrupt): +        except (EOFError, OSError, KeyboardInterrupt):              # lost connection or subprocess terminated itself, restart              # [the KBI is from rpc.SocketIO.handle_EOF()]              if self.tkconsole.closing: @@ -551,8 +584,8 @@ class ModifiedInterpreter(InteractiveInterpreter):                  pass          # Reschedule myself          if not self.tkconsole.closing: -            self.tkconsole.text.after(self.tkconsole.pollinterval, -                                      self.poll_subprocess) +            self._afterid = self.tkconsole.text.after( +                self.tkconsole.pollinterval, self.poll_subprocess)      debugger = None @@ -795,7 +828,7 @@ class ModifiedInterpreter(InteractiveInterpreter):  class PyShell(OutputWindow): -    shell_title = "Python Shell" +    shell_title = "Python " + python_version() + " Shell"      # Override classes      ColorDelegator = ModifiedColorDelegator @@ -812,7 +845,6 @@ class PyShell(OutputWindow):      ]      if macosxSupport.runningAsOSXApp(): -        del menu_specs[-3]          menu_specs[-2] = ("windows", "_Window") @@ -848,8 +880,6 @@ class PyShell(OutputWindow):          text.bind("<<open-stack-viewer>>", self.open_stack_viewer)          text.bind("<<toggle-debugger>>", self.toggle_debugger)          text.bind("<<toggle-jit-stack-viewer>>", self.toggle_jit_stack_viewer) -        self.color = color = self.ColorDelegator() -        self.per.insertfilter(color)          if use_subprocess:              text.bind("<<view-restart>>", self.view_restart_mark)              text.bind("<<restart-shell>>", self.restart_shell) @@ -887,6 +917,7 @@ class PyShell(OutputWindow):      canceled = False      endoffile = False      closing = False +    _stop_readline_flag = False      def set_warning_stream(self, stream):          global warning_stream @@ -962,14 +993,9 @@ class PyShell(OutputWindow):                  parent=self.text)              if response is False:                  return "cancel" -        if self.reading: -            self.top.quit() +        self.stop_readline()          self.canceled = True          self.closing = True -        # Wait for poll_subprocess() rescheduling to stop -        self.text.after(2 * self.pollinterval, self.close2) - -    def close2(self):          return EditorWindow.close(self)      def _close(self): @@ -1009,6 +1035,8 @@ class PyShell(OutputWindow):                  return False          else:              nosub = "==== No Subprocess ====" +            sys.displayhook = rpc.displayhook +          self.write("Python %s on %s\n%s\n%s" %                     (sys.version, sys.platform, self.COPYRIGHT, nosub))          self.showprompt() @@ -1016,6 +1044,12 @@ class PyShell(OutputWindow):          tkinter._default_root = None # 03Jan04 KBK What's this?          return True +    def stop_readline(self): +        if not self.reading:  # no nested mainloop to exit. +            return +        self._stop_readline_flag = True +        self.top.quit() +      def readline(self):          save = self.reading          try: @@ -1023,6 +1057,9 @@ class PyShell(OutputWindow):              self.top.mainloop()  # nested mainloop()          finally:              self.reading = save +        if self._stop_readline_flag: +            self._stop_readline_flag = False +            return ""          line = self.text.get("iomark", "end-1c")          if len(line) == 0:  # may be EOF if we quit our mainloop with Ctrl-C              line = "\n" @@ -1224,13 +1261,23 @@ class PyShell(OutputWindow):      def resetoutput(self):          source = self.text.get("iomark", "end-1c")          if self.history: -            self.history.history_store(source) +            self.history.store(source)          if self.text.get("end-2c") != "\n":              self.text.insert("end-1c", "\n")          self.text.mark_set("iomark", "end-1c")          self.set_line_and_column()      def write(self, s, tags=()): +        if isinstance(s, str) and len(s) and max(s) > '\uffff': +            # Tk doesn't support outputting non-BMP characters +            # Let's assume what printed string is not very long, +            # find first non-BMP character and construct informative +            # UnicodeEncodeError exception. +            for start, char in enumerate(s): +                if char > '\uffff': +                    break +            raise UnicodeEncodeError("UCS-2", char, start, start+1, +                                     'Non-BMP character not supported in Tk')          try:              self.text.mark_gravity("iomark", "right")              count = OutputWindow.write(self, s, tags, "iomark") @@ -1284,8 +1331,11 @@ class PseudoOutputFile(PseudoFile):      def write(self, s):          if self.closed:              raise ValueError("write to closed file") -        if not isinstance(s, str): -            raise TypeError('must be str, not ' + type(s).__name__) +        if type(s) is not str: +            if not isinstance(s, str): +                raise TypeError('must be str, not ' + type(s).__name__) +            # See issue #19481 +            s = str.__str__(s)          return self.shell.write(s, self.tags) @@ -1331,9 +1381,15 @@ class PseudoInputFile(PseudoFile):          line = self._line_buffer or self.shell.readline()          if size < 0:              size = len(line) +        eol = line.find('\n', 0, size) +        if eol >= 0: +            size = eol + 1          self._line_buffer = line[size:]          return line[:size] +    def close(self): +        self.shell.close() +  usage_msg = """\ @@ -1391,8 +1447,9 @@ echo "import sys; print(sys.argv)" | idle - "foobar"  def main():      global flist, root, use_subprocess +    capture_warnings(True)      use_subprocess = True -    enable_shell = True +    enable_shell = False      enable_edit = False      debug = False      cmd = None @@ -1413,7 +1470,6 @@ def main():              enable_shell = True          if o == '-e':              enable_edit = True -            enable_shell = False          if o == '-h':              sys.stdout.write(usage_msg)              sys.exit() @@ -1464,9 +1520,22 @@ def main():      edit_start = idleConf.GetOption('main', 'General',                                      'editor-on-startup', type='bool')      enable_edit = enable_edit or edit_start +    enable_shell = enable_shell or not enable_edit      # start editor and/or shell windows:      root = Tk(className="Idle") +    # set application icon +    icondir = os.path.join(os.path.dirname(__file__), 'Icons') +    if system() == 'Windows': +        iconfile = os.path.join(icondir, 'idle.ico') +        root.wm_iconbitmap(default=iconfile) +    elif TkVersion >= 8.5: +        ext = '.png' if TkVersion >= 8.6 else '.gif' +        iconfiles = [os.path.join(icondir, 'idle_%d%s' % (size, ext)) +                     for size in (16, 32, 48)] +        icons = [PhotoImage(file=iconfile) for iconfile in iconfiles] +        root.wm_iconphoto(True, *icons) +      fixwordbreaks(root)      root.withdraw()      flist = PyShellFileList(root) @@ -1480,20 +1549,22 @@ def main():                      args.remove(filename)              if not args:                  flist.new() +      if enable_shell:          shell = flist.open_shell()          if not shell:              return # couldn't open shell -          if macosxSupport.runningAsOSXApp() and flist.dict:              # On OSX: when the user has double-clicked on a file that causes              # IDLE to be launched the shell window will open just in front of              # the file she wants to see. Lower the interpreter window when              # there are open files.              shell.top.lower() +    else: +        shell = flist.pyshell -    shell = flist.pyshell -    # handle remaining options: +    # Handle remaining options. If any of these are set, enable_shell +    # was set also, so shell must be true to reach here.      if debug:          shell.open_debugger()      if startup: @@ -1501,7 +1572,7 @@ def main():                     os.environ.get("PYTHONSTARTUP")          if filename and os.path.isfile(filename):              shell.interp.execfile(filename) -    if shell and cmd or script: +    if cmd or script:          shell.interp.runcommand("""if 1:              import sys as _sys              _sys.argv = %r @@ -1512,18 +1583,22 @@ def main():          elif script:              shell.interp.prepend_syspath(script)              shell.interp.execfile(script) - -    # Check for problematic OS X Tk versions and print a warning message -    # in the IDLE shell window; this is less intrusive than always opening -    # a separate window. -    tkversionwarning = macosxSupport.tkVersionWarning(root) -    if tkversionwarning: -        shell.interp.runcommand(''.join(("print('", tkversionwarning, "')"))) +    elif shell: +        # If there is a shell window and no cmd or script in progress, +        # check for problematic OS X Tk versions and print a warning +        # message in the IDLE shell window; this is less intrusive +        # than always opening a separate window. +        tkversionwarning = macosxSupport.tkVersionWarning(root) +        if tkversionwarning: +            shell.interp.runcommand("print('%s')" % tkversionwarning)      while flist.inversedict:  # keep IDLE running while files are open.          root.mainloop()      root.destroy() +    capture_warnings(False)  if __name__ == "__main__":      sys.modules['PyShell'] = sys.modules['__main__']      main() + +capture_warnings(False)  # Make sure turned off; see issue 18081 | 
