append(): Fixing the test for convertability after consultation with
[python/dscho.git] / Tools / idle / IOBinding.py
blob8d654b9aec9454c012e0bf904bd007331f3dda17
1 import os
2 import types
3 import sys
4 import codecs
5 import re
6 import tempfile
7 import tkFileDialog
8 import tkMessageBox
9 from IdleConf import idleconf
11 #$ event <<open-window-from-file>>
12 #$ win <Control-o>
13 #$ unix <Control-x><Control-f>
15 #$ event <<save-window>>
16 #$ win <Control-s>
17 #$ unix <Control-x><Control-s>
19 #$ event <<save-window-as-file>>
20 #$ win <Alt-s>
21 #$ unix <Control-x><Control-w>
23 #$ event <<save-copy-of-window-as-file>>
24 #$ win <Alt-Shift-s>
25 #$ unix <Control-x><w>
27 #$ event <<print-window>>
28 #$ win <Control-p>
29 #$ unix <Control-x><Control-p>
31 try:
32 from codecs import BOM_UTF8
33 except ImportError:
34 # only available since Python 2.3
35 BOM_UTF8 = '\xef\xbb\xbf'
37 # Try setting the locale, so that we can find out
38 # what encoding to use
39 try:
40 import locale
41 locale.setlocale(locale.LC_CTYPE, "")
42 except ImportError:
43 pass
45 encoding = "ascii"
46 if sys.platform == 'win32':
47 # On Windows, we could use "mbcs". However, to give the user
48 # a portable encoding name, we need to find the code page
49 try:
50 encoding = locale.getdefaultlocale()[1]
51 codecs.lookup(encoding)
52 except LookupError:
53 pass
54 else:
55 try:
56 # Different things can fail here: the locale module may not be
57 # loaded, it may not offer nl_langinfo, or CODESET, or the
58 # resulting codeset may be unknown to Python. We ignore all
59 # these problems, falling back to ASCII
60 encoding = locale.nl_langinfo(locale.CODESET)
61 codecs.lookup(encoding)
62 except (NameError, AttributeError, LookupError):
63 # Try getdefaultlocale well: it parses environment variables,
64 # which may give a clue. Unfortunately, getdefaultlocale has
65 # bugs that can cause ValueError.
66 try:
67 encoding = locale.getdefaultlocale()[1]
68 codecs.lookup(encoding)
69 except (ValueError, LookupError):
70 pass
72 encoding = encoding.lower()
74 coding_re = re.compile("coding[:=]\s*([-\w_.]+)")
75 def coding_spec(str):
77 """Return the encoding declaration according to PEP 263.
78 Raise LookupError if the encoding is declared but unknown."""
80 # Only consider the first two lines
81 str = str.split("\n")[:2]
82 str = "\n".join(str)
84 match = coding_re.search(str)
85 if not match:
86 return None
87 name = match.group(1)
88 # Check whether the encoding is known
89 import codecs
90 try:
91 codecs.lookup(name)
92 except LookupError:
93 # The standard encoding error does not indicate the encoding
94 raise LookupError, "Unknown encoding "+name
95 return name
97 class IOBinding:
99 def __init__(self, editwin):
100 self.editwin = editwin
101 self.text = editwin.text
102 self.__id_open = self.text.bind("<<open-window-from-file>>", self.open)
103 self.__id_save = self.text.bind("<<save-window>>", self.save)
104 self.__id_saveas = self.text.bind("<<save-window-as-file>>",
105 self.save_as)
106 self.__id_savecopy = self.text.bind("<<save-copy-of-window-as-file>>",
107 self.save_a_copy)
108 self.__id_print = self.text.bind("<<print-window>>", self.print_window)
109 self.fileencoding = None
111 def close(self):
112 # Undo command bindings
113 self.text.unbind("<<open-window-from-file>>", self.__id_open)
114 self.text.unbind("<<save-window>>", self.__id_save)
115 self.text.unbind("<<save-window-as-file>>",self.__id_saveas)
116 self.text.unbind("<<save-copy-of-window-as-file>>", self.__id_savecopy)
117 self.text.unbind("<<print-window>>", self.__id_print)
118 # Break cycles
119 self.editwin = None
120 self.text = None
121 self.filename_change_hook = None
123 def get_saved(self):
124 return self.editwin.get_saved()
126 def set_saved(self, flag):
127 self.editwin.set_saved(flag)
129 def reset_undo(self):
130 self.editwin.reset_undo()
132 filename_change_hook = None
134 def set_filename_change_hook(self, hook):
135 self.filename_change_hook = hook
137 filename = None
139 def set_filename(self, filename):
140 self.filename = filename
141 self.set_saved(1)
142 if self.filename_change_hook:
143 self.filename_change_hook()
145 def open(self, event):
146 if self.editwin.flist:
147 filename = self.askopenfile()
148 if filename:
149 self.editwin.flist.open(filename)
150 else:
151 self.text.focus_set()
152 return "break"
153 # Code for use outside IDLE:
154 if self.get_saved():
155 reply = self.maybesave()
156 if reply == "cancel":
157 self.text.focus_set()
158 return "break"
159 filename = self.askopenfile()
160 if filename:
161 self.loadfile(filename)
162 else:
163 self.text.focus_set()
164 return "break"
166 def loadfile(self, filename):
167 try:
168 f = open(filename)
169 chars = f.read()
170 f.close()
171 except IOError, msg:
172 tkMessageBox.showerror("I/O Error", str(msg), master=self.text)
173 return False
175 chars = self.decode(chars)
177 self.text.delete("1.0", "end")
178 self.set_filename(None)
179 self.text.insert("1.0", chars)
180 self.reset_undo()
181 self.set_filename(filename)
182 self.text.mark_set("insert", "1.0")
183 self.text.see("insert")
184 return True
186 def decode(self, chars):
187 # Try to create a Unicode string. If that fails, let Tcl try
188 # its best
190 # Check presence of a UTF-8 signature first
191 if chars.startswith(BOM_UTF8):
192 try:
193 chars = chars[3:].decode("utf-8")
194 except UnicodeError:
195 # has UTF-8 signature, but fails to decode...
196 return chars
197 else:
198 # Indicates that this file originally had a BOM
199 self.fileencoding = BOM_UTF8
200 return chars
202 # Next look for coding specification
203 try:
204 enc = coding_spec(chars)
205 except LookupError, name:
206 tkMessageBox.showerror(
207 title="Error loading the file",
208 message="The encoding '%s' is not known to this Python "\
209 "installation. The file may not display correctly" % name,
210 master = self.text)
211 enc = None
213 if enc:
214 try:
215 return unicode(chars, enc)
216 except UnicodeError:
217 pass
219 # If it is ASCII, we need not to record anything
220 try:
221 return unicode(chars, 'ascii')
222 except UnicodeError:
223 pass
225 # Finally, try the locale's encoding. This is deprecated;
226 # the user should declare a non-ASCII encoding
227 try:
228 chars = unicode(chars, encoding)
229 self.fileencoding = encoding
230 except UnicodeError:
231 pass
232 return chars
234 def maybesave(self):
235 if self.get_saved():
236 return "yes"
237 message = "Do you want to save %s before closing?" % (
238 self.filename or "this untitled document")
239 m = tkMessageBox.Message(
240 title="Save On Close",
241 message=message,
242 icon=tkMessageBox.QUESTION,
243 type=tkMessageBox.YESNOCANCEL,
244 master=self.text)
245 reply = m.show()
246 if reply == "yes":
247 self.save(None)
248 if not self.get_saved():
249 reply = "cancel"
250 self.text.focus_set()
251 return reply
253 def save(self, event):
254 if not self.filename:
255 self.save_as(event)
256 else:
257 if self.writefile(self.filename):
258 self.set_saved(1)
259 self.text.focus_set()
260 return "break"
262 def save_as(self, event):
263 filename = self.asksavefile()
264 if filename:
265 if self.writefile(filename):
266 self.set_filename(filename)
267 self.set_saved(1)
268 self.text.focus_set()
269 return "break"
271 def save_a_copy(self, event):
272 filename = self.asksavefile()
273 if filename:
274 self.writefile(filename)
275 self.text.focus_set()
276 return "break"
278 def print_window(self, event):
279 if self.get_saved():
280 filename = self.filename
281 else:
282 (tfd, tfn) = tempfile.mkstemp()
283 os.close(tfd)
284 filename = tfn
285 if not self.writefile(filename):
286 os.unlink(tfn)
287 return "break"
288 edconf = idleconf.getsection('EditorWindow')
289 command = edconf.get('print-command')
290 command = command % filename
291 if os.name == 'posix':
292 command = command + " 2>&1"
293 pipe = os.popen(command, "r")
294 output = pipe.read().strip()
295 status = pipe.close()
296 if status:
297 output = "Printing failed (exit status 0x%x)\n" % status + output
298 if output:
299 output = "Printing command: %s\n" % repr(command) + output
300 tkMessageBox.showerror("Print status", output, master=self.text)
301 return "break"
303 def writefile(self, filename):
304 self.fixlastline()
305 chars = self.encode(self.text.get("1.0", "end-1c"))
306 try:
307 f = open(filename, "w")
308 f.write(chars)
309 f.close()
310 ## print "saved to", `filename`
311 return True
312 except IOError, msg:
313 tkMessageBox.showerror("I/O Error", str(msg),
314 master=self.text)
315 return False
317 def encode(self, chars):
318 if isinstance(chars, types.StringType):
319 # This is either plain ASCII, or Tk was returning mixed-encoding
320 # text to us. Don't try to guess further.
321 return chars
323 # See whether there is anything non-ASCII in it.
324 # If not, no need to figure out the encoding.
325 try:
326 return chars.encode('ascii')
327 except UnicodeError:
328 pass
330 # If there is an encoding declared, try this first.
331 try:
332 enc = coding_spec(chars)
333 failed = None
334 except LookupError, msg:
335 failed = msg
336 enc = None
337 if enc:
338 try:
339 return chars.encode(enc)
340 except UnicodeError:
341 failed = "Invalid encoding '%s'" % enc
343 if failed:
344 tkMessageBox.showerror(
345 "I/O Error",
346 "%s. Saving as UTF-8" % failed,
347 master = self.text)
349 # If there was a UTF-8 signature, use that. This should not fail
350 if self.fileencoding == BOM_UTF8 or failed:
351 return BOM_UTF8 + chars.encode("utf-8")
353 # Try the original file encoding next, if any
354 if self.fileencoding:
355 try:
356 return chars.encode(self.fileencoding)
357 except UnicodeError:
358 tkMessageBox.showerror(
359 "I/O Error",
360 "Cannot save this as '%s' anymore. Saving as UTF-8" % self.fileencoding,
361 master = self.text)
362 return BOM_UTF8 + chars.encode("utf-8")
364 # Nothing was declared, and we had not determined an encoding
365 # on loading. Recommend an encoding line.
366 try:
367 chars = chars.encode(encoding)
368 enc = encoding
369 except UnicodeError:
370 chars = BOM_UTF8 + chars.encode("utf-8")
371 enc = "utf-8"
372 tkMessageBox.showerror(
373 "I/O Error",
374 "Non-ASCII found, yet no encoding declared. Add a line like\n"
375 "# -*- coding: %s -*- \nto your file" % enc,
376 master = self.text)
377 return chars
379 def fixlastline(self):
380 c = self.text.get("end-2c")
381 if c != '\n':
382 self.text.insert("end-1c", "\n")
384 opendialog = None
385 savedialog = None
387 filetypes = [
388 ("Python and text files", "*.py *.pyw *.txt", "TEXT"),
389 ("All text files", "*", "TEXT"),
390 ("All files", "*"),
393 def askopenfile(self):
394 dir, base = self.defaultfilename("open")
395 if not self.opendialog:
396 self.opendialog = tkFileDialog.Open(master=self.text,
397 filetypes=self.filetypes)
398 return self.opendialog.show(initialdir=dir, initialfile=base)
400 def defaultfilename(self, mode="open"):
401 if self.filename:
402 return os.path.split(self.filename)
403 else:
404 try:
405 pwd = os.getcwd()
406 except os.error:
407 pwd = ""
408 return pwd, ""
410 def asksavefile(self):
411 dir, base = self.defaultfilename("save")
412 if not self.savedialog:
413 self.savedialog = tkFileDialog.SaveAs(master=self.text,
414 filetypes=self.filetypes)
415 return self.savedialog.show(initialdir=dir, initialfile=base)
418 def test():
419 root = Tk()
420 class MyEditWin:
421 def __init__(self, text):
422 self.text = text
423 self.flist = None
424 self.text.bind("<Control-o>", self.open)
425 self.text.bind("<Control-s>", self.save)
426 self.text.bind("<Alt-s>", self.save_as)
427 self.text.bind("<Alt-z>", self.save_a_copy)
428 def get_saved(self): return 0
429 def set_saved(self, flag): pass
430 def reset_undo(self): pass
431 def open(self, event):
432 self.text.event_generate("<<open-window-from-file>>")
433 def save(self, event):
434 self.text.event_generate("<<save-window>>")
435 def save_as(self, event):
436 self.text.event_generate("<<save-window-as-file>>")
437 def save_a_copy(self, event):
438 self.text.event_generate("<<save-copy-of-window-as-file>>")
439 text = Text(root)
440 text.pack()
441 text.focus_set()
442 editwin = MyEditWin(text)
443 io = IOBinding(editwin)
444 root.mainloop()
446 if __name__ == "__main__":
447 from Tkinter import *
448 test()