source: python/vendor/Python-2.7.6/Lib/idlelib/IOBinding.py

Last change on this file was 388, checked in by dmik, 11 years ago

python: Update vendor to 2.7.6.

  • Property svn:eol-style set to native
File size: 20.7 KB
Line 
1# changes by dscherer@cmu.edu
2# - IOBinding.open() replaces the current window with the opened file,
3# if the current window is both unmodified and unnamed
4# - IOBinding.loadfile() interprets Windows, UNIX, and Macintosh
5# end-of-line conventions, instead of relying on the standard library,
6# which will only understand the local convention.
7
8import os
9import types
10import pipes
11import sys
12import codecs
13import tempfile
14import tkFileDialog
15import tkMessageBox
16import re
17from Tkinter import *
18from SimpleDialog import SimpleDialog
19
20from idlelib.configHandler import idleConf
21
22try:
23 from codecs import BOM_UTF8
24except ImportError:
25 # only available since Python 2.3
26 BOM_UTF8 = '\xef\xbb\xbf'
27
28# Try setting the locale, so that we can find out
29# what encoding to use
30try:
31 import locale
32 locale.setlocale(locale.LC_CTYPE, "")
33except (ImportError, locale.Error):
34 pass
35
36# Encoding for file names
37filesystemencoding = sys.getfilesystemencoding()
38
39encoding = "ascii"
40if sys.platform == 'win32':
41 # On Windows, we could use "mbcs". However, to give the user
42 # a portable encoding name, we need to find the code page
43 try:
44 encoding = locale.getdefaultlocale()[1]
45 codecs.lookup(encoding)
46 except LookupError:
47 pass
48else:
49 try:
50 # Different things can fail here: the locale module may not be
51 # loaded, it may not offer nl_langinfo, or CODESET, or the
52 # resulting codeset may be unknown to Python. We ignore all
53 # these problems, falling back to ASCII
54 encoding = locale.nl_langinfo(locale.CODESET)
55 if encoding is None or encoding is '':
56 # situation occurs on Mac OS X
57 encoding = 'ascii'
58 codecs.lookup(encoding)
59 except (NameError, AttributeError, LookupError):
60 # Try getdefaultlocale well: it parses environment variables,
61 # which may give a clue. Unfortunately, getdefaultlocale has
62 # bugs that can cause ValueError.
63 try:
64 encoding = locale.getdefaultlocale()[1]
65 if encoding is None or encoding is '':
66 # situation occurs on Mac OS X
67 encoding = 'ascii'
68 codecs.lookup(encoding)
69 except (ValueError, LookupError):
70 pass
71
72encoding = encoding.lower()
73
74coding_re = re.compile(r'^[ \t\f]*#.*coding[:=][ \t]*([-\w.]+)')
75
76class EncodingMessage(SimpleDialog):
77 "Inform user that an encoding declaration is needed."
78 def __init__(self, master, enc):
79 self.should_edit = False
80
81 self.root = top = Toplevel(master)
82 top.bind("<Return>", self.return_event)
83 top.bind("<Escape>", self.do_ok)
84 top.protocol("WM_DELETE_WINDOW", self.wm_delete_window)
85 top.wm_title("I/O Warning")
86 top.wm_iconname("I/O Warning")
87 self.top = top
88
89 l1 = Label(top,
90 text="Non-ASCII found, yet no encoding declared. Add a line like")
91 l1.pack(side=TOP, anchor=W)
92 l2 = Entry(top, font="courier")
93 l2.insert(0, "# -*- coding: %s -*-" % enc)
94 # For some reason, the text is not selectable anymore if the
95 # widget is disabled.
96 # l2['state'] = DISABLED
97 l2.pack(side=TOP, anchor = W, fill=X)
98 l3 = Label(top, text="to your file\n"
99 "Choose OK to save this file as %s\n"
100 "Edit your general options to silence this warning" % enc)
101 l3.pack(side=TOP, anchor = W)
102
103 buttons = Frame(top)
104 buttons.pack(side=TOP, fill=X)
105 # Both return and cancel mean the same thing: do nothing
106 self.default = self.cancel = 0
107 b1 = Button(buttons, text="Ok", default="active",
108 command=self.do_ok)
109 b1.pack(side=LEFT, fill=BOTH, expand=1)
110 b2 = Button(buttons, text="Edit my file",
111 command=self.do_edit)
112 b2.pack(side=LEFT, fill=BOTH, expand=1)
113
114 self._set_transient(master)
115
116 def do_ok(self):
117 self.done(0)
118
119 def do_edit(self):
120 self.done(1)
121
122def coding_spec(str):
123 """Return the encoding declaration according to PEP 263.
124
125 Raise LookupError if the encoding is declared but unknown.
126 """
127 # Only consider the first two lines
128 lst = str.split("\n", 2)[:2]
129 for line in lst:
130 match = coding_re.match(line)
131 if match is not None:
132 break
133 else:
134 return None
135 name = match.group(1)
136 # Check whether the encoding is known
137 import codecs
138 try:
139 codecs.lookup(name)
140 except LookupError:
141 # The standard encoding error does not indicate the encoding
142 raise LookupError, "Unknown encoding "+name
143 return name
144
145
146class IOBinding:
147
148 def __init__(self, editwin):
149 self.editwin = editwin
150 self.text = editwin.text
151 self.__id_open = self.text.bind("<<open-window-from-file>>", self.open)
152 self.__id_save = self.text.bind("<<save-window>>", self.save)
153 self.__id_saveas = self.text.bind("<<save-window-as-file>>",
154 self.save_as)
155 self.__id_savecopy = self.text.bind("<<save-copy-of-window-as-file>>",
156 self.save_a_copy)
157 self.fileencoding = None
158 self.__id_print = self.text.bind("<<print-window>>", self.print_window)
159
160 def close(self):
161 # Undo command bindings
162 self.text.unbind("<<open-window-from-file>>", self.__id_open)
163 self.text.unbind("<<save-window>>", self.__id_save)
164 self.text.unbind("<<save-window-as-file>>",self.__id_saveas)
165 self.text.unbind("<<save-copy-of-window-as-file>>", self.__id_savecopy)
166 self.text.unbind("<<print-window>>", self.__id_print)
167 # Break cycles
168 self.editwin = None
169 self.text = None
170 self.filename_change_hook = None
171
172 def get_saved(self):
173 return self.editwin.get_saved()
174
175 def set_saved(self, flag):
176 self.editwin.set_saved(flag)
177
178 def reset_undo(self):
179 self.editwin.reset_undo()
180
181 filename_change_hook = None
182
183 def set_filename_change_hook(self, hook):
184 self.filename_change_hook = hook
185
186 filename = None
187 dirname = None
188
189 def set_filename(self, filename):
190 if filename and os.path.isdir(filename):
191 self.filename = None
192 self.dirname = filename
193 else:
194 self.filename = filename
195 self.dirname = None
196 self.set_saved(1)
197 if self.filename_change_hook:
198 self.filename_change_hook()
199
200 def open(self, event=None, editFile=None):
201 flist = self.editwin.flist
202 # Save in case parent window is closed (ie, during askopenfile()).
203 if flist:
204 if not editFile:
205 filename = self.askopenfile()
206 else:
207 filename=editFile
208 if filename:
209 # If editFile is valid and already open, flist.open will
210 # shift focus to its existing window.
211 # If the current window exists and is a fresh unnamed,
212 # unmodified editor window (not an interpreter shell),
213 # pass self.loadfile to flist.open so it will load the file
214 # in the current window (if the file is not already open)
215 # instead of a new window.
216 if (self.editwin and
217 not getattr(self.editwin, 'interp', None) and
218 not self.filename and
219 self.get_saved()):
220 flist.open(filename, self.loadfile)
221 else:
222 flist.open(filename)
223 else:
224 if self.text:
225 self.text.focus_set()
226 return "break"
227
228 # Code for use outside IDLE:
229 if self.get_saved():
230 reply = self.maybesave()
231 if reply == "cancel":
232 self.text.focus_set()
233 return "break"
234 if not editFile:
235 filename = self.askopenfile()
236 else:
237 filename=editFile
238 if filename:
239 self.loadfile(filename)
240 else:
241 self.text.focus_set()
242 return "break"
243
244 eol = r"(\r\n)|\n|\r" # \r\n (Windows), \n (UNIX), or \r (Mac)
245 eol_re = re.compile(eol)
246 eol_convention = os.linesep # Default
247
248 def loadfile(self, filename):
249 try:
250 # open the file in binary mode so that we can handle
251 # end-of-line convention ourselves.
252 with open(filename, 'rb') as f:
253 chars = f.read()
254 except IOError as msg:
255 tkMessageBox.showerror("I/O Error", str(msg), master=self.text)
256 return False
257
258 chars = self.decode(chars)
259 # We now convert all end-of-lines to '\n's
260 firsteol = self.eol_re.search(chars)
261 if firsteol:
262 self.eol_convention = firsteol.group(0)
263 if isinstance(self.eol_convention, unicode):
264 # Make sure it is an ASCII string
265 self.eol_convention = self.eol_convention.encode("ascii")
266 chars = self.eol_re.sub(r"\n", chars)
267
268 self.text.delete("1.0", "end")
269 self.set_filename(None)
270 self.text.insert("1.0", chars)
271 self.reset_undo()
272 self.set_filename(filename)
273 self.text.mark_set("insert", "1.0")
274 self.text.yview("insert")
275 self.updaterecentfileslist(filename)
276 return True
277
278 def decode(self, chars):
279 """Create a Unicode string
280
281 If that fails, let Tcl try its best
282 """
283 # Check presence of a UTF-8 signature first
284 if chars.startswith(BOM_UTF8):
285 try:
286 chars = chars[3:].decode("utf-8")
287 except UnicodeError:
288 # has UTF-8 signature, but fails to decode...
289 return chars
290 else:
291 # Indicates that this file originally had a BOM
292 self.fileencoding = BOM_UTF8
293 return chars
294 # Next look for coding specification
295 try:
296 enc = coding_spec(chars)
297 except LookupError as name:
298 tkMessageBox.showerror(
299 title="Error loading the file",
300 message="The encoding '%s' is not known to this Python "\
301 "installation. The file may not display correctly" % name,
302 master = self.text)
303 enc = None
304 if enc:
305 try:
306 return unicode(chars, enc)
307 except UnicodeError:
308 pass
309 # If it is ASCII, we need not to record anything
310 try:
311 return unicode(chars, 'ascii')
312 except UnicodeError:
313 pass
314 # Finally, try the locale's encoding. This is deprecated;
315 # the user should declare a non-ASCII encoding
316 try:
317 chars = unicode(chars, encoding)
318 self.fileencoding = encoding
319 except UnicodeError:
320 pass
321 return chars
322
323 def maybesave(self):
324 if self.get_saved():
325 return "yes"
326 message = "Do you want to save %s before closing?" % (
327 self.filename or "this untitled document")
328 confirm = tkMessageBox.askyesnocancel(
329 title="Save On Close",
330 message=message,
331 default=tkMessageBox.YES,
332 master=self.text)
333 if confirm:
334 reply = "yes"
335 self.save(None)
336 if not self.get_saved():
337 reply = "cancel"
338 elif confirm is None:
339 reply = "cancel"
340 else:
341 reply = "no"
342 self.text.focus_set()
343 return reply
344
345 def save(self, event):
346 if not self.filename:
347 self.save_as(event)
348 else:
349 if self.writefile(self.filename):
350 self.set_saved(True)
351 try:
352 self.editwin.store_file_breaks()
353 except AttributeError: # may be a PyShell
354 pass
355 self.text.focus_set()
356 return "break"
357
358 def save_as(self, event):
359 filename = self.asksavefile()
360 if filename:
361 if self.writefile(filename):
362 self.set_filename(filename)
363 self.set_saved(1)
364 try:
365 self.editwin.store_file_breaks()
366 except AttributeError:
367 pass
368 self.text.focus_set()
369 self.updaterecentfileslist(filename)
370 return "break"
371
372 def save_a_copy(self, event):
373 filename = self.asksavefile()
374 if filename:
375 self.writefile(filename)
376 self.text.focus_set()
377 self.updaterecentfileslist(filename)
378 return "break"
379
380 def writefile(self, filename):
381 self.fixlastline()
382 chars = self.encode(self.text.get("1.0", "end-1c"))
383 if self.eol_convention != "\n":
384 chars = chars.replace("\n", self.eol_convention)
385 try:
386 with open(filename, "wb") as f:
387 f.write(chars)
388 return True
389 except IOError as msg:
390 tkMessageBox.showerror("I/O Error", str(msg),
391 master=self.text)
392 return False
393
394 def encode(self, chars):
395 if isinstance(chars, types.StringType):
396 # This is either plain ASCII, or Tk was returning mixed-encoding
397 # text to us. Don't try to guess further.
398 return chars
399 # See whether there is anything non-ASCII in it.
400 # If not, no need to figure out the encoding.
401 try:
402 return chars.encode('ascii')
403 except UnicodeError:
404 pass
405 # If there is an encoding declared, try this first.
406 try:
407 enc = coding_spec(chars)
408 failed = None
409 except LookupError as msg:
410 failed = msg
411 enc = None
412 if enc:
413 try:
414 return chars.encode(enc)
415 except UnicodeError:
416 failed = "Invalid encoding '%s'" % enc
417 if failed:
418 tkMessageBox.showerror(
419 "I/O Error",
420 "%s. Saving as UTF-8" % failed,
421 master = self.text)
422 # If there was a UTF-8 signature, use that. This should not fail
423 if self.fileencoding == BOM_UTF8 or failed:
424 return BOM_UTF8 + chars.encode("utf-8")
425 # Try the original file encoding next, if any
426 if self.fileencoding:
427 try:
428 return chars.encode(self.fileencoding)
429 except UnicodeError:
430 tkMessageBox.showerror(
431 "I/O Error",
432 "Cannot save this as '%s' anymore. Saving as UTF-8" \
433 % self.fileencoding,
434 master = self.text)
435 return BOM_UTF8 + chars.encode("utf-8")
436 # Nothing was declared, and we had not determined an encoding
437 # on loading. Recommend an encoding line.
438 config_encoding = idleConf.GetOption("main","EditorWindow",
439 "encoding")
440 if config_encoding == 'utf-8':
441 # User has requested that we save files as UTF-8
442 return BOM_UTF8 + chars.encode("utf-8")
443 ask_user = True
444 try:
445 chars = chars.encode(encoding)
446 enc = encoding
447 if config_encoding == 'locale':
448 ask_user = False
449 except UnicodeError:
450 chars = BOM_UTF8 + chars.encode("utf-8")
451 enc = "utf-8"
452 if not ask_user:
453 return chars
454 dialog = EncodingMessage(self.editwin.top, enc)
455 dialog.go()
456 if dialog.num == 1:
457 # User asked us to edit the file
458 encline = "# -*- coding: %s -*-\n" % enc
459 firstline = self.text.get("1.0", "2.0")
460 if firstline.startswith("#!"):
461 # Insert encoding after #! line
462 self.text.insert("2.0", encline)
463 else:
464 self.text.insert("1.0", encline)
465 return self.encode(self.text.get("1.0", "end-1c"))
466 return chars
467
468 def fixlastline(self):
469 c = self.text.get("end-2c")
470 if c != '\n':
471 self.text.insert("end-1c", "\n")
472
473 def print_window(self, event):
474 confirm = tkMessageBox.askokcancel(
475 title="Print",
476 message="Print to Default Printer",
477 default=tkMessageBox.OK,
478 master=self.text)
479 if not confirm:
480 self.text.focus_set()
481 return "break"
482 tempfilename = None
483 saved = self.get_saved()
484 if saved:
485 filename = self.filename
486 # shell undo is reset after every prompt, looks saved, probably isn't
487 if not saved or filename is None:
488 (tfd, tempfilename) = tempfile.mkstemp(prefix='IDLE_tmp_')
489 filename = tempfilename
490 os.close(tfd)
491 if not self.writefile(tempfilename):
492 os.unlink(tempfilename)
493 return "break"
494 platform = os.name
495 printPlatform = True
496 if platform == 'posix': #posix platform
497 command = idleConf.GetOption('main','General',
498 'print-command-posix')
499 command = command + " 2>&1"
500 elif platform == 'nt': #win32 platform
501 command = idleConf.GetOption('main','General','print-command-win')
502 else: #no printing for this platform
503 printPlatform = False
504 if printPlatform: #we can try to print for this platform
505 command = command % pipes.quote(filename)
506 pipe = os.popen(command, "r")
507 # things can get ugly on NT if there is no printer available.
508 output = pipe.read().strip()
509 status = pipe.close()
510 if status:
511 output = "Printing failed (exit status 0x%x)\n" % \
512 status + output
513 if output:
514 output = "Printing command: %s\n" % repr(command) + output
515 tkMessageBox.showerror("Print status", output, master=self.text)
516 else: #no printing for this platform
517 message = "Printing is not enabled for this platform: %s" % platform
518 tkMessageBox.showinfo("Print status", message, master=self.text)
519 if tempfilename:
520 os.unlink(tempfilename)
521 return "break"
522
523 opendialog = None
524 savedialog = None
525
526 filetypes = [
527 ("Python files", "*.py *.pyw", "TEXT"),
528 ("Text files", "*.txt", "TEXT"),
529 ("All files", "*"),
530 ]
531
532 def askopenfile(self):
533 dir, base = self.defaultfilename("open")
534 if not self.opendialog:
535 self.opendialog = tkFileDialog.Open(master=self.text,
536 filetypes=self.filetypes)
537 filename = self.opendialog.show(initialdir=dir, initialfile=base)
538 if isinstance(filename, unicode):
539 filename = filename.encode(filesystemencoding)
540 return filename
541
542 def defaultfilename(self, mode="open"):
543 if self.filename:
544 return os.path.split(self.filename)
545 elif self.dirname:
546 return self.dirname, ""
547 else:
548 try:
549 pwd = os.getcwd()
550 except os.error:
551 pwd = ""
552 return pwd, ""
553
554 def asksavefile(self):
555 dir, base = self.defaultfilename("save")
556 if not self.savedialog:
557 self.savedialog = tkFileDialog.SaveAs(master=self.text,
558 filetypes=self.filetypes)
559 filename = self.savedialog.show(initialdir=dir, initialfile=base)
560 if isinstance(filename, unicode):
561 filename = filename.encode(filesystemencoding)
562 return filename
563
564 def updaterecentfileslist(self,filename):
565 "Update recent file list on all editor windows"
566 self.editwin.update_recent_files_list(filename)
567
568def test():
569 root = Tk()
570 class MyEditWin:
571 def __init__(self, text):
572 self.text = text
573 self.flist = None
574 self.text.bind("<Control-o>", self.open)
575 self.text.bind("<Control-s>", self.save)
576 self.text.bind("<Alt-s>", self.save_as)
577 self.text.bind("<Alt-z>", self.save_a_copy)
578 def get_saved(self): return 0
579 def set_saved(self, flag): pass
580 def reset_undo(self): pass
581 def open(self, event):
582 self.text.event_generate("<<open-window-from-file>>")
583 def save(self, event):
584 self.text.event_generate("<<save-window>>")
585 def save_as(self, event):
586 self.text.event_generate("<<save-window-as-file>>")
587 def save_a_copy(self, event):
588 self.text.event_generate("<<save-copy-of-window-as-file>>")
589 text = Text(root)
590 text.pack()
591 text.focus_set()
592 editwin = MyEditWin(text)
593 io = IOBinding(editwin)
594 root.mainloop()
595
596if __name__ == "__main__":
597 test()
Note: See TracBrowser for help on using the repository browser.