blob: a90157ea0c73881ca97893862181c581627e72cd [file] [log] [blame]
David Scherer7aced172000-08-15 01:13:23 +00001# 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
Kurt B. Kaiser01166da2002-09-16 22:03:37 +00009import types
10import sys
11import codecs
Steven M. Gava7981ce52002-06-11 04:45:34 +000012import tempfile
David Scherer7aced172000-08-15 01:13:23 +000013import tkFileDialog
14import tkMessageBox
15import re
Kurt B. Kaiserbfed3462002-12-14 04:38:51 +000016
Steven M. Gava7981ce52002-06-11 04:45:34 +000017from configHandler import idleConf
David Scherer7aced172000-08-15 01:13:23 +000018
Kurt B. Kaiser01166da2002-09-16 22:03:37 +000019try:
20 from codecs import BOM_UTF8
21except ImportError:
22 # only available since Python 2.3
23 BOM_UTF8 = '\xef\xbb\xbf'
24
25# Try setting the locale, so that we can find out
26# what encoding to use
27try:
28 import locale
29 locale.setlocale(locale.LC_CTYPE, "")
30except ImportError:
31 pass
32
33encoding = "ascii"
34if sys.platform == 'win32':
35 # On Windows, we could use "mbcs". However, to give the user
36 # a portable encoding name, we need to find the code page
37 try:
38 encoding = locale.getdefaultlocale()[1]
39 codecs.lookup(encoding)
40 except LookupError:
41 pass
42else:
43 try:
44 # Different things can fail here: the locale module may not be
45 # loaded, it may not offer nl_langinfo, or CODESET, or the
46 # resulting codeset may be unknown to Python. We ignore all
47 # these problems, falling back to ASCII
48 encoding = locale.nl_langinfo(locale.CODESET)
Tony Lowndse555fc72002-09-23 01:01:20 +000049 if encoding is None:
50 # situation occurs on Mac OS X
51 encoding = 'ascii'
Kurt B. Kaiser01166da2002-09-16 22:03:37 +000052 codecs.lookup(encoding)
53 except (NameError, AttributeError, LookupError):
54 # Try getdefaultlocale well: it parses environment variables,
55 # which may give a clue. Unfortunately, getdefaultlocale has
56 # bugs that can cause ValueError.
57 try:
58 encoding = locale.getdefaultlocale()[1]
Tony Lowndse555fc72002-09-23 01:01:20 +000059 if encoding is None:
60 # situation occurs on Mac OS X
61 encoding = 'ascii'
Kurt B. Kaiser01166da2002-09-16 22:03:37 +000062 codecs.lookup(encoding)
63 except (ValueError, LookupError):
64 pass
65
66encoding = encoding.lower()
67
68coding_re = re.compile("coding[:=]\s*([-\w_.]+)")
Kurt B. Kaiserbfed3462002-12-14 04:38:51 +000069
Kurt B. Kaiser01166da2002-09-16 22:03:37 +000070def coding_spec(str):
Kurt B. Kaiser01166da2002-09-16 22:03:37 +000071 """Return the encoding declaration according to PEP 263.
Kurt B. Kaiser01166da2002-09-16 22:03:37 +000072
Kurt B. Kaiserbfed3462002-12-14 04:38:51 +000073 Raise LookupError if the encoding is declared but unknown.
74 """
Kurt B. Kaiser01166da2002-09-16 22:03:37 +000075 # Only consider the first two lines
76 str = str.split("\n")[:2]
77 str = "\n".join(str)
78
79 match = coding_re.search(str)
80 if not match:
81 return None
82 name = match.group(1)
83 # Check whether the encoding is known
84 import codecs
85 try:
86 codecs.lookup(name)
87 except LookupError:
88 # The standard encoding error does not indicate the encoding
89 raise LookupError, "Unknown encoding "+name
90 return name
David Scherer7aced172000-08-15 01:13:23 +000091
Kurt B. Kaiserbfed3462002-12-14 04:38:51 +000092
David Scherer7aced172000-08-15 01:13:23 +000093class IOBinding:
94
95 def __init__(self, editwin):
96 self.editwin = editwin
97 self.text = editwin.text
98 self.__id_open = self.text.bind("<<open-window-from-file>>", self.open)
99 self.__id_save = self.text.bind("<<save-window>>", self.save)
100 self.__id_saveas = self.text.bind("<<save-window-as-file>>",
101 self.save_as)
102 self.__id_savecopy = self.text.bind("<<save-copy-of-window-as-file>>",
103 self.save_a_copy)
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000104 self.fileencoding = None
Steven M. Gava7981ce52002-06-11 04:45:34 +0000105 self.__id_print = self.text.bind("<<print-window>>", self.print_window)
106
David Scherer7aced172000-08-15 01:13:23 +0000107 def close(self):
108 # Undo command bindings
109 self.text.unbind("<<open-window-from-file>>", self.__id_open)
110 self.text.unbind("<<save-window>>", self.__id_save)
111 self.text.unbind("<<save-window-as-file>>",self.__id_saveas)
112 self.text.unbind("<<save-copy-of-window-as-file>>", self.__id_savecopy)
Steven M. Gava7981ce52002-06-11 04:45:34 +0000113 self.text.unbind("<<print-window>>", self.__id_print)
David Scherer7aced172000-08-15 01:13:23 +0000114 # Break cycles
115 self.editwin = None
116 self.text = None
117 self.filename_change_hook = None
118
119 def get_saved(self):
120 return self.editwin.get_saved()
121
122 def set_saved(self, flag):
123 self.editwin.set_saved(flag)
124
125 def reset_undo(self):
126 self.editwin.reset_undo()
127
128 filename_change_hook = None
129
130 def set_filename_change_hook(self, hook):
131 self.filename_change_hook = hook
132
133 filename = None
134
135 def set_filename(self, filename):
136 self.filename = filename
137 self.set_saved(1)
138 if self.filename_change_hook:
139 self.filename_change_hook()
140
Steven M. Gava1d46e402002-03-27 08:40:46 +0000141 def open(self, event=None, editFile=None):
David Scherer7aced172000-08-15 01:13:23 +0000142 if self.editwin.flist:
Steven M. Gava1d46e402002-03-27 08:40:46 +0000143 if not editFile:
144 filename = self.askopenfile()
145 else:
146 filename=editFile
David Scherer7aced172000-08-15 01:13:23 +0000147 if filename:
Kurt B. Kaiser1bf4c2d2002-07-21 01:24:28 +0000148 # If the current window has no filename and hasn't been
149 # modified, we replace its contents (no loss). Otherwise
150 # we open a new window. But we won't replace the
151 # shell window (which has an interp(reter) attribute), which
152 # gets set to "not modified" at every new prompt.
153 try:
154 interp = self.editwin.interp
155 except:
156 interp = None
157 if not self.filename and self.get_saved() and not interp:
David Scherer7aced172000-08-15 01:13:23 +0000158 self.editwin.flist.open(filename, self.loadfile)
159 else:
160 self.editwin.flist.open(filename)
161 else:
162 self.text.focus_set()
David Scherer7aced172000-08-15 01:13:23 +0000163 return "break"
Kurt B. Kaiser1bf4c2d2002-07-21 01:24:28 +0000164 #
David Scherer7aced172000-08-15 01:13:23 +0000165 # Code for use outside IDLE:
166 if self.get_saved():
167 reply = self.maybesave()
168 if reply == "cancel":
169 self.text.focus_set()
170 return "break"
Steven M. Gava1d46e402002-03-27 08:40:46 +0000171 if not editFile:
172 filename = self.askopenfile()
173 else:
174 filename=editFile
David Scherer7aced172000-08-15 01:13:23 +0000175 if filename:
176 self.loadfile(filename)
177 else:
178 self.text.focus_set()
179 return "break"
180
181 def loadfile(self, filename):
182 try:
183 # open the file in binary mode so that we can handle
184 # end-of-line convention ourselves.
185 f = open(filename,'rb')
186 chars = f.read()
187 f.close()
188 except IOError, msg:
189 tkMessageBox.showerror("I/O Error", str(msg), master=self.text)
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000190 return False
David Scherer7aced172000-08-15 01:13:23 +0000191
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000192 chars = self.decode(chars)
David Scherer7aced172000-08-15 01:13:23 +0000193 # We now convert all end-of-lines to '\n's
194 eol = r"(\r\n)|\n|\r" # \r\n (Windows), \n (UNIX), or \r (Mac)
195 chars = re.compile( eol ).sub( r"\n", chars )
196
197 self.text.delete("1.0", "end")
198 self.set_filename(None)
199 self.text.insert("1.0", chars)
200 self.reset_undo()
201 self.set_filename(filename)
202 self.text.mark_set("insert", "1.0")
203 self.text.see("insert")
Chui Tey993e81a2002-11-04 03:11:10 +0000204 self.updaterecentfileslist(filename)
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000205 return True
206
207 def decode(self, chars):
Kurt B. Kaiserbfed3462002-12-14 04:38:51 +0000208 """Create a Unicode string
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000209
Kurt B. Kaiserbfed3462002-12-14 04:38:51 +0000210 If that fails, let Tcl try its best
211 """
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000212 # Check presence of a UTF-8 signature first
213 if chars.startswith(BOM_UTF8):
214 try:
215 chars = chars[3:].decode("utf-8")
216 except UnicodeError:
217 # has UTF-8 signature, but fails to decode...
218 return chars
219 else:
220 # Indicates that this file originally had a BOM
221 self.fileencoding = BOM_UTF8
222 return chars
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000223 # Next look for coding specification
224 try:
225 enc = coding_spec(chars)
226 except LookupError, name:
227 tkMessageBox.showerror(
228 title="Error loading the file",
229 message="The encoding '%s' is not known to this Python "\
230 "installation. The file may not display correctly" % name,
231 master = self.text)
232 enc = None
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000233 if enc:
234 try:
235 return unicode(chars, enc)
236 except UnicodeError:
237 pass
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000238 # If it is ASCII, we need not to record anything
239 try:
240 return unicode(chars, 'ascii')
241 except UnicodeError:
242 pass
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000243 # Finally, try the locale's encoding. This is deprecated;
244 # the user should declare a non-ASCII encoding
245 try:
246 chars = unicode(chars, encoding)
247 self.fileencoding = encoding
248 except UnicodeError:
249 pass
250 return chars
David Scherer7aced172000-08-15 01:13:23 +0000251
252 def maybesave(self):
253 if self.get_saved():
254 return "yes"
255 message = "Do you want to save %s before closing?" % (
256 self.filename or "this untitled document")
257 m = tkMessageBox.Message(
258 title="Save On Close",
259 message=message,
260 icon=tkMessageBox.QUESTION,
261 type=tkMessageBox.YESNOCANCEL,
262 master=self.text)
263 reply = m.show()
264 if reply == "yes":
265 self.save(None)
266 if not self.get_saved():
267 reply = "cancel"
268 self.text.focus_set()
269 return reply
270
271 def save(self, event):
272 if not self.filename:
273 self.save_as(event)
274 else:
275 if self.writefile(self.filename):
276 self.set_saved(1)
Kurt B. Kaiserbfed3462002-12-14 04:38:51 +0000277 self.editwin.store_file_breaks()
David Scherer7aced172000-08-15 01:13:23 +0000278 self.text.focus_set()
279 return "break"
280
281 def save_as(self, event):
282 filename = self.asksavefile()
283 if filename:
284 if self.writefile(filename):
285 self.set_filename(filename)
286 self.set_saved(1)
Kurt B. Kaiserbfed3462002-12-14 04:38:51 +0000287 self.editwin.store_file_breaks()
David Scherer7aced172000-08-15 01:13:23 +0000288 self.text.focus_set()
Chui Tey993e81a2002-11-04 03:11:10 +0000289 self.updaterecentfileslist(filename)
David Scherer7aced172000-08-15 01:13:23 +0000290 return "break"
291
292 def save_a_copy(self, event):
293 filename = self.asksavefile()
294 if filename:
295 self.writefile(filename)
296 self.text.focus_set()
Chui Tey993e81a2002-11-04 03:11:10 +0000297 self.updaterecentfileslist(filename)
David Scherer7aced172000-08-15 01:13:23 +0000298 return "break"
299
300 def writefile(self, filename):
301 self.fixlastline()
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000302 chars = self.encode(self.text.get("1.0", "end-1c"))
David Scherer7aced172000-08-15 01:13:23 +0000303 try:
304 f = open(filename, "w")
David Scherer7aced172000-08-15 01:13:23 +0000305 f.write(chars)
306 f.close()
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000307 return True
David Scherer7aced172000-08-15 01:13:23 +0000308 except IOError, msg:
309 tkMessageBox.showerror("I/O Error", str(msg),
310 master=self.text)
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000311 return False
312
313 def encode(self, chars):
314 if isinstance(chars, types.StringType):
315 # This is either plain ASCII, or Tk was returning mixed-encoding
316 # text to us. Don't try to guess further.
317 return chars
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000318 # See whether there is anything non-ASCII in it.
319 # If not, no need to figure out the encoding.
320 try:
321 return chars.encode('ascii')
322 except UnicodeError:
323 pass
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000324 # If there is an encoding declared, try this first.
325 try:
326 enc = coding_spec(chars)
327 failed = None
328 except LookupError, msg:
329 failed = msg
330 enc = None
331 if enc:
332 try:
333 return chars.encode(enc)
334 except UnicodeError:
335 failed = "Invalid encoding '%s'" % enc
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000336 if failed:
337 tkMessageBox.showerror(
338 "I/O Error",
339 "%s. Saving as UTF-8" % failed,
340 master = self.text)
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000341 # If there was a UTF-8 signature, use that. This should not fail
342 if self.fileencoding == BOM_UTF8 or failed:
343 return BOM_UTF8 + chars.encode("utf-8")
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000344 # Try the original file encoding next, if any
345 if self.fileencoding:
346 try:
347 return chars.encode(self.fileencoding)
348 except UnicodeError:
349 tkMessageBox.showerror(
350 "I/O Error",
351 "Cannot save this as '%s' anymore. Saving as UTF-8" \
352 % self.fileencoding,
353 master = self.text)
354 return BOM_UTF8 + chars.encode("utf-8")
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000355 # Nothing was declared, and we had not determined an encoding
356 # on loading. Recommend an encoding line.
357 try:
358 chars = chars.encode(encoding)
359 enc = encoding
360 except UnicodeError:
361 chars = BOM_UTF8 + chars.encode("utf-8")
362 enc = "utf-8"
363 tkMessageBox.showerror(
364 "I/O Error",
365 "Non-ASCII found, yet no encoding declared. Add a line like\n"
366 "# -*- coding: %s -*- \nto your file" % enc,
367 master = self.text)
368 return chars
Steven M. Gava7981ce52002-06-11 04:45:34 +0000369
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000370 def fixlastline(self):
371 c = self.text.get("end-2c")
372 if c != '\n':
373 self.text.insert("end-1c", "\n")
374
Steven M. Gava7981ce52002-06-11 04:45:34 +0000375 def print_window(self, event):
376 tempfilename = None
377 if self.get_saved():
378 filename = self.filename
379 else:
380 filename = tempfilename = tempfile.mktemp()
381 if not self.writefile(filename):
382 os.unlink(tempfilename)
383 return "break"
384 platform=os.name
385 printPlatform=1
386 if platform == 'posix': #posix platform
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000387 command = idleConf.GetOption('main','General',
388 'print-command-posix')
Steven M. Gava7981ce52002-06-11 04:45:34 +0000389 command = command + " 2>&1"
390 elif platform == 'nt': #win32 platform
391 command = idleConf.GetOption('main','General','print-command-win')
392 else: #no printing for this platform
393 printPlatform=0
394 if printPlatform: #we can try to print for this platform
395 command = command % filename
396 pipe = os.popen(command, "r")
397 output = pipe.read().strip()
398 status = pipe.close()
399 if status:
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000400 output = "Printing failed (exit status 0x%x)\n" % \
401 status + output
Steven M. Gava7981ce52002-06-11 04:45:34 +0000402 if output:
403 output = "Printing command: %s\n" % repr(command) + output
404 tkMessageBox.showerror("Print status", output, master=self.text)
405 else: #no printing for this platform
406 message="Printing is not enabled for this platform: %s" % platform
407 tkMessageBox.showinfo("Print status", message, master=self.text)
408 return "break"
409
David Scherer7aced172000-08-15 01:13:23 +0000410 opendialog = None
411 savedialog = None
412
413 filetypes = [
414 ("Python and text files", "*.py *.pyw *.txt", "TEXT"),
415 ("All text files", "*", "TEXT"),
416 ("All files", "*"),
417 ]
418
419 def askopenfile(self):
420 dir, base = self.defaultfilename("open")
421 if not self.opendialog:
422 self.opendialog = tkFileDialog.Open(master=self.text,
423 filetypes=self.filetypes)
424 return self.opendialog.show(initialdir=dir, initialfile=base)
425
426 def defaultfilename(self, mode="open"):
427 if self.filename:
428 return os.path.split(self.filename)
429 else:
430 try:
431 pwd = os.getcwd()
432 except os.error:
433 pwd = ""
434 return pwd, ""
435
436 def asksavefile(self):
437 dir, base = self.defaultfilename("save")
438 if not self.savedialog:
439 self.savedialog = tkFileDialog.SaveAs(master=self.text,
440 filetypes=self.filetypes)
441 return self.savedialog.show(initialdir=dir, initialfile=base)
442
Chui Tey993e81a2002-11-04 03:11:10 +0000443 def updaterecentfileslist(self,filename):
Kurt B. Kaiserbfed3462002-12-14 04:38:51 +0000444 "Update recent file list on all editor windows"
Chui Tey993e81a2002-11-04 03:11:10 +0000445 self.editwin.UpdateRecentFilesList(filename)
446
David Scherer7aced172000-08-15 01:13:23 +0000447def test():
David Scherer7aced172000-08-15 01:13:23 +0000448 root = Tk()
449 class MyEditWin:
450 def __init__(self, text):
451 self.text = text
452 self.flist = None
453 self.text.bind("<Control-o>", self.open)
454 self.text.bind("<Control-s>", self.save)
455 self.text.bind("<Alt-s>", self.save_as)
456 self.text.bind("<Alt-z>", self.save_a_copy)
457 def get_saved(self): return 0
458 def set_saved(self, flag): pass
459 def reset_undo(self): pass
460 def open(self, event):
461 self.text.event_generate("<<open-window-from-file>>")
462 def save(self, event):
463 self.text.event_generate("<<save-window>>")
464 def save_as(self, event):
465 self.text.event_generate("<<save-window-as-file>>")
466 def save_a_copy(self, event):
467 self.text.event_generate("<<save-copy-of-window-as-file>>")
468 text = Text(root)
469 text.pack()
470 text.focus_set()
471 editwin = MyEditWin(text)
472 io = IOBinding(editwin)
473 root.mainloop()
474
475if __name__ == "__main__":
Kurt B. Kaiser7eea2712001-07-13 04:18:32 +0000476 from Tkinter import *
David Scherer7aced172000-08-15 01:13:23 +0000477 test()