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.
17 from SimpleDialog
import SimpleDialog
19 from configHandler
import idleConf
22 from codecs
import BOM_UTF8
24 # only available since Python 2.3
25 BOM_UTF8
= '\xef\xbb\xbf'
27 # Try setting the locale, so that we can find out
28 # what encoding to use
31 locale
.setlocale(locale
.LC_CTYPE
, "")
32 except (ImportError, locale
.Error
):
36 if sys
.platform
== 'win32':
37 # On Windows, we could use "mbcs". However, to give the user
38 # a portable encoding name, we need to find the code page
40 encoding
= locale
.getdefaultlocale()[1]
41 codecs
.lookup(encoding
)
46 # Different things can fail here: the locale module may not be
47 # loaded, it may not offer nl_langinfo, or CODESET, or the
48 # resulting codeset may be unknown to Python. We ignore all
49 # these problems, falling back to ASCII
50 encoding
= locale
.nl_langinfo(locale
.CODESET
)
52 # situation occurs on Mac OS X
54 codecs
.lookup(encoding
)
55 except (NameError, AttributeError, LookupError):
56 # Try getdefaultlocale well: it parses environment variables,
57 # which may give a clue. Unfortunately, getdefaultlocale has
58 # bugs that can cause ValueError.
60 encoding
= locale
.getdefaultlocale()[1]
62 # situation occurs on Mac OS X
64 codecs
.lookup(encoding
)
65 except (ValueError, LookupError):
68 encoding
= encoding
.lower()
70 coding_re
= re
.compile("coding[:=]\s*([-\w_.]+)")
72 class EncodingMessage(SimpleDialog
):
73 "Inform user that an encoding declaration is needed."
74 def __init__(self
, master
, enc
):
75 self
.should_edit
= False
77 self
.root
= top
= Toplevel(master
)
78 top
.bind("<Return>", self
.return_event
)
79 top
.bind("<Escape>", self
.do_ok
)
80 top
.protocol("WM_DELETE_WINDOW", self
.wm_delete_window
)
81 top
.wm_title("I/O Warning")
82 top
.wm_iconname("I/O Warning")
86 text
="Non-ASCII found, yet no encoding declared. Add a line like")
87 l1
.pack(side
=TOP
, anchor
=W
)
88 l2
= Entry(top
, font
="courier")
89 l2
.insert(0, "# -*- coding: %s -*-" % enc
)
90 # For some reason, the text is not selectable anymore if the
92 # l2['state'] = DISABLED
93 l2
.pack(side
=TOP
, anchor
= W
, fill
=X
)
94 l3
= Label(top
, text
="to your file\n"
95 "Choose OK to save this file as %s\n"
96 "Edit your general options to silence this warning" % enc
)
97 l3
.pack(side
=TOP
, anchor
= W
)
100 buttons
.pack(side
=TOP
, fill
=X
)
101 # Both return and cancel mean the same thing: do nothing
102 self
.default
= self
.cancel
= 0
103 b1
= Button(buttons
, text
="Ok", default
="active",
105 b1
.pack(side
=LEFT
, fill
=BOTH
, expand
=1)
106 b2
= Button(buttons
, text
="Edit my file",
107 command
=self
.do_edit
)
108 b2
.pack(side
=LEFT
, fill
=BOTH
, expand
=1)
110 self
._set
_transient
(master
)
118 def coding_spec(str):
119 """Return the encoding declaration according to PEP 263.
121 Raise LookupError if the encoding is declared but unknown.
123 # Only consider the first two lines
124 str = str.split("\n")[:2]
127 match
= coding_re
.search(str)
130 name
= match
.group(1)
131 # Check whether the encoding is known
136 # The standard encoding error does not indicate the encoding
137 raise LookupError, "Unknown encoding "+name
143 def __init__(self
, editwin
):
144 self
.editwin
= editwin
145 self
.text
= editwin
.text
146 self
.__id
_open
= self
.text
.bind("<<open-window-from-file>>", self
.open)
147 self
.__id
_save
= self
.text
.bind("<<save-window>>", self
.save
)
148 self
.__id
_saveas
= self
.text
.bind("<<save-window-as-file>>",
150 self
.__id
_savecopy
= self
.text
.bind("<<save-copy-of-window-as-file>>",
152 self
.fileencoding
= None
153 self
.__id
_print
= self
.text
.bind("<<print-window>>", self
.print_window
)
156 # Undo command bindings
157 self
.text
.unbind("<<open-window-from-file>>", self
.__id
_open
)
158 self
.text
.unbind("<<save-window>>", self
.__id
_save
)
159 self
.text
.unbind("<<save-window-as-file>>",self
.__id
_saveas
)
160 self
.text
.unbind("<<save-copy-of-window-as-file>>", self
.__id
_savecopy
)
161 self
.text
.unbind("<<print-window>>", self
.__id
_print
)
165 self
.filename_change_hook
= None
168 return self
.editwin
.get_saved()
170 def set_saved(self
, flag
):
171 self
.editwin
.set_saved(flag
)
173 def reset_undo(self
):
174 self
.editwin
.reset_undo()
176 filename_change_hook
= None
178 def set_filename_change_hook(self
, hook
):
179 self
.filename_change_hook
= hook
184 def set_filename(self
, filename
):
185 if filename
and os
.path
.isdir(filename
):
187 self
.dirname
= filename
189 self
.filename
= filename
192 if self
.filename_change_hook
:
193 self
.filename_change_hook()
195 def open(self
, event
=None, editFile
=None):
196 if self
.editwin
.flist
:
198 filename
= self
.askopenfile()
202 # If the current window has no filename and hasn't been
203 # modified, we replace its contents (no loss). Otherwise
204 # we open a new window. But we won't replace the
205 # shell window (which has an interp(reter) attribute), which
206 # gets set to "not modified" at every new prompt.
208 interp
= self
.editwin
.interp
211 if not self
.filename
and self
.get_saved() and not interp
:
212 self
.editwin
.flist
.open(filename
, self
.loadfile
)
214 self
.editwin
.flist
.open(filename
)
216 self
.text
.focus_set()
219 # Code for use outside IDLE:
221 reply
= self
.maybesave()
222 if reply
== "cancel":
223 self
.text
.focus_set()
226 filename
= self
.askopenfile()
230 self
.loadfile(filename
)
232 self
.text
.focus_set()
235 eol
= r
"(\r\n)|\n|\r" # \r\n (Windows), \n (UNIX), or \r (Mac)
236 eol_re
= re
.compile(eol
)
237 eol_convention
= os
.linesep
# Default
239 def loadfile(self
, filename
):
241 # open the file in binary mode so that we can handle
242 # end-of-line convention ourselves.
243 f
= open(filename
,'rb')
247 tkMessageBox
.showerror("I/O Error", str(msg
), master
=self
.text
)
250 chars
= self
.decode(chars
)
251 # We now convert all end-of-lines to '\n's
252 firsteol
= self
.eol_re
.search(chars
)
254 self
.eol_convention
= firsteol
.group(0)
255 if isinstance(self
.eol_convention
, unicode):
256 # Make sure it is an ASCII string
257 self
.eol_convention
= self
.eol_convention
.encode("ascii")
258 chars
= self
.eol_re
.sub(r
"\n", chars
)
260 self
.text
.delete("1.0", "end")
261 self
.set_filename(None)
262 self
.text
.insert("1.0", chars
)
264 self
.set_filename(filename
)
265 self
.text
.mark_set("insert", "1.0")
266 self
.text
.see("insert")
267 self
.updaterecentfileslist(filename
)
270 def decode(self
, chars
):
271 """Create a Unicode string
273 If that fails, let Tcl try its best
275 # Check presence of a UTF-8 signature first
276 if chars
.startswith(BOM_UTF8
):
278 chars
= chars
[3:].decode("utf-8")
280 # has UTF-8 signature, but fails to decode...
283 # Indicates that this file originally had a BOM
284 self
.fileencoding
= BOM_UTF8
286 # Next look for coding specification
288 enc
= coding_spec(chars
)
289 except LookupError, name
:
290 tkMessageBox
.showerror(
291 title
="Error loading the file",
292 message
="The encoding '%s' is not known to this Python "\
293 "installation. The file may not display correctly" % name
,
298 return unicode(chars
, enc
)
301 # If it is ASCII, we need not to record anything
303 return unicode(chars
, 'ascii')
306 # Finally, try the locale's encoding. This is deprecated;
307 # the user should declare a non-ASCII encoding
309 chars
= unicode(chars
, encoding
)
310 self
.fileencoding
= encoding
318 message
= "Do you want to save %s before closing?" % (
319 self
.filename
or "this untitled document")
320 m
= tkMessageBox
.Message(
321 title
="Save On Close",
323 icon
=tkMessageBox
.QUESTION
,
324 type=tkMessageBox
.YESNOCANCEL
,
329 if not self
.get_saved():
331 self
.text
.focus_set()
334 def save(self
, event
):
335 if not self
.filename
:
338 if self
.writefile(self
.filename
):
341 self
.editwin
.store_file_breaks()
342 except AttributeError: # may be a PyShell
344 self
.text
.focus_set()
347 def save_as(self
, event
):
348 filename
= self
.asksavefile()
350 if self
.writefile(filename
):
351 self
.set_filename(filename
)
354 self
.editwin
.store_file_breaks()
355 except AttributeError:
357 self
.text
.focus_set()
358 self
.updaterecentfileslist(filename
)
361 def save_a_copy(self
, event
):
362 filename
= self
.asksavefile()
364 self
.writefile(filename
)
365 self
.text
.focus_set()
366 self
.updaterecentfileslist(filename
)
369 def writefile(self
, filename
):
371 chars
= self
.encode(self
.text
.get("1.0", "end-1c"))
372 if self
.eol_convention
!= "\n":
373 chars
= chars
.replace("\n", self
.eol_convention
)
375 f
= open(filename
, "wb")
380 tkMessageBox
.showerror("I/O Error", str(msg
),
384 def encode(self
, chars
):
385 if isinstance(chars
, types
.StringType
):
386 # This is either plain ASCII, or Tk was returning mixed-encoding
387 # text to us. Don't try to guess further.
389 # See whether there is anything non-ASCII in it.
390 # If not, no need to figure out the encoding.
392 return chars
.encode('ascii')
395 # If there is an encoding declared, try this first.
397 enc
= coding_spec(chars
)
399 except LookupError, msg
:
404 return chars
.encode(enc
)
406 failed
= "Invalid encoding '%s'" % enc
408 tkMessageBox
.showerror(
410 "%s. Saving as UTF-8" % failed
,
412 # If there was a UTF-8 signature, use that. This should not fail
413 if self
.fileencoding
== BOM_UTF8
or failed
:
414 return BOM_UTF8
+ chars
.encode("utf-8")
415 # Try the original file encoding next, if any
416 if self
.fileencoding
:
418 return chars
.encode(self
.fileencoding
)
420 tkMessageBox
.showerror(
422 "Cannot save this as '%s' anymore. Saving as UTF-8" \
425 return BOM_UTF8
+ chars
.encode("utf-8")
426 # Nothing was declared, and we had not determined an encoding
427 # on loading. Recommend an encoding line.
428 config_encoding
= idleConf
.GetOption("main","EditorWindow",
430 if config_encoding
== 'utf-8':
431 # User has requested that we save files as UTF-8
432 return BOM_UTF8
+ chars
.encode("utf-8")
435 chars
= chars
.encode(encoding
)
437 if config_encoding
== 'locale':
440 chars
= BOM_UTF8
+ chars
.encode("utf-8")
444 dialog
= EncodingMessage(self
.editwin
.top
, enc
)
447 # User asked us to edit the file
448 encline
= "# -*- coding: %s -*-\n" % enc
449 firstline
= self
.text
.get("1.0", "2.0")
450 if firstline
.startswith("#!"):
451 # Insert encoding after #! line
452 self
.text
.insert("2.0", encline
)
454 self
.text
.insert("1.0", encline
)
455 return self
.encode(self
.text
.get("1.0", "end-1c"))
458 def fixlastline(self
):
459 c
= self
.text
.get("end-2c")
461 self
.text
.insert("end-1c", "\n")
463 def print_window(self
, event
):
465 saved
= self
.get_saved()
467 filename
= self
.filename
468 # shell undo is reset after every prompt, looks saved, probably isn't
469 if not saved
or filename
is None:
470 # XXX KBK 08Jun03 Wouldn't it be better to ask the user to save?
471 (tfd
, tempfilename
) = tempfile
.mkstemp(prefix
='IDLE_tmp_')
472 filename
= tempfilename
474 if not self
.writefile(tempfilename
):
475 os
.unlink(tempfilename
)
479 if platform
== 'posix': #posix platform
480 command
= idleConf
.GetOption('main','General',
481 'print-command-posix')
482 command
= command
+ " 2>&1"
483 elif platform
== 'nt': #win32 platform
484 command
= idleConf
.GetOption('main','General','print-command-win')
485 else: #no printing for this platform
487 if printPlatform
: #we can try to print for this platform
488 command
= command
% filename
489 pipe
= os
.popen(command
, "r")
490 # things can get ugly on NT if there is no printer available.
491 output
= pipe
.read().strip()
492 status
= pipe
.close()
494 output
= "Printing failed (exit status 0x%x)\n" % \
497 output
= "Printing command: %s\n" % repr(command
) + output
498 tkMessageBox
.showerror("Print status", output
, master
=self
.text
)
499 else: #no printing for this platform
500 message
="Printing is not enabled for this platform: %s" % platform
501 tkMessageBox
.showinfo("Print status", message
, master
=self
.text
)
503 os
.unlink(tempfilename
)
510 ("Python and text files", "*.py *.pyw *.txt", "TEXT"),
511 ("All text files", "*", "TEXT"),
515 def askopenfile(self
):
516 dir, base
= self
.defaultfilename("open")
517 if not self
.opendialog
:
518 self
.opendialog
= tkFileDialog
.Open(master
=self
.text
,
519 filetypes
=self
.filetypes
)
520 return self
.opendialog
.show(initialdir
=dir, initialfile
=base
)
522 def defaultfilename(self
, mode
="open"):
524 return os
.path
.split(self
.filename
)
526 return self
.dirname
, ""
534 def asksavefile(self
):
535 dir, base
= self
.defaultfilename("save")
536 if not self
.savedialog
:
537 self
.savedialog
= tkFileDialog
.SaveAs(master
=self
.text
,
538 filetypes
=self
.filetypes
)
539 return self
.savedialog
.show(initialdir
=dir, initialfile
=base
)
541 def updaterecentfileslist(self
,filename
):
542 "Update recent file list on all editor windows"
543 self
.editwin
.UpdateRecentFilesList(filename
)
548 def __init__(self
, text
):
551 self
.text
.bind("<Control-o>", self
.open)
552 self
.text
.bind("<Control-s>", self
.save
)
553 self
.text
.bind("<Alt-s>", self
.save_as
)
554 self
.text
.bind("<Alt-z>", self
.save_a_copy
)
555 def get_saved(self
): return 0
556 def set_saved(self
, flag
): pass
557 def reset_undo(self
): pass
558 def open(self
, event
):
559 self
.text
.event_generate("<<open-window-from-file>>")
560 def save(self
, event
):
561 self
.text
.event_generate("<<save-window>>")
562 def save_as(self
, event
):
563 self
.text
.event_generate("<<save-window-as-file>>")
564 def save_a_copy(self
, event
):
565 self
.text
.event_generate("<<save-copy-of-window-as-file>>")
569 editwin
= MyEditWin(text
)
570 io
= IOBinding(editwin
)
573 if __name__
== "__main__":