(root)/
Python-3.11.7/
Lib/
idlelib/
iomenu.py
       1  import io
       2  import os
       3  import shlex
       4  import sys
       5  import tempfile
       6  import tokenize
       7  
       8  from tkinter import filedialog
       9  from tkinter import messagebox
      10  from tkinter.simpledialog import askstring
      11  
      12  from idlelib.config import idleConf
      13  from idlelib.util import py_extensions
      14  
      15  py_extensions = ' '.join("*"+ext for ext in py_extensions)
      16  encoding = 'utf-8'
      17  errors = 'surrogatepass' if sys.platform == 'win32' else 'surrogateescape'
      18  
      19  
      20  class ESC[4;38;5;81mIOBinding:
      21  # One instance per editor Window so methods know which to save, close.
      22  # Open returns focus to self.editwin if aborted.
      23  # EditorWindow.open_module, others, belong here.
      24  
      25      def __init__(self, editwin):
      26          self.editwin = editwin
      27          self.text = editwin.text
      28          self.__id_open = self.text.bind("<<open-window-from-file>>", self.open)
      29          self.__id_save = self.text.bind("<<save-window>>", self.save)
      30          self.__id_saveas = self.text.bind("<<save-window-as-file>>",
      31                                            self.save_as)
      32          self.__id_savecopy = self.text.bind("<<save-copy-of-window-as-file>>",
      33                                              self.save_a_copy)
      34          self.fileencoding = 'utf-8'
      35          self.__id_print = self.text.bind("<<print-window>>", self.print_window)
      36  
      37      def close(self):
      38          # Undo command bindings
      39          self.text.unbind("<<open-window-from-file>>", self.__id_open)
      40          self.text.unbind("<<save-window>>", self.__id_save)
      41          self.text.unbind("<<save-window-as-file>>",self.__id_saveas)
      42          self.text.unbind("<<save-copy-of-window-as-file>>", self.__id_savecopy)
      43          self.text.unbind("<<print-window>>", self.__id_print)
      44          # Break cycles
      45          self.editwin = None
      46          self.text = None
      47          self.filename_change_hook = None
      48  
      49      def get_saved(self):
      50          return self.editwin.get_saved()
      51  
      52      def set_saved(self, flag):
      53          self.editwin.set_saved(flag)
      54  
      55      def reset_undo(self):
      56          self.editwin.reset_undo()
      57  
      58      filename_change_hook = None
      59  
      60      def set_filename_change_hook(self, hook):
      61          self.filename_change_hook = hook
      62  
      63      filename = None
      64      dirname = None
      65  
      66      def set_filename(self, filename):
      67          if filename and os.path.isdir(filename):
      68              self.filename = None
      69              self.dirname = filename
      70          else:
      71              self.filename = filename
      72              self.dirname = None
      73              self.set_saved(1)
      74              if self.filename_change_hook:
      75                  self.filename_change_hook()
      76  
      77      def open(self, event=None, editFile=None):
      78          flist = self.editwin.flist
      79          # Save in case parent window is closed (ie, during askopenfile()).
      80          if flist:
      81              if not editFile:
      82                  filename = self.askopenfile()
      83              else:
      84                  filename=editFile
      85              if filename:
      86                  # If editFile is valid and already open, flist.open will
      87                  # shift focus to its existing window.
      88                  # If the current window exists and is a fresh unnamed,
      89                  # unmodified editor window (not an interpreter shell),
      90                  # pass self.loadfile to flist.open so it will load the file
      91                  # in the current window (if the file is not already open)
      92                  # instead of a new window.
      93                  if (self.editwin and
      94                          not getattr(self.editwin, 'interp', None) and
      95                          not self.filename and
      96                          self.get_saved()):
      97                      flist.open(filename, self.loadfile)
      98                  else:
      99                      flist.open(filename)
     100              else:
     101                  if self.text:
     102                      self.text.focus_set()
     103              return "break"
     104  
     105          # Code for use outside IDLE:
     106          if self.get_saved():
     107              reply = self.maybesave()
     108              if reply == "cancel":
     109                  self.text.focus_set()
     110                  return "break"
     111          if not editFile:
     112              filename = self.askopenfile()
     113          else:
     114              filename=editFile
     115          if filename:
     116              self.loadfile(filename)
     117          else:
     118              self.text.focus_set()
     119          return "break"
     120  
     121      eol_convention = os.linesep  # default
     122  
     123      def loadfile(self, filename):
     124          try:
     125              try:
     126                  with tokenize.open(filename) as f:
     127                      chars = f.read()
     128                      fileencoding = f.encoding
     129                      eol_convention = f.newlines
     130                      converted = False
     131              except (UnicodeDecodeError, SyntaxError):
     132                  # Wait for the editor window to appear
     133                  self.editwin.text.update()
     134                  enc = askstring(
     135                      "Specify file encoding",
     136                      "The file's encoding is invalid for Python 3.x.\n"
     137                      "IDLE will convert it to UTF-8.\n"
     138                      "What is the current encoding of the file?",
     139                      initialvalue='utf-8',
     140                      parent=self.editwin.text)
     141                  with open(filename, encoding=enc) as f:
     142                      chars = f.read()
     143                      fileencoding = f.encoding
     144                      eol_convention = f.newlines
     145                      converted = True
     146          except OSError as err:
     147              messagebox.showerror("I/O Error", str(err), parent=self.text)
     148              return False
     149          except UnicodeDecodeError:
     150              messagebox.showerror("Decoding Error",
     151                                     "File %s\nFailed to Decode" % filename,
     152                                     parent=self.text)
     153              return False
     154  
     155          if not isinstance(eol_convention, str):
     156              # If the file does not contain line separators, it is None.
     157              # If the file contains mixed line separators, it is a tuple.
     158              if eol_convention is not None:
     159                  messagebox.showwarning("Mixed Newlines",
     160                                           "Mixed newlines detected.\n"
     161                                           "The file will be changed on save.",
     162                                           parent=self.text)
     163                  converted = True
     164              eol_convention = os.linesep  # default
     165  
     166          self.text.delete("1.0", "end")
     167          self.set_filename(None)
     168          self.fileencoding = fileencoding
     169          self.eol_convention = eol_convention
     170          self.text.insert("1.0", chars)
     171          self.reset_undo()
     172          self.set_filename(filename)
     173          if converted:
     174              # We need to save the conversion results first
     175              # before being able to execute the code
     176              self.set_saved(False)
     177          self.text.mark_set("insert", "1.0")
     178          self.text.yview("insert")
     179          self.updaterecentfileslist(filename)
     180          return True
     181  
     182      def maybesave(self):
     183          if self.get_saved():
     184              return "yes"
     185          message = "Do you want to save %s before closing?" % (
     186              self.filename or "this untitled document")
     187          confirm = messagebox.askyesnocancel(
     188                    title="Save On Close",
     189                    message=message,
     190                    default=messagebox.YES,
     191                    parent=self.text)
     192          if confirm:
     193              reply = "yes"
     194              self.save(None)
     195              if not self.get_saved():
     196                  reply = "cancel"
     197          elif confirm is None:
     198              reply = "cancel"
     199          else:
     200              reply = "no"
     201          self.text.focus_set()
     202          return reply
     203  
     204      def save(self, event):
     205          if not self.filename:
     206              self.save_as(event)
     207          else:
     208              if self.writefile(self.filename):
     209                  self.set_saved(True)
     210                  try:
     211                      self.editwin.store_file_breaks()
     212                  except AttributeError:  # may be a PyShell
     213                      pass
     214          self.text.focus_set()
     215          return "break"
     216  
     217      def save_as(self, event):
     218          filename = self.asksavefile()
     219          if filename:
     220              if self.writefile(filename):
     221                  self.set_filename(filename)
     222                  self.set_saved(1)
     223                  try:
     224                      self.editwin.store_file_breaks()
     225                  except AttributeError:
     226                      pass
     227          self.text.focus_set()
     228          self.updaterecentfileslist(filename)
     229          return "break"
     230  
     231      def save_a_copy(self, event):
     232          filename = self.asksavefile()
     233          if filename:
     234              self.writefile(filename)
     235          self.text.focus_set()
     236          self.updaterecentfileslist(filename)
     237          return "break"
     238  
     239      def writefile(self, filename):
     240          text = self.fixnewlines()
     241          chars = self.encode(text)
     242          try:
     243              with open(filename, "wb") as f:
     244                  f.write(chars)
     245                  f.flush()
     246                  os.fsync(f.fileno())
     247              return True
     248          except OSError as msg:
     249              messagebox.showerror("I/O Error", str(msg),
     250                                     parent=self.text)
     251              return False
     252  
     253      def fixnewlines(self):
     254          """Return text with os eols.
     255  
     256          Add prompts if shell else final \n if missing.
     257          """
     258  
     259          if hasattr(self.editwin, "interp"):  # Saving shell.
     260              text = self.editwin.get_prompt_text('1.0', self.text.index('end-1c'))
     261          else:
     262              if self.text.get("end-2c") != '\n':
     263                  self.text.insert("end-1c", "\n")  # Changes 'end-1c' value.
     264              text = self.text.get('1.0', "end-1c")
     265          if self.eol_convention != "\n":
     266              text = text.replace("\n", self.eol_convention)
     267          return text
     268  
     269      def encode(self, chars):
     270          if isinstance(chars, bytes):
     271              # This is either plain ASCII, or Tk was returning mixed-encoding
     272              # text to us. Don't try to guess further.
     273              return chars
     274          # Preserve a BOM that might have been present on opening
     275          if self.fileencoding == 'utf-8-sig':
     276              return chars.encode('utf-8-sig')
     277          # See whether there is anything non-ASCII in it.
     278          # If not, no need to figure out the encoding.
     279          try:
     280              return chars.encode('ascii')
     281          except UnicodeEncodeError:
     282              pass
     283          # Check if there is an encoding declared
     284          try:
     285              encoded = chars.encode('ascii', 'replace')
     286              enc, _ = tokenize.detect_encoding(io.BytesIO(encoded).readline)
     287              return chars.encode(enc)
     288          except SyntaxError as err:
     289              failed = str(err)
     290          except UnicodeEncodeError:
     291              failed = "Invalid encoding '%s'" % enc
     292          messagebox.showerror(
     293              "I/O Error",
     294              "%s.\nSaving as UTF-8" % failed,
     295              parent=self.text)
     296          # Fallback: save as UTF-8, with BOM - ignoring the incorrect
     297          # declared encoding
     298          return chars.encode('utf-8-sig')
     299  
     300      def print_window(self, event):
     301          confirm = messagebox.askokcancel(
     302                    title="Print",
     303                    message="Print to Default Printer",
     304                    default=messagebox.OK,
     305                    parent=self.text)
     306          if not confirm:
     307              self.text.focus_set()
     308              return "break"
     309          tempfilename = None
     310          saved = self.get_saved()
     311          if saved:
     312              filename = self.filename
     313          # shell undo is reset after every prompt, looks saved, probably isn't
     314          if not saved or filename is None:
     315              (tfd, tempfilename) = tempfile.mkstemp(prefix='IDLE_tmp_')
     316              filename = tempfilename
     317              os.close(tfd)
     318              if not self.writefile(tempfilename):
     319                  os.unlink(tempfilename)
     320                  return "break"
     321          platform = os.name
     322          printPlatform = True
     323          if platform == 'posix': #posix platform
     324              command = idleConf.GetOption('main','General',
     325                                           'print-command-posix')
     326              command = command + " 2>&1"
     327          elif platform == 'nt': #win32 platform
     328              command = idleConf.GetOption('main','General','print-command-win')
     329          else: #no printing for this platform
     330              printPlatform = False
     331          if printPlatform:  #we can try to print for this platform
     332              command = command % shlex.quote(filename)
     333              pipe = os.popen(command, "r")
     334              # things can get ugly on NT if there is no printer available.
     335              output = pipe.read().strip()
     336              status = pipe.close()
     337              if status:
     338                  output = "Printing failed (exit status 0x%x)\n" % \
     339                           status + output
     340              if output:
     341                  output = "Printing command: %s\n" % repr(command) + output
     342                  messagebox.showerror("Print status", output, parent=self.text)
     343          else:  #no printing for this platform
     344              message = "Printing is not enabled for this platform: %s" % platform
     345              messagebox.showinfo("Print status", message, parent=self.text)
     346          if tempfilename:
     347              os.unlink(tempfilename)
     348          return "break"
     349  
     350      opendialog = None
     351      savedialog = None
     352  
     353      filetypes = (
     354          ("Python files", py_extensions, "TEXT"),
     355          ("Text files", "*.txt", "TEXT"),
     356          ("All files", "*"),
     357          )
     358  
     359      defaultextension = '.py' if sys.platform == 'darwin' else ''
     360  
     361      def askopenfile(self):
     362          dir, base = self.defaultfilename("open")
     363          if not self.opendialog:
     364              self.opendialog = filedialog.Open(parent=self.text,
     365                                                  filetypes=self.filetypes)
     366          filename = self.opendialog.show(initialdir=dir, initialfile=base)
     367          return filename
     368  
     369      def defaultfilename(self, mode="open"):
     370          if self.filename:
     371              return os.path.split(self.filename)
     372          elif self.dirname:
     373              return self.dirname, ""
     374          else:
     375              try:
     376                  pwd = os.getcwd()
     377              except OSError:
     378                  pwd = ""
     379              return pwd, ""
     380  
     381      def asksavefile(self):
     382          dir, base = self.defaultfilename("save")
     383          if not self.savedialog:
     384              self.savedialog = filedialog.SaveAs(
     385                      parent=self.text,
     386                      filetypes=self.filetypes,
     387                      defaultextension=self.defaultextension)
     388          filename = self.savedialog.show(initialdir=dir, initialfile=base)
     389          return filename
     390  
     391      def updaterecentfileslist(self,filename):
     392          "Update recent file list on all editor windows"
     393          if self.editwin.flist:
     394              self.editwin.update_recent_files_list(filename)
     395  
     396  
     397  def _io_binding(parent):  # htest #
     398      from tkinter import Toplevel, Text
     399  
     400      top = Toplevel(parent)
     401      top.title("Test IOBinding")
     402      x, y = map(int, parent.geometry().split('+')[1:])
     403      top.geometry("+%d+%d" % (x, y + 175))
     404  
     405      class ESC[4;38;5;81mMyEditWin:
     406          def __init__(self, text):
     407              self.text = text
     408              self.flist = None
     409              self.text.bind("<Control-o>", self.open)
     410              self.text.bind('<Control-p>', self.print)
     411              self.text.bind("<Control-s>", self.save)
     412              self.text.bind("<Alt-s>", self.saveas)
     413              self.text.bind('<Control-c>', self.savecopy)
     414          def get_saved(self): return 0
     415          def set_saved(self, flag): pass
     416          def reset_undo(self): pass
     417          def open(self, event):
     418              self.text.event_generate("<<open-window-from-file>>")
     419          def print(self, event):
     420              self.text.event_generate("<<print-window>>")
     421          def save(self, event):
     422              self.text.event_generate("<<save-window>>")
     423          def saveas(self, event):
     424              self.text.event_generate("<<save-window-as-file>>")
     425          def savecopy(self, event):
     426              self.text.event_generate("<<save-copy-of-window-as-file>>")
     427  
     428      text = Text(top)
     429      text.pack()
     430      text.focus_set()
     431      editwin = MyEditWin(text)
     432      IOBinding(editwin)
     433  
     434  
     435  if __name__ == "__main__":
     436      from unittest import main
     437      main('idlelib.idle_test.test_iomenu', verbosity=2, exit=False)
     438  
     439      from idlelib.idle_test.htest import run
     440      run(_io_binding)