blob: d321d8cb5ac67c7e86237a6b34fe7ef69fc8f233 [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
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. Kaiserddeaf112003-03-04 04:42:04 +0000277 try:
278 self.editwin.store_file_breaks()
279 except AttributeError: # may be a PyShell
280 pass
David Scherer7aced172000-08-15 01:13:23 +0000281 self.text.focus_set()
282 return "break"
283
284 def save_as(self, event):
285 filename = self.asksavefile()
286 if filename:
287 if self.writefile(filename):
288 self.set_filename(filename)
289 self.set_saved(1)
Kurt B. Kaiserddeaf112003-03-04 04:42:04 +0000290 try:
291 self.editwin.store_file_breaks()
292 except AttributeError:
293 pass
David Scherer7aced172000-08-15 01:13:23 +0000294 self.text.focus_set()
Chui Tey993e81a2002-11-04 03:11:10 +0000295 self.updaterecentfileslist(filename)
David Scherer7aced172000-08-15 01:13:23 +0000296 return "break"
297
298 def save_a_copy(self, event):
299 filename = self.asksavefile()
300 if filename:
301 self.writefile(filename)
302 self.text.focus_set()
Chui Tey993e81a2002-11-04 03:11:10 +0000303 self.updaterecentfileslist(filename)
David Scherer7aced172000-08-15 01:13:23 +0000304 return "break"
305
306 def writefile(self, filename):
307 self.fixlastline()
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000308 chars = self.encode(self.text.get("1.0", "end-1c"))
David Scherer7aced172000-08-15 01:13:23 +0000309 try:
310 f = open(filename, "w")
David Scherer7aced172000-08-15 01:13:23 +0000311 f.write(chars)
312 f.close()
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000313 return True
David Scherer7aced172000-08-15 01:13:23 +0000314 except IOError, msg:
315 tkMessageBox.showerror("I/O Error", str(msg),
316 master=self.text)
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000317 return False
318
319 def encode(self, chars):
320 if isinstance(chars, types.StringType):
321 # This is either plain ASCII, or Tk was returning mixed-encoding
322 # text to us. Don't try to guess further.
323 return chars
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000324 # See whether there is anything non-ASCII in it.
325 # If not, no need to figure out the encoding.
326 try:
327 return chars.encode('ascii')
328 except UnicodeError:
329 pass
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000330 # 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
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000342 if failed:
343 tkMessageBox.showerror(
344 "I/O Error",
345 "%s. Saving as UTF-8" % failed,
346 master = self.text)
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000347 # If there was a UTF-8 signature, use that. This should not fail
348 if self.fileencoding == BOM_UTF8 or failed:
349 return BOM_UTF8 + chars.encode("utf-8")
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000350 # Try the original file encoding next, if any
351 if self.fileencoding:
352 try:
353 return chars.encode(self.fileencoding)
354 except UnicodeError:
355 tkMessageBox.showerror(
356 "I/O Error",
357 "Cannot save this as '%s' anymore. Saving as UTF-8" \
358 % self.fileencoding,
359 master = self.text)
360 return BOM_UTF8 + chars.encode("utf-8")
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000361 # Nothing was declared, and we had not determined an encoding
362 # on loading. Recommend an encoding line.
363 try:
364 chars = chars.encode(encoding)
365 enc = encoding
366 except UnicodeError:
367 chars = BOM_UTF8 + chars.encode("utf-8")
368 enc = "utf-8"
369 tkMessageBox.showerror(
370 "I/O Error",
371 "Non-ASCII found, yet no encoding declared. Add a line like\n"
372 "# -*- coding: %s -*- \nto your file" % enc,
373 master = self.text)
374 return chars
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000375
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000376 def fixlastline(self):
377 c = self.text.get("end-2c")
378 if c != '\n':
379 self.text.insert("end-1c", "\n")
380
Steven M. Gava7981ce52002-06-11 04:45:34 +0000381 def print_window(self, event):
382 tempfilename = None
383 if self.get_saved():
384 filename = self.filename
385 else:
386 filename = tempfilename = tempfile.mktemp()
387 if not self.writefile(filename):
388 os.unlink(tempfilename)
389 return "break"
390 platform=os.name
391 printPlatform=1
392 if platform == 'posix': #posix platform
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000393 command = idleConf.GetOption('main','General',
394 'print-command-posix')
Steven M. Gava7981ce52002-06-11 04:45:34 +0000395 command = command + " 2>&1"
396 elif platform == 'nt': #win32 platform
397 command = idleConf.GetOption('main','General','print-command-win')
398 else: #no printing for this platform
399 printPlatform=0
400 if printPlatform: #we can try to print for this platform
401 command = command % filename
402 pipe = os.popen(command, "r")
403 output = pipe.read().strip()
404 status = pipe.close()
405 if status:
Kurt B. Kaiser01166da2002-09-16 22:03:37 +0000406 output = "Printing failed (exit status 0x%x)\n" % \
407 status + output
Steven M. Gava7981ce52002-06-11 04:45:34 +0000408 if output:
409 output = "Printing command: %s\n" % repr(command) + output
410 tkMessageBox.showerror("Print status", output, master=self.text)
411 else: #no printing for this platform
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000412 message="Printing is not enabled for this platform: %s" % platform
Steven M. Gava7981ce52002-06-11 04:45:34 +0000413 tkMessageBox.showinfo("Print status", message, master=self.text)
414 return "break"
Kurt B. Kaiser6655e4b2002-12-31 16:03:23 +0000415
David Scherer7aced172000-08-15 01:13:23 +0000416 opendialog = None
417 savedialog = None
418
419 filetypes = [
420 ("Python and text files", "*.py *.pyw *.txt", "TEXT"),
421 ("All text files", "*", "TEXT"),
422 ("All files", "*"),
423 ]
424
425 def askopenfile(self):
426 dir, base = self.defaultfilename("open")
427 if not self.opendialog:
428 self.opendialog = tkFileDialog.Open(master=self.text,
429 filetypes=self.filetypes)
430 return self.opendialog.show(initialdir=dir, initialfile=base)
431
432 def defaultfilename(self, mode="open"):
433 if self.filename:
434 return os.path.split(self.filename)
435 else:
436 try:
437 pwd = os.getcwd()
438 except os.error:
439 pwd = ""
440 return pwd, ""
441
442 def asksavefile(self):
443 dir, base = self.defaultfilename("save")
444 if not self.savedialog:
445 self.savedialog = tkFileDialog.SaveAs(master=self.text,
446 filetypes=self.filetypes)
447 return self.savedialog.show(initialdir=dir, initialfile=base)
448
Chui Tey993e81a2002-11-04 03:11:10 +0000449 def updaterecentfileslist(self,filename):
Kurt B. Kaiserbfed3462002-12-14 04:38:51 +0000450 "Update recent file list on all editor windows"
Chui Tey993e81a2002-11-04 03:11:10 +0000451 self.editwin.UpdateRecentFilesList(filename)
452
David Scherer7aced172000-08-15 01:13:23 +0000453def test():
David Scherer7aced172000-08-15 01:13:23 +0000454 root = Tk()
455 class MyEditWin:
456 def __init__(self, text):
457 self.text = text
458 self.flist = None
459 self.text.bind("<Control-o>", self.open)
460 self.text.bind("<Control-s>", self.save)
461 self.text.bind("<Alt-s>", self.save_as)
462 self.text.bind("<Alt-z>", self.save_a_copy)
463 def get_saved(self): return 0
464 def set_saved(self, flag): pass
465 def reset_undo(self): pass
466 def open(self, event):
467 self.text.event_generate("<<open-window-from-file>>")
468 def save(self, event):
469 self.text.event_generate("<<save-window>>")
470 def save_as(self, event):
471 self.text.event_generate("<<save-window-as-file>>")
472 def save_a_copy(self, event):
473 self.text.event_generate("<<save-copy-of-window-as-file>>")
474 text = Text(root)
475 text.pack()
476 text.focus_set()
477 editwin = MyEditWin(text)
478 io = IOBinding(editwin)
479 root.mainloop()
480
481if __name__ == "__main__":
Kurt B. Kaiser7eea2712001-07-13 04:18:32 +0000482 from Tkinter import *
David Scherer7aced172000-08-15 01:13:23 +0000483 test()