blob: 6f46a60d7846cf1d4661d6d134717a90e9fffc9b [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)
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000106
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
Guido van Rossumc2f77dd2003-04-25 18:36:31 +0000181 eol = r"(\r\n)|\n|\r" # \r\n (Windows), \n (UNIX), or \r (Mac)
182 eol_re = re.compile(eol)
183 eol_convention = os.linesep # Default
184
David Scherer7aced172000-08-15 01:13:23 +0000185 def loadfile(self, filename):
186 try:
187 # open the file in binary mode so that we can handle
188 # end-of-line convention ourselves.
189 f = open(filename,'rb')
190 chars = f.read()
191 f.close()
192 except IOError, msg:
193 tkMessageBox.showerror("I/O Error", str(msg), master=self.text)
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000194 return False
David Scherer7aced172000-08-15 01:13:23 +0000195
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000196 chars = self.decode(chars)
David Scherer7aced172000-08-15 01:13:23 +0000197 # We now convert all end-of-lines to '\n's
Guido van Rossumc2f77dd2003-04-25 18:36:31 +0000198 firsteol = self.eol_re.search(chars)
199 if firsteol:
200 self.eol_convention = firsteol.group(0)
201 chars = self.eol_re.sub(r"\n", chars)
David Scherer7aced172000-08-15 01:13:23 +0000202
203 self.text.delete("1.0", "end")
204 self.set_filename(None)
205 self.text.insert("1.0", chars)
206 self.reset_undo()
207 self.set_filename(filename)
208 self.text.mark_set("insert", "1.0")
209 self.text.see("insert")
Chui Tey993e81a2002-11-04 03:11:10 +0000210 self.updaterecentfileslist(filename)
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000211 return True
212
213 def decode(self, chars):
Kurt B. Kaiserbfed3462002-12-14 04:38:51 +0000214 """Create a Unicode string
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000215
Kurt B. Kaiserbfed3462002-12-14 04:38:51 +0000216 If that fails, let Tcl try its best
217 """
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000218 # Check presence of a UTF-8 signature first
219 if chars.startswith(BOM_UTF8):
220 try:
221 chars = chars[3:].decode("utf-8")
222 except UnicodeError:
223 # has UTF-8 signature, but fails to decode...
224 return chars
225 else:
226 # Indicates that this file originally had a BOM
227 self.fileencoding = BOM_UTF8
228 return chars
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000229 # Next look for coding specification
230 try:
231 enc = coding_spec(chars)
232 except LookupError, name:
233 tkMessageBox.showerror(
234 title="Error loading the file",
235 message="The encoding '%s' is not known to this Python "\
236 "installation. The file may not display correctly" % name,
237 master = self.text)
238 enc = None
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000239 if enc:
240 try:
241 return unicode(chars, enc)
242 except UnicodeError:
243 pass
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000244 # If it is ASCII, we need not to record anything
245 try:
246 return unicode(chars, 'ascii')
247 except UnicodeError:
248 pass
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000249 # Finally, try the locale's encoding. This is deprecated;
250 # the user should declare a non-ASCII encoding
251 try:
252 chars = unicode(chars, encoding)
253 self.fileencoding = encoding
254 except UnicodeError:
255 pass
256 return chars
David Scherer7aced172000-08-15 01:13:23 +0000257
258 def maybesave(self):
259 if self.get_saved():
260 return "yes"
261 message = "Do you want to save %s before closing?" % (
262 self.filename or "this untitled document")
263 m = tkMessageBox.Message(
264 title="Save On Close",
265 message=message,
266 icon=tkMessageBox.QUESTION,
267 type=tkMessageBox.YESNOCANCEL,
268 master=self.text)
269 reply = m.show()
270 if reply == "yes":
271 self.save(None)
272 if not self.get_saved():
273 reply = "cancel"
274 self.text.focus_set()
275 return reply
276
277 def save(self, event):
278 if not self.filename:
279 self.save_as(event)
280 else:
281 if self.writefile(self.filename):
282 self.set_saved(1)
Kurt B. Kaiserddeaf112003-03-04 04:42:04 +0000283 try:
284 self.editwin.store_file_breaks()
285 except AttributeError: # may be a PyShell
286 pass
David Scherer7aced172000-08-15 01:13:23 +0000287 self.text.focus_set()
288 return "break"
289
290 def save_as(self, event):
291 filename = self.asksavefile()
292 if filename:
293 if self.writefile(filename):
294 self.set_filename(filename)
295 self.set_saved(1)
Kurt B. Kaiserddeaf112003-03-04 04:42:04 +0000296 try:
297 self.editwin.store_file_breaks()
298 except AttributeError:
299 pass
David Scherer7aced172000-08-15 01:13:23 +0000300 self.text.focus_set()
Chui Tey993e81a2002-11-04 03:11:10 +0000301 self.updaterecentfileslist(filename)
David Scherer7aced172000-08-15 01:13:23 +0000302 return "break"
303
304 def save_a_copy(self, event):
305 filename = self.asksavefile()
306 if filename:
307 self.writefile(filename)
308 self.text.focus_set()
Chui Tey993e81a2002-11-04 03:11:10 +0000309 self.updaterecentfileslist(filename)
David Scherer7aced172000-08-15 01:13:23 +0000310 return "break"
311
312 def writefile(self, filename):
313 self.fixlastline()
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000314 chars = self.encode(self.text.get("1.0", "end-1c"))
Guido van Rossumc2f77dd2003-04-25 18:36:31 +0000315 if self.eol_convention != "\n":
316 chars = chars.replace("\n", self.eol_convention)
David Scherer7aced172000-08-15 01:13:23 +0000317 try:
Guido van Rossumc2f77dd2003-04-25 18:36:31 +0000318 f = open(filename, "wb")
David Scherer7aced172000-08-15 01:13:23 +0000319 f.write(chars)
320 f.close()
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000321 return True
David Scherer7aced172000-08-15 01:13:23 +0000322 except IOError, msg:
323 tkMessageBox.showerror("I/O Error", str(msg),
324 master=self.text)
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000325 return False
326
327 def encode(self, chars):
328 if isinstance(chars, types.StringType):
329 # This is either plain ASCII, or Tk was returning mixed-encoding
330 # text to us. Don't try to guess further.
331 return chars
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000332 # See whether there is anything non-ASCII in it.
333 # If not, no need to figure out the encoding.
334 try:
335 return chars.encode('ascii')
336 except UnicodeError:
337 pass
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000338 # If there is an encoding declared, try this first.
339 try:
340 enc = coding_spec(chars)
341 failed = None
342 except LookupError, msg:
343 failed = msg
344 enc = None
345 if enc:
346 try:
347 return chars.encode(enc)
348 except UnicodeError:
349 failed = "Invalid encoding '%s'" % enc
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000350 if failed:
351 tkMessageBox.showerror(
352 "I/O Error",
353 "%s. Saving as UTF-8" % failed,
354 master = self.text)
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000355 # If there was a UTF-8 signature, use that. This should not fail
356 if self.fileencoding == BOM_UTF8 or failed:
357 return BOM_UTF8 + chars.encode("utf-8")
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000358 # Try the original file encoding next, if any
359 if self.fileencoding:
360 try:
361 return chars.encode(self.fileencoding)
362 except UnicodeError:
363 tkMessageBox.showerror(
364 "I/O Error",
365 "Cannot save this as '%s' anymore. Saving as UTF-8" \
366 % self.fileencoding,
367 master = self.text)
368 return BOM_UTF8 + chars.encode("utf-8")
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000369 # Nothing was declared, and we had not determined an encoding
370 # on loading. Recommend an encoding line.
371 try:
372 chars = chars.encode(encoding)
373 enc = encoding
374 except UnicodeError:
375 chars = BOM_UTF8 + chars.encode("utf-8")
376 enc = "utf-8"
377 tkMessageBox.showerror(
378 "I/O Error",
379 "Non-ASCII found, yet no encoding declared. Add a line like\n"
380 "# -*- coding: %s -*- \nto your file" % enc,
381 master = self.text)
382 return chars
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000383
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000384 def fixlastline(self):
385 c = self.text.get("end-2c")
386 if c != '\n':
387 self.text.insert("end-1c", "\n")
388
Steven M. Gava7981ce52002-06-11 04:45:34 +0000389 def print_window(self, event):
390 tempfilename = None
391 if self.get_saved():
392 filename = self.filename
393 else:
394 filename = tempfilename = tempfile.mktemp()
395 if not self.writefile(filename):
396 os.unlink(tempfilename)
397 return "break"
398 platform=os.name
399 printPlatform=1
400 if platform == 'posix': #posix platform
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000401 command = idleConf.GetOption('main','General',
402 'print-command-posix')
Steven M. Gava7981ce52002-06-11 04:45:34 +0000403 command = command + " 2>&1"
404 elif platform == 'nt': #win32 platform
405 command = idleConf.GetOption('main','General','print-command-win')
406 else: #no printing for this platform
407 printPlatform=0
408 if printPlatform: #we can try to print for this platform
409 command = command % filename
410 pipe = os.popen(command, "r")
411 output = pipe.read().strip()
412 status = pipe.close()
413 if status:
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000414 output = "Printing failed (exit status 0x%x)\n" % \
415 status + output
Steven M. Gava7981ce52002-06-11 04:45:34 +0000416 if output:
417 output = "Printing command: %s\n" % repr(command) + output
418 tkMessageBox.showerror("Print status", output, master=self.text)
419 else: #no printing for this platform
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000420 message="Printing is not enabled for this platform: %s" % platform
Steven M. Gava7981ce52002-06-11 04:45:34 +0000421 tkMessageBox.showinfo("Print status", message, master=self.text)
422 return "break"
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000423
David Scherer7aced172000-08-15 01:13:23 +0000424 opendialog = None
425 savedialog = None
426
427 filetypes = [
428 ("Python and text files", "*.py *.pyw *.txt", "TEXT"),
429 ("All text files", "*", "TEXT"),
430 ("All files", "*"),
431 ]
432
433 def askopenfile(self):
434 dir, base = self.defaultfilename("open")
435 if not self.opendialog:
436 self.opendialog = tkFileDialog.Open(master=self.text,
437 filetypes=self.filetypes)
438 return self.opendialog.show(initialdir=dir, initialfile=base)
439
440 def defaultfilename(self, mode="open"):
441 if self.filename:
442 return os.path.split(self.filename)
443 else:
444 try:
445 pwd = os.getcwd()
446 except os.error:
447 pwd = ""
448 return pwd, ""
449
450 def asksavefile(self):
451 dir, base = self.defaultfilename("save")
452 if not self.savedialog:
453 self.savedialog = tkFileDialog.SaveAs(master=self.text,
454 filetypes=self.filetypes)
455 return self.savedialog.show(initialdir=dir, initialfile=base)
456
Chui Tey993e81a2002-11-04 03:11:10 +0000457 def updaterecentfileslist(self,filename):
Kurt B. Kaiserbfed3462002-12-14 04:38:51 +0000458 "Update recent file list on all editor windows"
Chui Tey993e81a2002-11-04 03:11:10 +0000459 self.editwin.UpdateRecentFilesList(filename)
460
David Scherer7aced172000-08-15 01:13:23 +0000461def test():
David Scherer7aced172000-08-15 01:13:23 +0000462 root = Tk()
463 class MyEditWin:
464 def __init__(self, text):
465 self.text = text
466 self.flist = None
467 self.text.bind("<Control-o>", self.open)
468 self.text.bind("<Control-s>", self.save)
469 self.text.bind("<Alt-s>", self.save_as)
470 self.text.bind("<Alt-z>", self.save_a_copy)
471 def get_saved(self): return 0
472 def set_saved(self, flag): pass
473 def reset_undo(self): pass
474 def open(self, event):
475 self.text.event_generate("<<open-window-from-file>>")
476 def save(self, event):
477 self.text.event_generate("<<save-window>>")
478 def save_as(self, event):
479 self.text.event_generate("<<save-window-as-file>>")
480 def save_a_copy(self, event):
481 self.text.event_generate("<<save-copy-of-window-as-file>>")
482 text = Text(root)
483 text.pack()
484 text.focus_set()
485 editwin = MyEditWin(text)
486 io = IOBinding(editwin)
487 root.mainloop()
488
489if __name__ == "__main__":
Kurt B. Kaiser7eea2712001-07-13 04:18:32 +0000490 from Tkinter import *
David Scherer7aced172000-08-15 01:13:23 +0000491 test()