6 import tkinter
.filedialog
as tkFileDialog
7 import tkinter
.messagebox
as tkMessageBox
10 from tkinter
.simpledialog
import askstring
12 from idlelib
.configHandler
import idleConf
14 from codecs
import BOM_UTF8
16 # Try setting the locale, so that we can find out
17 # what encoding to use
20 locale
.setlocale(locale
.LC_CTYPE
, "")
21 except (ImportError, locale
.Error
):
24 # Encoding for file names
25 filesystemencoding
= sys
.getfilesystemencoding() ### currently unused
27 locale_encoding
= 'ascii'
28 if sys
.platform
== 'win32':
29 # On Windows, we could use "mbcs". However, to give the user
30 # a portable encoding name, we need to find the code page
32 locale_encoding
= locale
.getdefaultlocale()[1]
33 codecs
.lookup(locale_encoding
)
38 # Different things can fail here: the locale module may not be
39 # loaded, it may not offer nl_langinfo, or CODESET, or the
40 # resulting codeset may be unknown to Python. We ignore all
41 # these problems, falling back to ASCII
42 locale_encoding
= locale
.nl_langinfo(locale
.CODESET
)
43 if locale_encoding
is None or locale_encoding
is '':
44 # situation occurs on Mac OS X
45 locale_encoding
= 'ascii'
46 codecs
.lookup(locale_encoding
)
47 except (NameError, AttributeError, LookupError):
48 # Try getdefaultlocale: it parses environment variables,
49 # which may give a clue. Unfortunately, getdefaultlocale has
50 # bugs that can cause ValueError.
52 locale_encoding
= locale
.getdefaultlocale()[1]
53 if locale_encoding
is None or locale_encoding
is '':
54 # situation occurs on Mac OS X
55 locale_encoding
= 'ascii'
56 codecs
.lookup(locale_encoding
)
57 except (ValueError, LookupError):
60 locale_encoding
= locale_encoding
.lower()
62 encoding
= locale_encoding
### KBK 07Sep07 This is used all over IDLE, check!
63 ### 'encoding' is used below in encode(), check!
65 coding_re
= re
.compile("coding[:=]\s*([-\w_.]+)")
67 def coding_spec(data
):
68 """Return the encoding declaration according to PEP 263.
70 When checking encoded data, only the first two lines should be passed
71 in to avoid a UnicodeDecodeError if the rest of the data is not unicode.
72 The first two lines would contain the encoding specification.
74 Raise a LookupError if the encoding is declared but unknown.
76 if isinstance(data
, bytes
):
77 # This encoding might be wrong. However, the coding
78 # spec must be ASCII-only, so any non-ASCII characters
79 # around here will be ignored. Decoding to Latin-1 should
80 # never fail (except for memory outage)
81 lines
= data
.decode('iso-8859-1')
84 # consider only the first two lines
86 lst
= lines
.split('\n')[:2]
88 lst
= lines
.split('\r')[:2]
92 match
= coding_re
.search(str)
99 # The standard encoding error does not indicate the encoding
100 raise LookupError("Unknown encoding: "+name
)
106 def __init__(self
, editwin
):
107 self
.editwin
= editwin
108 self
.text
= editwin
.text
109 self
.__id
_open
= self
.text
.bind("<<open-window-from-file>>", self
.open)
110 self
.__id
_save
= self
.text
.bind("<<save-window>>", self
.save
)
111 self
.__id
_saveas
= self
.text
.bind("<<save-window-as-file>>",
113 self
.__id
_savecopy
= self
.text
.bind("<<save-copy-of-window-as-file>>",
115 self
.fileencoding
= None
116 self
.__id
_print
= self
.text
.bind("<<print-window>>", self
.print_window
)
119 # Undo command bindings
120 self
.text
.unbind("<<open-window-from-file>>", self
.__id
_open
)
121 self
.text
.unbind("<<save-window>>", self
.__id
_save
)
122 self
.text
.unbind("<<save-window-as-file>>",self
.__id
_saveas
)
123 self
.text
.unbind("<<save-copy-of-window-as-file>>", self
.__id
_savecopy
)
124 self
.text
.unbind("<<print-window>>", self
.__id
_print
)
128 self
.filename_change_hook
= None
131 return self
.editwin
.get_saved()
133 def set_saved(self
, flag
):
134 self
.editwin
.set_saved(flag
)
136 def reset_undo(self
):
137 self
.editwin
.reset_undo()
139 filename_change_hook
= None
141 def set_filename_change_hook(self
, hook
):
142 self
.filename_change_hook
= hook
147 def set_filename(self
, filename
):
148 if filename
and os
.path
.isdir(filename
):
150 self
.dirname
= filename
152 self
.filename
= filename
155 if self
.filename_change_hook
:
156 self
.filename_change_hook()
158 def open(self
, event
=None, editFile
=None):
159 if self
.editwin
.flist
:
161 filename
= self
.askopenfile()
165 # If the current window has no filename and hasn't been
166 # modified, we replace its contents (no loss). Otherwise
167 # we open a new window. But we won't replace the
168 # shell window (which has an interp(reter) attribute), which
169 # gets set to "not modified" at every new prompt.
171 interp
= self
.editwin
.interp
172 except AttributeError:
174 if not self
.filename
and self
.get_saved() and not interp
:
175 self
.editwin
.flist
.open(filename
, self
.loadfile
)
177 self
.editwin
.flist
.open(filename
)
179 self
.text
.focus_set()
182 # Code for use outside IDLE:
184 reply
= self
.maybesave()
185 if reply
== "cancel":
186 self
.text
.focus_set()
189 filename
= self
.askopenfile()
193 self
.loadfile(filename
)
195 self
.text
.focus_set()
198 eol
= r
"(\r\n)|\n|\r" # \r\n (Windows), \n (UNIX), or \r (Mac)
199 eol_re
= re
.compile(eol
)
200 eol_convention
= os
.linesep
# default
202 def loadfile(self
, filename
):
204 # open the file in binary mode so that we can handle
205 # end-of-line convention ourselves.
206 f
= open(filename
,'rb')
207 two_lines
= f
.readline() + f
.readline()
211 except IOError as msg
:
212 tkMessageBox
.showerror("I/O Error", str(msg
), master
=self
.text
)
214 chars
, converted
= self
._decode
(two_lines
, bytes
)
216 tkMessageBox
.showerror("Decoding Error",
217 "File %s\nFailed to Decode" % filename
,
220 # We now convert all end-of-lines to '\n's
221 firsteol
= self
.eol_re
.search(chars
)
223 self
.eol_convention
= firsteol
.group(0)
224 chars
= self
.eol_re
.sub(r
"\n", chars
)
225 self
.text
.delete("1.0", "end")
226 self
.set_filename(None)
227 self
.text
.insert("1.0", chars
)
229 self
.set_filename(filename
)
231 # We need to save the conversion results first
232 # before being able to execute the code
233 self
.set_saved(False)
234 self
.text
.mark_set("insert", "1.0")
235 self
.text
.see("insert")
236 self
.updaterecentfileslist(filename
)
239 def _decode(self
, two_lines
, bytes
):
240 "Create a Unicode string."
242 # Check presence of a UTF-8 signature first
243 if bytes
.startswith(BOM_UTF8
):
245 chars
= bytes
[3:].decode("utf-8")
246 except UnicodeDecodeError:
247 # has UTF-8 signature, but fails to decode...
250 # Indicates that this file originally had a BOM
251 self
.fileencoding
= 'BOM'
253 # Next look for coding specification
255 enc
= coding_spec(two_lines
)
256 except LookupError as name
:
257 tkMessageBox
.showerror(
258 title
="Error loading the file",
259 message
="The encoding '%s' is not known to this Python "\
260 "installation. The file may not display correctly" % name
,
263 except UnicodeDecodeError:
267 chars
= str(bytes
, enc
)
268 self
.fileencoding
= enc
270 except UnicodeDecodeError:
274 chars
= str(bytes
, 'ascii')
275 self
.fileencoding
= None
277 except UnicodeDecodeError:
281 chars
= str(bytes
, 'utf-8')
282 self
.fileencoding
= 'utf-8'
284 except UnicodeDecodeError:
286 # Finally, try the locale's encoding. This is deprecated;
287 # the user should declare a non-ASCII encoding
289 # Wait for the editor window to appear
290 self
.editwin
.text
.update()
292 "Specify file encoding",
293 "The file's encoding is invalid for Python 3.x.\n"
294 "IDLE will convert it to UTF-8.\n"
295 "What is the current encoding of the file?",
296 initialvalue
= locale_encoding
,
297 parent
= self
.editwin
.text
)
300 chars
= str(bytes
, enc
)
301 self
.fileencoding
= None
303 except (UnicodeDecodeError, LookupError):
305 return None, False # None on failure
310 message
= "Do you want to save %s before closing?" % (
311 self
.filename
or "this untitled document")
312 m
= tkMessageBox
.Message(
313 title
="Save On Close",
315 icon
=tkMessageBox
.QUESTION
,
316 type=tkMessageBox
.YESNOCANCEL
,
321 if not self
.get_saved():
323 self
.text
.focus_set()
326 def save(self
, event
):
327 if not self
.filename
:
330 if self
.writefile(self
.filename
):
333 self
.editwin
.store_file_breaks()
334 except AttributeError: # may be a PyShell
336 self
.text
.focus_set()
339 def save_as(self
, event
):
340 filename
= self
.asksavefile()
342 if self
.writefile(filename
):
343 self
.set_filename(filename
)
346 self
.editwin
.store_file_breaks()
347 except AttributeError:
349 self
.text
.focus_set()
350 self
.updaterecentfileslist(filename
)
353 def save_a_copy(self
, event
):
354 filename
= self
.asksavefile()
356 self
.writefile(filename
)
357 self
.text
.focus_set()
358 self
.updaterecentfileslist(filename
)
361 def writefile(self
, filename
):
363 text
= self
.text
.get("1.0", "end-1c")
364 if self
.eol_convention
!= "\n":
365 text
= text
.replace("\n", self
.eol_convention
)
366 chars
= self
.encode(text
)
368 f
= open(filename
, "wb")
373 except IOError as msg
:
374 tkMessageBox
.showerror("I/O Error", str(msg
),
378 def encode(self
, chars
):
379 if isinstance(chars
, bytes
):
380 # This is either plain ASCII, or Tk was returning mixed-encoding
381 # text to us. Don't try to guess further.
383 # Preserve a BOM that might have been present on opening
384 if self
.fileencoding
== 'BOM':
385 return BOM_UTF8
+ chars
.encode("utf-8")
386 # See whether there is anything non-ASCII in it.
387 # If not, no need to figure out the encoding.
389 return chars
.encode('ascii')
392 # Check if there is an encoding declared
394 # a string, let coding_spec slice it to the first two lines
395 enc
= coding_spec(chars
)
397 except LookupError as msg
:
402 # PEP 3120: default source encoding is UTF-8
406 return chars
.encode(enc
)
408 failed
= "Invalid encoding '%s'" % enc
409 tkMessageBox
.showerror(
411 "%s.\nSaving as UTF-8" % failed
,
413 # Fallback: save as UTF-8, with BOM - ignoring the incorrect
415 return BOM_UTF8
+ chars
.encode("utf-8")
417 def fixlastline(self
):
418 c
= self
.text
.get("end-2c")
420 self
.text
.insert("end-1c", "\n")
422 def print_window(self
, event
):
423 m
= tkMessageBox
.Message(
425 message
="Print to Default Printer",
426 icon
=tkMessageBox
.QUESTION
,
427 type=tkMessageBox
.OKCANCEL
,
428 default
=tkMessageBox
.OK
,
431 if reply
!= tkMessageBox
.OK
:
432 self
.text
.focus_set()
435 saved
= self
.get_saved()
437 filename
= self
.filename
438 # shell undo is reset after every prompt, looks saved, probably isn't
439 if not saved
or filename
is None:
440 (tfd
, tempfilename
) = tempfile
.mkstemp(prefix
='IDLE_tmp_')
441 filename
= tempfilename
443 if not self
.writefile(tempfilename
):
444 os
.unlink(tempfilename
)
448 if platform
== 'posix': #posix platform
449 command
= idleConf
.GetOption('main','General',
450 'print-command-posix')
451 command
= command
+ " 2>&1"
452 elif platform
== 'nt': #win32 platform
453 command
= idleConf
.GetOption('main','General','print-command-win')
454 else: #no printing for this platform
456 if printPlatform
: #we can try to print for this platform
457 command
= command
% filename
458 pipe
= os
.popen(command
, "r")
459 # things can get ugly on NT if there is no printer available.
460 output
= pipe
.read().strip()
461 status
= pipe
.close()
463 output
= "Printing failed (exit status 0x%x)\n" % \
466 output
= "Printing command: %s\n" % repr(command
) + output
467 tkMessageBox
.showerror("Print status", output
, master
=self
.text
)
468 else: #no printing for this platform
469 message
="Printing is not enabled for this platform: %s" % platform
470 tkMessageBox
.showinfo("Print status", message
, master
=self
.text
)
472 os
.unlink(tempfilename
)
479 ("Python and text files", "*.py *.pyw *.txt", "TEXT"),
480 ("All text files", "*", "TEXT"),
484 def askopenfile(self
):
485 dir, base
= self
.defaultfilename("open")
486 if not self
.opendialog
:
487 self
.opendialog
= tkFileDialog
.Open(master
=self
.text
,
488 filetypes
=self
.filetypes
)
489 filename
= self
.opendialog
.show(initialdir
=dir, initialfile
=base
)
492 def defaultfilename(self
, mode
="open"):
494 return os
.path
.split(self
.filename
)
496 return self
.dirname
, ""
504 def asksavefile(self
):
505 dir, base
= self
.defaultfilename("save")
506 if not self
.savedialog
:
507 self
.savedialog
= tkFileDialog
.SaveAs(master
=self
.text
,
508 filetypes
=self
.filetypes
)
509 filename
= self
.savedialog
.show(initialdir
=dir, initialfile
=base
)
512 def updaterecentfileslist(self
,filename
):
513 "Update recent file list on all editor windows"
514 if self
.editwin
.flist
:
515 self
.editwin
.update_recent_files_list(filename
)
520 def __init__(self
, text
):
523 self
.text
.bind("<Control-o>", self
.open)
524 self
.text
.bind("<Control-s>", self
.save
)
525 self
.text
.bind("<Alt-s>", self
.save_as
)
526 self
.text
.bind("<Alt-z>", self
.save_a_copy
)
527 def get_saved(self
): return 0
528 def set_saved(self
, flag
): pass
529 def reset_undo(self
): pass
530 def open(self
, event
):
531 self
.text
.event_generate("<<open-window-from-file>>")
532 def save(self
, event
):
533 self
.text
.event_generate("<<save-window>>")
534 def save_as(self
, event
):
535 self
.text
.event_generate("<<save-window-as-file>>")
536 def save_a_copy(self
, event
):
537 self
.text
.event_generate("<<save-copy-of-window-as-file>>")
541 editwin
= MyEditWin(text
)
542 io
= IOBinding(editwin
)
545 if __name__
== "__main__":