0001 # changes by dscherer@cmu.edu 0002 # - IOBinding.open() replaces the current window with the opened file, 0003 # if the current window is both unmodified and unnamed 0004 # - IOBinding.loadfile() interprets Windows, UNIX, and Macintosh 0005 # end-of-line conventions, instead of relying on the standard library, 0006 # which will only understand the local convention. 0007 0008 import os 0009 import types 0010 import sys 0011 import codecs 0012 import tempfile 0013 import tkFileDialog 0014 import tkMessageBox 0015 import re 0016 from Tkinter import * 0017 from SimpleDialog import SimpleDialog 0018 0019 from configHandler import idleConf 0020 0021 try: 0022 from codecs import BOM_UTF8 0023 except ImportError: 0024 # only available since Python 2.3 0025 BOM_UTF8 = '\xef\xbb\xbf' 0026 0027 # Try setting the locale, so that we can find out 0028 # what encoding to use 0029 try: 0030 import locale 0031 locale.setlocale(locale.LC_CTYPE, "") 0032 except (ImportError, locale.Error): 0033 pass 0034 0035 encoding = "ascii" 0036 if sys.platform == 'win32': 0037 # On Windows, we could use "mbcs". However, to give the user 0038 # a portable encoding name, we need to find the code page 0039 try: 0040 encoding = locale.getdefaultlocale()[1] 0041 codecs.lookup(encoding) 0042 except LookupError: 0043 pass 0044 else: 0045 try: 0046 # Different things can fail here: the locale module may not be 0047 # loaded, it may not offer nl_langinfo, or CODESET, or the 0048 # resulting codeset may be unknown to Python. We ignore all 0049 # these problems, falling back to ASCII 0050 encoding = locale.nl_langinfo(locale.CODESET) 0051 if encoding is None or encoding is '': 0052 # situation occurs on Mac OS X 0053 encoding = 'ascii' 0054 codecs.lookup(encoding) 0055 except (NameError, AttributeError, LookupError): 0056 # Try getdefaultlocale well: it parses environment variables, 0057 # which may give a clue. Unfortunately, getdefaultlocale has 0058 # bugs that can cause ValueError. 0059 try: 0060 encoding = locale.getdefaultlocale()[1] 0061 if encoding is None or encoding is '': 0062 # situation occurs on Mac OS X 0063 encoding = 'ascii' 0064 codecs.lookup(encoding) 0065 except (ValueError, LookupError): 0066 pass 0067 0068 encoding = encoding.lower() 0069 0070 coding_re = re.compile("coding[:=]\s*([-\w_.]+)") 0071 0072 class EncodingMessage(SimpleDialog): 0073 "Inform user that an encoding declaration is needed." 0074 def __init__(self, master, enc): 0075 self.should_edit = False 0076 0077 self.root = top = Toplevel(master) 0078 top.bind("<Return>", self.return_event) 0079 top.bind("<Escape>", self.do_ok) 0080 top.protocol("WM_DELETE_WINDOW", self.wm_delete_window) 0081 top.wm_title("I/O Warning") 0082 top.wm_iconname("I/O Warning") 0083 self.top = top 0084 0085 l1 = Label(top, 0086 text="Non-ASCII found, yet no encoding declared. Add a line like") 0087 l1.pack(side=TOP, anchor=W) 0088 l2 = Entry(top, font="courier") 0089 l2.insert(0, "# -*- coding: %s -*-" % enc) 0090 # For some reason, the text is not selectable anymore if the 0091 # widget is disabled. 0092 # l2['state'] = DISABLED 0093 l2.pack(side=TOP, anchor = W, fill=X) 0094 l3 = Label(top, text="to your file\n" 0095 "Choose OK to save this file as %s\n" 0096 "Edit your general options to silence this warning" % enc) 0097 l3.pack(side=TOP, anchor = W) 0098 0099 buttons = Frame(top) 0100 buttons.pack(side=TOP, fill=X) 0101 # Both return and cancel mean the same thing: do nothing 0102 self.default = self.cancel = 0 0103 b1 = Button(buttons, text="Ok", default="active", 0104 command=self.do_ok) 0105 b1.pack(side=LEFT, fill=BOTH, expand=1) 0106 b2 = Button(buttons, text="Edit my file", 0107 command=self.do_edit) 0108 b2.pack(side=LEFT, fill=BOTH, expand=1) 0109 0110 self._set_transient(master) 0111 0112 def do_ok(self): 0113 self.done(0) 0114 0115 def do_edit(self): 0116 self.done(1) 0117 0118 def coding_spec(str): 0119 """Return the encoding declaration according to PEP 263. 0120 0121 Raise LookupError if the encoding is declared but unknown. 0122 """ 0123 # Only consider the first two lines 0124 str = str.split("\n")[:2] 0125 str = "\n".join(str) 0126 0127 match = coding_re.search(str) 0128 if not match: 0129 return None 0130 name = match.group(1) 0131 # Check whether the encoding is known 0132 import codecs 0133 try: 0134 codecs.lookup(name) 0135 except LookupError: 0136 # The standard encoding error does not indicate the encoding 0137 raise LookupError, "Unknown encoding "+name 0138 return name 0139 0140 0141 class IOBinding: 0142 0143 def __init__(self, editwin): 0144 self.editwin = editwin 0145 self.text = editwin.text 0146 self.__id_open = self.text.bind("<<open-window-from-file>>", self.open) 0147 self.__id_save = self.text.bind("<<save-window>>", self.save) 0148 self.__id_saveas = self.text.bind("<<save-window-as-file>>", 0149 self.save_as) 0150 self.__id_savecopy = self.text.bind("<<save-copy-of-window-as-file>>", 0151 self.save_a_copy) 0152 self.fileencoding = None 0153 self.__id_print = self.text.bind("<<print-window>>", self.print_window) 0154 0155 def close(self): 0156 # Undo command bindings 0157 self.text.unbind("<<open-window-from-file>>", self.__id_open) 0158 self.text.unbind("<<save-window>>", self.__id_save) 0159 self.text.unbind("<<save-window-as-file>>",self.__id_saveas) 0160 self.text.unbind("<<save-copy-of-window-as-file>>", self.__id_savecopy) 0161 self.text.unbind("<<print-window>>", self.__id_print) 0162 # Break cycles 0163 self.editwin = None 0164 self.text = None 0165 self.filename_change_hook = None 0166 0167 def get_saved(self): 0168 return self.editwin.get_saved() 0169 0170 def set_saved(self, flag): 0171 self.editwin.set_saved(flag) 0172 0173 def reset_undo(self): 0174 self.editwin.reset_undo() 0175 0176 filename_change_hook = None 0177 0178 def set_filename_change_hook(self, hook): 0179 self.filename_change_hook = hook 0180 0181 filename = None 0182 dirname = None 0183 0184 def set_filename(self, filename): 0185 if filename and os.path.isdir(filename): 0186 self.filename = None 0187 self.dirname = filename 0188 else: 0189 self.filename = filename 0190 self.dirname = None 0191 self.set_saved(1) 0192 if self.filename_change_hook: 0193 self.filename_change_hook() 0194 0195 def open(self, event=None, editFile=None): 0196 if self.editwin.flist: 0197 if not editFile: 0198 filename = self.askopenfile() 0199 else: 0200 filename=editFile 0201 if filename: 0202 # If the current window has no filename and hasn't been 0203 # modified, we replace its contents (no loss). Otherwise 0204 # we open a new window. But we won't replace the 0205 # shell window (which has an interp(reter) attribute), which 0206 # gets set to "not modified" at every new prompt. 0207 try: 0208 interp = self.editwin.interp 0209 except: 0210 interp = None 0211 if not self.filename and self.get_saved() and not interp: 0212 self.editwin.flist.open(filename, self.loadfile) 0213 else: 0214 self.editwin.flist.open(filename) 0215 else: 0216 self.text.focus_set() 0217 return "break" 0218 # 0219 # Code for use outside IDLE: 0220 if self.get_saved(): 0221 reply = self.maybesave() 0222 if reply == "cancel": 0223 self.text.focus_set() 0224 return "break" 0225 if not editFile: 0226 filename = self.askopenfile() 0227 else: 0228 filename=editFile 0229 if filename: 0230 self.loadfile(filename) 0231 else: 0232 self.text.focus_set() 0233 return "break" 0234 0235 eol = r"(\r\n)|\n|\r" # \r\n (Windows), \n (UNIX), or \r (Mac) 0236 eol_re = re.compile(eol) 0237 eol_convention = os.linesep # Default 0238 0239 def loadfile(self, filename): 0240 try: 0241 # open the file in binary mode so that we can handle 0242 # end-of-line convention ourselves. 0243 f = open(filename,'rb') 0244 chars = f.read() 0245 f.close() 0246 except IOError, msg: 0247 tkMessageBox.showerror("I/O Error", str(msg), master=self.text) 0248 return False 0249 0250 chars = self.decode(chars) 0251 # We now convert all end-of-lines to '\n's 0252 firsteol = self.eol_re.search(chars) 0253 if firsteol: 0254 self.eol_convention = firsteol.group(0) 0255 if isinstance(self.eol_convention, unicode): 0256 # Make sure it is an ASCII string 0257 self.eol_convention = self.eol_convention.encode("ascii") 0258 chars = self.eol_re.sub(r"\n", chars) 0259 0260 self.text.delete("1.0", "end") 0261 self.set_filename(None) 0262 self.text.insert("1.0", chars) 0263 self.reset_undo() 0264 self.set_filename(filename) 0265 self.text.mark_set("insert", "1.0") 0266 self.text.see("insert") 0267 self.updaterecentfileslist(filename) 0268 return True 0269 0270 def decode(self, chars): 0271 """Create a Unicode string 0272 0273 If that fails, let Tcl try its best 0274 """ 0275 # Check presence of a UTF-8 signature first 0276 if chars.startswith(BOM_UTF8): 0277 try: 0278 chars = chars[3:].decode("utf-8") 0279 except UnicodeError: 0280 # has UTF-8 signature, but fails to decode... 0281 return chars 0282 else: 0283 # Indicates that this file originally had a BOM 0284 self.fileencoding = BOM_UTF8 0285 return chars 0286 # Next look for coding specification 0287 try: 0288 enc = coding_spec(chars) 0289 except LookupError, name: 0290 tkMessageBox.showerror( 0291 title="Error loading the file", 0292 message="The encoding '%s' is not known to this Python "\ 0293 "installation. The file may not display correctly" % name, 0294 master = self.text) 0295 enc = None 0296 if enc: 0297 try: 0298 return unicode(chars, enc) 0299 except UnicodeError: 0300 pass 0301 # If it is ASCII, we need not to record anything 0302 try: 0303 return unicode(chars, 'ascii') 0304 except UnicodeError: 0305 pass 0306 # Finally, try the locale's encoding. This is deprecated; 0307 # the user should declare a non-ASCII encoding 0308 try: 0309 chars = unicode(chars, encoding) 0310 self.fileencoding = encoding 0311 except UnicodeError: 0312 pass 0313 return chars 0314 0315 def maybesave(self): 0316 if self.get_saved(): 0317 return "yes" 0318 message = "Do you want to save %s before closing?" % ( 0319 self.filename or "this untitled document") 0320 m = tkMessageBox.Message( 0321 title="Save On Close", 0322 message=message, 0323 icon=tkMessageBox.QUESTION, 0324 type=tkMessageBox.YESNOCANCEL, 0325 master=self.text) 0326 reply = m.show() 0327 if reply == "yes": 0328 self.save(None) 0329 if not self.get_saved(): 0330 reply = "cancel" 0331 self.text.focus_set() 0332 return reply 0333 0334 def save(self, event): 0335 if not self.filename: 0336 self.save_as(event) 0337 else: 0338 if self.writefile(self.filename): 0339 self.set_saved(1) 0340 try: 0341 self.editwin.store_file_breaks() 0342 except AttributeError: # may be a PyShell 0343 pass 0344 self.text.focus_set() 0345 return "break" 0346 0347 def save_as(self, event): 0348 filename = self.asksavefile() 0349 if filename: 0350 if self.writefile(filename): 0351 self.set_filename(filename) 0352 self.set_saved(1) 0353 try: 0354 self.editwin.store_file_breaks() 0355 except AttributeError: 0356 pass 0357 self.text.focus_set() 0358 self.updaterecentfileslist(filename) 0359 return "break" 0360 0361 def save_a_copy(self, event): 0362 filename = self.asksavefile() 0363 if filename: 0364 self.writefile(filename) 0365 self.text.focus_set() 0366 self.updaterecentfileslist(filename) 0367 return "break" 0368 0369 def writefile(self, filename): 0370 self.fixlastline() 0371 chars = self.encode(self.text.get("1.0", "end-1c")) 0372 if self.eol_convention != "\n": 0373 chars = chars.replace("\n", self.eol_convention) 0374 try: 0375 f = open(filename, "wb") 0376 f.write(chars) 0377 f.close() 0378 return True 0379 except IOError, msg: 0380 tkMessageBox.showerror("I/O Error", str(msg), 0381 master=self.text) 0382 return False 0383 0384 def encode(self, chars): 0385 if isinstance(chars, types.StringType): 0386 # This is either plain ASCII, or Tk was returning mixed-encoding 0387 # text to us. Don't try to guess further. 0388 return chars 0389 # See whether there is anything non-ASCII in it. 0390 # If not, no need to figure out the encoding. 0391 try: 0392 return chars.encode('ascii') 0393 except UnicodeError: 0394 pass 0395 # If there is an encoding declared, try this first. 0396 try: 0397 enc = coding_spec(chars) 0398 failed = None 0399 except LookupError, msg: 0400 failed = msg 0401 enc = None 0402 if enc: 0403 try: 0404 return chars.encode(enc) 0405 except UnicodeError: 0406 failed = "Invalid encoding '%s'" % enc 0407 if failed: 0408 tkMessageBox.showerror( 0409 "I/O Error", 0410 "%s. Saving as UTF-8" % failed, 0411 master = self.text) 0412 # If there was a UTF-8 signature, use that. This should not fail 0413 if self.fileencoding == BOM_UTF8 or failed: 0414 return BOM_UTF8 + chars.encode("utf-8") 0415 # Try the original file encoding next, if any 0416 if self.fileencoding: 0417 try: 0418 return chars.encode(self.fileencoding) 0419 except UnicodeError: 0420 tkMessageBox.showerror( 0421 "I/O Error", 0422 "Cannot save this as '%s' anymore. Saving as UTF-8" \ 0423 % self.fileencoding, 0424 master = self.text) 0425 return BOM_UTF8 + chars.encode("utf-8") 0426 # Nothing was declared, and we had not determined an encoding 0427 # on loading. Recommend an encoding line. 0428 config_encoding = idleConf.GetOption("main","EditorWindow", 0429 "encoding") 0430 if config_encoding == 'utf-8': 0431 # User has requested that we save files as UTF-8 0432 return BOM_UTF8 + chars.encode("utf-8") 0433 ask_user = True 0434 try: 0435 chars = chars.encode(encoding) 0436 enc = encoding 0437 if config_encoding == 'locale': 0438 ask_user = False 0439 except UnicodeError: 0440 chars = BOM_UTF8 + chars.encode("utf-8") 0441 enc = "utf-8" 0442 if not ask_user: 0443 return chars 0444 dialog = EncodingMessage(self.editwin.top, enc) 0445 dialog.go() 0446 if dialog.num == 1: 0447 # User asked us to edit the file 0448 encline = "# -*- coding: %s -*-\n" % enc 0449 firstline = self.text.get("1.0", "2.0") 0450 if firstline.startswith("#!"): 0451 # Insert encoding after #! line 0452 self.text.insert("2.0", encline) 0453 else: 0454 self.text.insert("1.0", encline) 0455 return self.encode(self.text.get("1.0", "end-1c")) 0456 return chars 0457 0458 def fixlastline(self): 0459 c = self.text.get("end-2c") 0460 if c != '\n': 0461 self.text.insert("end-1c", "\n") 0462 0463 def print_window(self, event): 0464 tempfilename = None 0465 saved = self.get_saved() 0466 if saved: 0467 filename = self.filename 0468 # shell undo is reset after every prompt, looks saved, probably isn't 0469 if not saved or filename is None: 0470 # XXX KBK 08Jun03 Wouldn't it be better to ask the user to save? 0471 (tfd, tempfilename) = tempfile.mkstemp(prefix='IDLE_tmp_') 0472 filename = tempfilename 0473 os.close(tfd) 0474 if not self.writefile(tempfilename): 0475 os.unlink(tempfilename) 0476 return "break" 0477 platform=os.name 0478 printPlatform=1 0479 if platform == 'posix': #posix platform 0480 command = idleConf.GetOption('main','General', 0481 'print-command-posix') 0482 command = command + " 2>&1" 0483 elif platform == 'nt': #win32 platform 0484 command = idleConf.GetOption('main','General','print-command-win') 0485 else: #no printing for this platform 0486 printPlatform=0 0487 if printPlatform: #we can try to print for this platform 0488 command = command % filename 0489 pipe = os.popen(command, "r") 0490 # things can get ugly on NT if there is no printer available. 0491 output = pipe.read().strip() 0492 status = pipe.close() 0493 if status: 0494 output = "Printing failed (exit status 0x%x)\n" % \ 0495 status + output 0496 if output: 0497 output = "Printing command: %s\n" % repr(command) + output 0498 tkMessageBox.showerror("Print status", output, master=self.text) 0499 else: #no printing for this platform 0500 message="Printing is not enabled for this platform: %s" % platform 0501 tkMessageBox.showinfo("Print status", message, master=self.text) 0502 if tempfilename: 0503 os.unlink(tempfilename) 0504 return "break" 0505 0506 opendialog = None 0507 savedialog = None 0508 0509 filetypes = [ 0510 ("Python and text files", "*.py *.pyw *.txt", "TEXT"), 0511 ("All text files", "*", "TEXT"), 0512 ("All files", "*"), 0513 ] 0514 0515 def askopenfile(self): 0516 dir, base = self.defaultfilename("open") 0517 if not self.opendialog: 0518 self.opendialog = tkFileDialog.Open(master=self.text, 0519 filetypes=self.filetypes) 0520 return self.opendialog.show(initialdir=dir, initialfile=base) 0521 0522 def defaultfilename(self, mode="open"): 0523 if self.filename: 0524 return os.path.split(self.filename) 0525 elif self.dirname: 0526 return self.dirname, "" 0527 else: 0528 try: 0529 pwd = os.getcwd() 0530 except os.error: 0531 pwd = "" 0532 return pwd, "" 0533 0534 def asksavefile(self): 0535 dir, base = self.defaultfilename("save") 0536 if not self.savedialog: 0537 self.savedialog = tkFileDialog.SaveAs(master=self.text, 0538 filetypes=self.filetypes) 0539 return self.savedialog.show(initialdir=dir, initialfile=base) 0540 0541 def updaterecentfileslist(self,filename): 0542 "Update recent file list on all editor windows" 0543 self.editwin.update_recent_files_list(filename) 0544 0545 def test(): 0546 root = Tk() 0547 class MyEditWin: 0548 def __init__(self, text): 0549 self.text = text 0550 self.flist = None 0551 self.text.bind("<Control-o>", self.open) 0552 self.text.bind("<Control-s>", self.save) 0553 self.text.bind("<Alt-s>", self.save_as) 0554 self.text.bind("<Alt-z>", self.save_a_copy) 0555 def get_saved(self): return 0 0556 def set_saved(self, flag): pass 0557 def reset_undo(self): pass 0558 def open(self, event): 0559 self.text.event_generate("<<open-window-from-file>>") 0560 def save(self, event): 0561 self.text.event_generate("<<save-window>>") 0562 def save_as(self, event): 0563 self.text.event_generate("<<save-window-as-file>>") 0564 def save_a_copy(self, event): 0565 self.text.event_generate("<<save-copy-of-window-as-file>>") 0566 text = Text(root) 0567 text.pack() 0568 text.focus_set() 0569 editwin = MyEditWin(text) 0570 io = IOBinding(editwin) 0571 root.mainloop() 0572 0573 if __name__ == "__main__": 0574 test() 0575
Generated by PyXR 0.9.4