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 idlelib
.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
):
35 # Encoding for file names
36 filesystemencoding
= sys
.getfilesystemencoding()
39 if sys
.platform
== 'win32':
40 # On Windows, we could use "mbcs". However, to give the user
41 # a portable encoding name, we need to find the code page
43 encoding
= locale
.getdefaultlocale()[1]
44 codecs
.lookup(encoding
)
49 # Different things can fail here: the locale module may not be
50 # loaded, it may not offer nl_langinfo, or CODESET, or the
51 # resulting codeset may be unknown to Python. We ignore all
52 # these problems, falling back to ASCII
53 encoding
= locale
.nl_langinfo(locale
.CODESET
)
54 if encoding
is None or encoding
is '':
55 # situation occurs on Mac OS X
57 codecs
.lookup(encoding
)
58 except (NameError, AttributeError, LookupError):
59 # Try getdefaultlocale well: it parses environment variables,
60 # which may give a clue. Unfortunately, getdefaultlocale has
61 # bugs that can cause ValueError.
63 encoding
= locale
.getdefaultlocale()[1]
64 if encoding
is None or encoding
is '':
65 # situation occurs on Mac OS X
67 codecs
.lookup(encoding
)
68 except (ValueError, LookupError):
71 encoding
= encoding
.lower()
73 coding_re
= re
.compile("coding[:=]\s*([-\w_.]+)")
75 class EncodingMessage(SimpleDialog
):
76 "Inform user that an encoding declaration is needed."
77 def __init__(self
, master
, enc
):
78 self
.should_edit
= False
80 self
.root
= top
= Toplevel(master
)
81 top
.bind("<Return>", self
.return_event
)
82 top
.bind("<Escape>", self
.do_ok
)
83 top
.protocol("WM_DELETE_WINDOW", self
.wm_delete_window
)
84 top
.wm_title("I/O Warning")
85 top
.wm_iconname("I/O Warning")
89 text
="Non-ASCII found, yet no encoding declared. Add a line like")
90 l1
.pack(side
=TOP
, anchor
=W
)
91 l2
= Entry(top
, font
="courier")
92 l2
.insert(0, "# -*- coding: %s -*-" % enc
)
93 # For some reason, the text is not selectable anymore if the
95 # l2['state'] = DISABLED
96 l2
.pack(side
=TOP
, anchor
= W
, fill
=X
)
97 l3
= Label(top
, text
="to your file\n"
98 "Choose OK to save this file as %s\n"
99 "Edit your general options to silence this warning" % enc
)
100 l3
.pack(side
=TOP
, anchor
= W
)
103 buttons
.pack(side
=TOP
, fill
=X
)
104 # Both return and cancel mean the same thing: do nothing
105 self
.default
= self
.cancel
= 0
106 b1
= Button(buttons
, text
="Ok", default
="active",
108 b1
.pack(side
=LEFT
, fill
=BOTH
, expand
=1)
109 b2
= Button(buttons
, text
="Edit my file",
110 command
=self
.do_edit
)
111 b2
.pack(side
=LEFT
, fill
=BOTH
, expand
=1)
113 self
._set
_transient
(master
)
121 def coding_spec(str):
122 """Return the encoding declaration according to PEP 263.
124 Raise LookupError if the encoding is declared but unknown.
126 # Only consider the first two lines
127 str = str.split("\n")[:2]
130 match
= coding_re
.search(str)
133 name
= match
.group(1)
134 # Check whether the encoding is known
139 # The standard encoding error does not indicate the encoding
140 raise LookupError, "Unknown encoding "+name
146 def __init__(self
, editwin
):
147 self
.editwin
= editwin
148 self
.text
= editwin
.text
149 self
.__id
_open
= self
.text
.bind("<<open-window-from-file>>", self
.open)
150 self
.__id
_save
= self
.text
.bind("<<save-window>>", self
.save
)
151 self
.__id
_saveas
= self
.text
.bind("<<save-window-as-file>>",
153 self
.__id
_savecopy
= self
.text
.bind("<<save-copy-of-window-as-file>>",
155 self
.fileencoding
= None
156 self
.__id
_print
= self
.text
.bind("<<print-window>>", self
.print_window
)
159 # Undo command bindings
160 self
.text
.unbind("<<open-window-from-file>>", self
.__id
_open
)
161 self
.text
.unbind("<<save-window>>", self
.__id
_save
)
162 self
.text
.unbind("<<save-window-as-file>>",self
.__id
_saveas
)
163 self
.text
.unbind("<<save-copy-of-window-as-file>>", self
.__id
_savecopy
)
164 self
.text
.unbind("<<print-window>>", self
.__id
_print
)
168 self
.filename_change_hook
= None
171 return self
.editwin
.get_saved()
173 def set_saved(self
, flag
):
174 self
.editwin
.set_saved(flag
)
176 def reset_undo(self
):
177 self
.editwin
.reset_undo()
179 filename_change_hook
= None
181 def set_filename_change_hook(self
, hook
):
182 self
.filename_change_hook
= hook
187 def set_filename(self
, filename
):
188 if filename
and os
.path
.isdir(filename
):
190 self
.dirname
= filename
192 self
.filename
= filename
195 if self
.filename_change_hook
:
196 self
.filename_change_hook()
198 def open(self
, event
=None, editFile
=None):
199 if self
.editwin
.flist
:
201 filename
= self
.askopenfile()
205 # If the current window has no filename and hasn't been
206 # modified, we replace its contents (no loss). Otherwise
207 # we open a new window. But we won't replace the
208 # shell window (which has an interp(reter) attribute), which
209 # gets set to "not modified" at every new prompt.
211 interp
= self
.editwin
.interp
212 except AttributeError:
214 if not self
.filename
and self
.get_saved() and not interp
:
215 self
.editwin
.flist
.open(filename
, self
.loadfile
)
217 self
.editwin
.flist
.open(filename
)
219 self
.text
.focus_set()
222 # Code for use outside IDLE:
224 reply
= self
.maybesave()
225 if reply
== "cancel":
226 self
.text
.focus_set()
229 filename
= self
.askopenfile()
233 self
.loadfile(filename
)
235 self
.text
.focus_set()
238 eol
= r
"(\r\n)|\n|\r" # \r\n (Windows), \n (UNIX), or \r (Mac)
239 eol_re
= re
.compile(eol
)
240 eol_convention
= os
.linesep
# Default
242 def loadfile(self
, filename
):
244 # open the file in binary mode so that we can handle
245 # end-of-line convention ourselves.
246 f
= open(filename
,'rb')
250 tkMessageBox
.showerror("I/O Error", str(msg
), master
=self
.text
)
253 chars
= self
.decode(chars
)
254 # We now convert all end-of-lines to '\n's
255 firsteol
= self
.eol_re
.search(chars
)
257 self
.eol_convention
= firsteol
.group(0)
258 if isinstance(self
.eol_convention
, unicode):
259 # Make sure it is an ASCII string
260 self
.eol_convention
= self
.eol_convention
.encode("ascii")
261 chars
= self
.eol_re
.sub(r
"\n", chars
)
263 self
.text
.delete("1.0", "end")
264 self
.set_filename(None)
265 self
.text
.insert("1.0", chars
)
267 self
.set_filename(filename
)
268 self
.text
.mark_set("insert", "1.0")
269 self
.text
.see("insert")
270 self
.updaterecentfileslist(filename
)
273 def decode(self
, chars
):
274 """Create a Unicode string
276 If that fails, let Tcl try its best
278 # Check presence of a UTF-8 signature first
279 if chars
.startswith(BOM_UTF8
):
281 chars
= chars
[3:].decode("utf-8")
283 # has UTF-8 signature, but fails to decode...
286 # Indicates that this file originally had a BOM
287 self
.fileencoding
= BOM_UTF8
289 # Next look for coding specification
291 enc
= coding_spec(chars
)
292 except LookupError, name
:
293 tkMessageBox
.showerror(
294 title
="Error loading the file",
295 message
="The encoding '%s' is not known to this Python "\
296 "installation. The file may not display correctly" % name
,
301 return unicode(chars
, enc
)
304 # If it is ASCII, we need not to record anything
306 return unicode(chars
, 'ascii')
309 # Finally, try the locale's encoding. This is deprecated;
310 # the user should declare a non-ASCII encoding
312 chars
= unicode(chars
, encoding
)
313 self
.fileencoding
= encoding
321 message
= "Do you want to save %s before closing?" % (
322 self
.filename
or "this untitled document")
323 m
= tkMessageBox
.Message(
324 title
="Save On Close",
326 icon
=tkMessageBox
.QUESTION
,
327 type=tkMessageBox
.YESNOCANCEL
,
332 if not self
.get_saved():
334 self
.text
.focus_set()
337 def save(self
, event
):
338 if not self
.filename
:
341 if self
.writefile(self
.filename
):
344 self
.editwin
.store_file_breaks()
345 except AttributeError: # may be a PyShell
347 self
.text
.focus_set()
350 def save_as(self
, event
):
351 filename
= self
.asksavefile()
353 if self
.writefile(filename
):
354 self
.set_filename(filename
)
357 self
.editwin
.store_file_breaks()
358 except AttributeError:
360 self
.text
.focus_set()
361 self
.updaterecentfileslist(filename
)
364 def save_a_copy(self
, event
):
365 filename
= self
.asksavefile()
367 self
.writefile(filename
)
368 self
.text
.focus_set()
369 self
.updaterecentfileslist(filename
)
372 def writefile(self
, filename
):
374 chars
= self
.encode(self
.text
.get("1.0", "end-1c"))
375 if self
.eol_convention
!= "\n":
376 chars
= chars
.replace("\n", self
.eol_convention
)
378 f
= open(filename
, "wb")
384 tkMessageBox
.showerror("I/O Error", str(msg
),
388 def encode(self
, chars
):
389 if isinstance(chars
, types
.StringType
):
390 # This is either plain ASCII, or Tk was returning mixed-encoding
391 # text to us. Don't try to guess further.
393 # See whether there is anything non-ASCII in it.
394 # If not, no need to figure out the encoding.
396 return chars
.encode('ascii')
399 # If there is an encoding declared, try this first.
401 enc
= coding_spec(chars
)
403 except LookupError, msg
:
408 return chars
.encode(enc
)
410 failed
= "Invalid encoding '%s'" % enc
412 tkMessageBox
.showerror(
414 "%s. Saving as UTF-8" % failed
,
416 # If there was a UTF-8 signature, use that. This should not fail
417 if self
.fileencoding
== BOM_UTF8
or failed
:
418 return BOM_UTF8
+ chars
.encode("utf-8")
419 # Try the original file encoding next, if any
420 if self
.fileencoding
:
422 return chars
.encode(self
.fileencoding
)
424 tkMessageBox
.showerror(
426 "Cannot save this as '%s' anymore. Saving as UTF-8" \
429 return BOM_UTF8
+ chars
.encode("utf-8")
430 # Nothing was declared, and we had not determined an encoding
431 # on loading. Recommend an encoding line.
432 config_encoding
= idleConf
.GetOption("main","EditorWindow",
434 if config_encoding
== 'utf-8':
435 # User has requested that we save files as UTF-8
436 return BOM_UTF8
+ chars
.encode("utf-8")
439 chars
= chars
.encode(encoding
)
441 if config_encoding
== 'locale':
444 chars
= BOM_UTF8
+ chars
.encode("utf-8")
448 dialog
= EncodingMessage(self
.editwin
.top
, enc
)
451 # User asked us to edit the file
452 encline
= "# -*- coding: %s -*-\n" % enc
453 firstline
= self
.text
.get("1.0", "2.0")
454 if firstline
.startswith("#!"):
455 # Insert encoding after #! line
456 self
.text
.insert("2.0", encline
)
458 self
.text
.insert("1.0", encline
)
459 return self
.encode(self
.text
.get("1.0", "end-1c"))
462 def fixlastline(self
):
463 c
= self
.text
.get("end-2c")
465 self
.text
.insert("end-1c", "\n")
467 def print_window(self
, event
):
468 m
= tkMessageBox
.Message(
470 message
="Print to Default Printer",
471 icon
=tkMessageBox
.QUESTION
,
472 type=tkMessageBox
.OKCANCEL
,
473 default
=tkMessageBox
.OK
,
476 if reply
!= tkMessageBox
.OK
:
477 self
.text
.focus_set()
480 saved
= self
.get_saved()
482 filename
= self
.filename
483 # shell undo is reset after every prompt, looks saved, probably isn't
484 if not saved
or filename
is None:
485 (tfd
, tempfilename
) = tempfile
.mkstemp(prefix
='IDLE_tmp_')
486 filename
= tempfilename
488 if not self
.writefile(tempfilename
):
489 os
.unlink(tempfilename
)
493 if platform
== 'posix': #posix platform
494 command
= idleConf
.GetOption('main','General',
495 'print-command-posix')
496 command
= command
+ " 2>&1"
497 elif platform
== 'nt': #win32 platform
498 command
= idleConf
.GetOption('main','General','print-command-win')
499 else: #no printing for this platform
501 if printPlatform
: #we can try to print for this platform
502 command
= command
% filename
503 pipe
= os
.popen(command
, "r")
504 # things can get ugly on NT if there is no printer available.
505 output
= pipe
.read().strip()
506 status
= pipe
.close()
508 output
= "Printing failed (exit status 0x%x)\n" % \
511 output
= "Printing command: %s\n" % repr(command
) + output
512 tkMessageBox
.showerror("Print status", output
, master
=self
.text
)
513 else: #no printing for this platform
514 message
="Printing is not enabled for this platform: %s" % platform
515 tkMessageBox
.showinfo("Print status", message
, master
=self
.text
)
517 os
.unlink(tempfilename
)
524 ("Python and text files", "*.py *.pyw *.txt", "TEXT"),
525 ("All text files", "*", "TEXT"),
529 def askopenfile(self
):
530 dir, base
= self
.defaultfilename("open")
531 if not self
.opendialog
:
532 self
.opendialog
= tkFileDialog
.Open(master
=self
.text
,
533 filetypes
=self
.filetypes
)
534 filename
= self
.opendialog
.show(initialdir
=dir, initialfile
=base
)
535 if isinstance(filename
, unicode):
536 filename
= filename
.encode(filesystemencoding
)
539 def defaultfilename(self
, mode
="open"):
541 return os
.path
.split(self
.filename
)
543 return self
.dirname
, ""
551 def asksavefile(self
):
552 dir, base
= self
.defaultfilename("save")
553 if not self
.savedialog
:
554 self
.savedialog
= tkFileDialog
.SaveAs(master
=self
.text
,
555 filetypes
=self
.filetypes
)
556 filename
= self
.savedialog
.show(initialdir
=dir, initialfile
=base
)
557 if isinstance(filename
, unicode):
558 filename
= filename
.encode(filesystemencoding
)
561 def updaterecentfileslist(self
,filename
):
562 "Update recent file list on all editor windows"
563 self
.editwin
.update_recent_files_list(filename
)
568 def __init__(self
, text
):
571 self
.text
.bind("<Control-o>", self
.open)
572 self
.text
.bind("<Control-s>", self
.save
)
573 self
.text
.bind("<Alt-s>", self
.save_as
)
574 self
.text
.bind("<Alt-z>", self
.save_a_copy
)
575 def get_saved(self
): return 0
576 def set_saved(self
, flag
): pass
577 def reset_undo(self
): pass
578 def open(self
, event
):
579 self
.text
.event_generate("<<open-window-from-file>>")
580 def save(self
, event
):
581 self
.text
.event_generate("<<save-window>>")
582 def save_as(self
, event
):
583 self
.text
.event_generate("<<save-window-as-file>>")
584 def save_a_copy(self
, event
):
585 self
.text
.event_generate("<<save-copy-of-window-as-file>>")
589 editwin
= MyEditWin(text
)
590 io
= IOBinding(editwin
)
593 if __name__
== "__main__":