PageRenderTime 52ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/lib-python/2.7/idlelib/IOBinding.py

https://bitbucket.org/bwesterb/pypy
Python | 594 lines | 497 code | 39 blank | 58 comment | 104 complexity | cc860395247937d191cc53964bd1a06a MD5 | raw file
  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.
  7. import os
  8. import types
  9. import sys
  10. import codecs
  11. import tempfile
  12. import tkFileDialog
  13. import tkMessageBox
  14. import re
  15. from Tkinter import *
  16. from SimpleDialog import SimpleDialog
  17. from idlelib.configHandler import idleConf
  18. try:
  19. from codecs import BOM_UTF8
  20. except ImportError:
  21. # only available since Python 2.3
  22. BOM_UTF8 = '\xef\xbb\xbf'
  23. # Try setting the locale, so that we can find out
  24. # what encoding to use
  25. try:
  26. import locale
  27. locale.setlocale(locale.LC_CTYPE, "")
  28. except (ImportError, locale.Error):
  29. pass
  30. # Encoding for file names
  31. filesystemencoding = sys.getfilesystemencoding()
  32. encoding = "ascii"
  33. if sys.platform == 'win32':
  34. # On Windows, we could use "mbcs". However, to give the user
  35. # a portable encoding name, we need to find the code page
  36. try:
  37. encoding = locale.getdefaultlocale()[1]
  38. codecs.lookup(encoding)
  39. except LookupError:
  40. pass
  41. else:
  42. try:
  43. # Different things can fail here: the locale module may not be
  44. # loaded, it may not offer nl_langinfo, or CODESET, or the
  45. # resulting codeset may be unknown to Python. We ignore all
  46. # these problems, falling back to ASCII
  47. encoding = locale.nl_langinfo(locale.CODESET)
  48. if encoding is None or encoding is '':
  49. # situation occurs on Mac OS X
  50. encoding = 'ascii'
  51. codecs.lookup(encoding)
  52. except (NameError, AttributeError, LookupError):
  53. # Try getdefaultlocale well: it parses environment variables,
  54. # which may give a clue. Unfortunately, getdefaultlocale has
  55. # bugs that can cause ValueError.
  56. try:
  57. encoding = locale.getdefaultlocale()[1]
  58. if encoding is None or encoding is '':
  59. # situation occurs on Mac OS X
  60. encoding = 'ascii'
  61. codecs.lookup(encoding)
  62. except (ValueError, LookupError):
  63. pass
  64. encoding = encoding.lower()
  65. coding_re = re.compile("coding[:=]\s*([-\w_.]+)")
  66. class EncodingMessage(SimpleDialog):
  67. "Inform user that an encoding declaration is needed."
  68. def __init__(self, master, enc):
  69. self.should_edit = False
  70. self.root = top = Toplevel(master)
  71. top.bind("<Return>", self.return_event)
  72. top.bind("<Escape>", self.do_ok)
  73. top.protocol("WM_DELETE_WINDOW", self.wm_delete_window)
  74. top.wm_title("I/O Warning")
  75. top.wm_iconname("I/O Warning")
  76. self.top = top
  77. l1 = Label(top,
  78. text="Non-ASCII found, yet no encoding declared. Add a line like")
  79. l1.pack(side=TOP, anchor=W)
  80. l2 = Entry(top, font="courier")
  81. l2.insert(0, "# -*- coding: %s -*-" % enc)
  82. # For some reason, the text is not selectable anymore if the
  83. # widget is disabled.
  84. # l2['state'] = DISABLED
  85. l2.pack(side=TOP, anchor = W, fill=X)
  86. l3 = Label(top, text="to your file\n"
  87. "Choose OK to save this file as %s\n"
  88. "Edit your general options to silence this warning" % enc)
  89. l3.pack(side=TOP, anchor = W)
  90. buttons = Frame(top)
  91. buttons.pack(side=TOP, fill=X)
  92. # Both return and cancel mean the same thing: do nothing
  93. self.default = self.cancel = 0
  94. b1 = Button(buttons, text="Ok", default="active",
  95. command=self.do_ok)
  96. b1.pack(side=LEFT, fill=BOTH, expand=1)
  97. b2 = Button(buttons, text="Edit my file",
  98. command=self.do_edit)
  99. b2.pack(side=LEFT, fill=BOTH, expand=1)
  100. self._set_transient(master)
  101. def do_ok(self):
  102. self.done(0)
  103. def do_edit(self):
  104. self.done(1)
  105. def coding_spec(str):
  106. """Return the encoding declaration according to PEP 263.
  107. Raise LookupError if the encoding is declared but unknown.
  108. """
  109. # Only consider the first two lines
  110. str = str.split("\n")[:2]
  111. str = "\n".join(str)
  112. match = coding_re.search(str)
  113. if not match:
  114. return None
  115. name = match.group(1)
  116. # Check whether the encoding is known
  117. import codecs
  118. try:
  119. codecs.lookup(name)
  120. except LookupError:
  121. # The standard encoding error does not indicate the encoding
  122. raise LookupError, "Unknown encoding "+name
  123. return name
  124. class IOBinding:
  125. def __init__(self, editwin):
  126. self.editwin = editwin
  127. self.text = editwin.text
  128. self.__id_open = self.text.bind("<<open-window-from-file>>", self.open)
  129. self.__id_save = self.text.bind("<<save-window>>", self.save)
  130. self.__id_saveas = self.text.bind("<<save-window-as-file>>",
  131. self.save_as)
  132. self.__id_savecopy = self.text.bind("<<save-copy-of-window-as-file>>",
  133. self.save_a_copy)
  134. self.fileencoding = None
  135. self.__id_print = self.text.bind("<<print-window>>", self.print_window)
  136. def close(self):
  137. # Undo command bindings
  138. self.text.unbind("<<open-window-from-file>>", self.__id_open)
  139. self.text.unbind("<<save-window>>", self.__id_save)
  140. self.text.unbind("<<save-window-as-file>>",self.__id_saveas)
  141. self.text.unbind("<<save-copy-of-window-as-file>>", self.__id_savecopy)
  142. self.text.unbind("<<print-window>>", self.__id_print)
  143. # Break cycles
  144. self.editwin = None
  145. self.text = None
  146. self.filename_change_hook = None
  147. def get_saved(self):
  148. return self.editwin.get_saved()
  149. def set_saved(self, flag):
  150. self.editwin.set_saved(flag)
  151. def reset_undo(self):
  152. self.editwin.reset_undo()
  153. filename_change_hook = None
  154. def set_filename_change_hook(self, hook):
  155. self.filename_change_hook = hook
  156. filename = None
  157. dirname = None
  158. def set_filename(self, filename):
  159. if filename and os.path.isdir(filename):
  160. self.filename = None
  161. self.dirname = filename
  162. else:
  163. self.filename = filename
  164. self.dirname = None
  165. self.set_saved(1)
  166. if self.filename_change_hook:
  167. self.filename_change_hook()
  168. def open(self, event=None, editFile=None):
  169. if self.editwin.flist:
  170. if not editFile:
  171. filename = self.askopenfile()
  172. else:
  173. filename=editFile
  174. if filename:
  175. # If the current window has no filename and hasn't been
  176. # modified, we replace its contents (no loss). Otherwise
  177. # we open a new window. But we won't replace the
  178. # shell window (which has an interp(reter) attribute), which
  179. # gets set to "not modified" at every new prompt.
  180. try:
  181. interp = self.editwin.interp
  182. except AttributeError:
  183. interp = None
  184. if not self.filename and self.get_saved() and not interp:
  185. self.editwin.flist.open(filename, self.loadfile)
  186. else:
  187. self.editwin.flist.open(filename)
  188. else:
  189. self.text.focus_set()
  190. return "break"
  191. #
  192. # Code for use outside IDLE:
  193. if self.get_saved():
  194. reply = self.maybesave()
  195. if reply == "cancel":
  196. self.text.focus_set()
  197. return "break"
  198. if not editFile:
  199. filename = self.askopenfile()
  200. else:
  201. filename=editFile
  202. if filename:
  203. self.loadfile(filename)
  204. else:
  205. self.text.focus_set()
  206. return "break"
  207. eol = r"(\r\n)|\n|\r" # \r\n (Windows), \n (UNIX), or \r (Mac)
  208. eol_re = re.compile(eol)
  209. eol_convention = os.linesep # Default
  210. def loadfile(self, filename):
  211. try:
  212. # open the file in binary mode so that we can handle
  213. # end-of-line convention ourselves.
  214. f = open(filename,'rb')
  215. chars = f.read()
  216. f.close()
  217. except IOError, msg:
  218. tkMessageBox.showerror("I/O Error", str(msg), master=self.text)
  219. return False
  220. chars = self.decode(chars)
  221. # We now convert all end-of-lines to '\n's
  222. firsteol = self.eol_re.search(chars)
  223. if firsteol:
  224. self.eol_convention = firsteol.group(0)
  225. if isinstance(self.eol_convention, unicode):
  226. # Make sure it is an ASCII string
  227. self.eol_convention = self.eol_convention.encode("ascii")
  228. chars = self.eol_re.sub(r"\n", chars)
  229. self.text.delete("1.0", "end")
  230. self.set_filename(None)
  231. self.text.insert("1.0", chars)
  232. self.reset_undo()
  233. self.set_filename(filename)
  234. self.text.mark_set("insert", "1.0")
  235. self.text.yview("insert")
  236. self.updaterecentfileslist(filename)
  237. return True
  238. def decode(self, chars):
  239. """Create a Unicode string
  240. If that fails, let Tcl try its best
  241. """
  242. # Check presence of a UTF-8 signature first
  243. if chars.startswith(BOM_UTF8):
  244. try:
  245. chars = chars[3:].decode("utf-8")
  246. except UnicodeError:
  247. # has UTF-8 signature, but fails to decode...
  248. return chars
  249. else:
  250. # Indicates that this file originally had a BOM
  251. self.fileencoding = BOM_UTF8
  252. return chars
  253. # Next look for coding specification
  254. try:
  255. enc = coding_spec(chars)
  256. except LookupError, 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,
  261. master = self.text)
  262. enc = None
  263. if enc:
  264. try:
  265. return unicode(chars, enc)
  266. except UnicodeError:
  267. pass
  268. # If it is ASCII, we need not to record anything
  269. try:
  270. return unicode(chars, 'ascii')
  271. except UnicodeError:
  272. pass
  273. # Finally, try the locale's encoding. This is deprecated;
  274. # the user should declare a non-ASCII encoding
  275. try:
  276. chars = unicode(chars, encoding)
  277. self.fileencoding = encoding
  278. except UnicodeError:
  279. pass
  280. return chars
  281. def maybesave(self):
  282. if self.get_saved():
  283. return "yes"
  284. message = "Do you want to save %s before closing?" % (
  285. self.filename or "this untitled document")
  286. confirm = tkMessageBox.askyesnocancel(
  287. title="Save On Close",
  288. message=message,
  289. default=tkMessageBox.YES,
  290. master=self.text)
  291. if confirm:
  292. reply = "yes"
  293. self.save(None)
  294. if not self.get_saved():
  295. reply = "cancel"
  296. elif confirm is None:
  297. reply = "cancel"
  298. else:
  299. reply = "no"
  300. self.text.focus_set()
  301. return reply
  302. def save(self, event):
  303. if not self.filename:
  304. self.save_as(event)
  305. else:
  306. if self.writefile(self.filename):
  307. self.set_saved(True)
  308. try:
  309. self.editwin.store_file_breaks()
  310. except AttributeError: # may be a PyShell
  311. pass
  312. self.text.focus_set()
  313. return "break"
  314. def save_as(self, event):
  315. filename = self.asksavefile()
  316. if filename:
  317. if self.writefile(filename):
  318. self.set_filename(filename)
  319. self.set_saved(1)
  320. try:
  321. self.editwin.store_file_breaks()
  322. except AttributeError:
  323. pass
  324. self.text.focus_set()
  325. self.updaterecentfileslist(filename)
  326. return "break"
  327. def save_a_copy(self, event):
  328. filename = self.asksavefile()
  329. if filename:
  330. self.writefile(filename)
  331. self.text.focus_set()
  332. self.updaterecentfileslist(filename)
  333. return "break"
  334. def writefile(self, filename):
  335. self.fixlastline()
  336. chars = self.encode(self.text.get("1.0", "end-1c"))
  337. if self.eol_convention != "\n":
  338. chars = chars.replace("\n", self.eol_convention)
  339. try:
  340. f = open(filename, "wb")
  341. f.write(chars)
  342. f.flush()
  343. f.close()
  344. return True
  345. except IOError, msg:
  346. tkMessageBox.showerror("I/O Error", str(msg),
  347. master=self.text)
  348. return False
  349. def encode(self, chars):
  350. if isinstance(chars, types.StringType):
  351. # This is either plain ASCII, or Tk was returning mixed-encoding
  352. # text to us. Don't try to guess further.
  353. return chars
  354. # See whether there is anything non-ASCII in it.
  355. # If not, no need to figure out the encoding.
  356. try:
  357. return chars.encode('ascii')
  358. except UnicodeError:
  359. pass
  360. # If there is an encoding declared, try this first.
  361. try:
  362. enc = coding_spec(chars)
  363. failed = None
  364. except LookupError, msg:
  365. failed = msg
  366. enc = None
  367. if enc:
  368. try:
  369. return chars.encode(enc)
  370. except UnicodeError:
  371. failed = "Invalid encoding '%s'" % enc
  372. if failed:
  373. tkMessageBox.showerror(
  374. "I/O Error",
  375. "%s. Saving as UTF-8" % failed,
  376. master = self.text)
  377. # If there was a UTF-8 signature, use that. This should not fail
  378. if self.fileencoding == BOM_UTF8 or failed:
  379. return BOM_UTF8 + chars.encode("utf-8")
  380. # Try the original file encoding next, if any
  381. if self.fileencoding:
  382. try:
  383. return chars.encode(self.fileencoding)
  384. except UnicodeError:
  385. tkMessageBox.showerror(
  386. "I/O Error",
  387. "Cannot save this as '%s' anymore. Saving as UTF-8" \
  388. % self.fileencoding,
  389. master = self.text)
  390. return BOM_UTF8 + chars.encode("utf-8")
  391. # Nothing was declared, and we had not determined an encoding
  392. # on loading. Recommend an encoding line.
  393. config_encoding = idleConf.GetOption("main","EditorWindow",
  394. "encoding")
  395. if config_encoding == 'utf-8':
  396. # User has requested that we save files as UTF-8
  397. return BOM_UTF8 + chars.encode("utf-8")
  398. ask_user = True
  399. try:
  400. chars = chars.encode(encoding)
  401. enc = encoding
  402. if config_encoding == 'locale':
  403. ask_user = False
  404. except UnicodeError:
  405. chars = BOM_UTF8 + chars.encode("utf-8")
  406. enc = "utf-8"
  407. if not ask_user:
  408. return chars
  409. dialog = EncodingMessage(self.editwin.top, enc)
  410. dialog.go()
  411. if dialog.num == 1:
  412. # User asked us to edit the file
  413. encline = "# -*- coding: %s -*-\n" % enc
  414. firstline = self.text.get("1.0", "2.0")
  415. if firstline.startswith("#!"):
  416. # Insert encoding after #! line
  417. self.text.insert("2.0", encline)
  418. else:
  419. self.text.insert("1.0", encline)
  420. return self.encode(self.text.get("1.0", "end-1c"))
  421. return chars
  422. def fixlastline(self):
  423. c = self.text.get("end-2c")
  424. if c != '\n':
  425. self.text.insert("end-1c", "\n")
  426. def print_window(self, event):
  427. confirm = tkMessageBox.askokcancel(
  428. title="Print",
  429. message="Print to Default Printer",
  430. default=tkMessageBox.OK,
  431. master=self.text)
  432. if not confirm:
  433. self.text.focus_set()
  434. return "break"
  435. tempfilename = None
  436. saved = self.get_saved()
  437. if saved:
  438. filename = self.filename
  439. # shell undo is reset after every prompt, looks saved, probably isn't
  440. if not saved or filename is None:
  441. (tfd, tempfilename) = tempfile.mkstemp(prefix='IDLE_tmp_')
  442. filename = tempfilename
  443. os.close(tfd)
  444. if not self.writefile(tempfilename):
  445. os.unlink(tempfilename)
  446. return "break"
  447. platform = os.name
  448. printPlatform = True
  449. if platform == 'posix': #posix platform
  450. command = idleConf.GetOption('main','General',
  451. 'print-command-posix')
  452. command = command + " 2>&1"
  453. elif platform == 'nt': #win32 platform
  454. command = idleConf.GetOption('main','General','print-command-win')
  455. else: #no printing for this platform
  456. printPlatform = False
  457. if printPlatform: #we can try to print for this platform
  458. command = command % filename
  459. pipe = os.popen(command, "r")
  460. # things can get ugly on NT if there is no printer available.
  461. output = pipe.read().strip()
  462. status = pipe.close()
  463. if status:
  464. output = "Printing failed (exit status 0x%x)\n" % \
  465. status + output
  466. if output:
  467. output = "Printing command: %s\n" % repr(command) + output
  468. tkMessageBox.showerror("Print status", output, master=self.text)
  469. else: #no printing for this platform
  470. message = "Printing is not enabled for this platform: %s" % platform
  471. tkMessageBox.showinfo("Print status", message, master=self.text)
  472. if tempfilename:
  473. os.unlink(tempfilename)
  474. return "break"
  475. opendialog = None
  476. savedialog = None
  477. filetypes = [
  478. ("Python files", "*.py *.pyw", "TEXT"),
  479. ("Text files", "*.txt", "TEXT"),
  480. ("All files", "*"),
  481. ]
  482. def askopenfile(self):
  483. dir, base = self.defaultfilename("open")
  484. if not self.opendialog:
  485. self.opendialog = tkFileDialog.Open(master=self.text,
  486. filetypes=self.filetypes)
  487. filename = self.opendialog.show(initialdir=dir, initialfile=base)
  488. if isinstance(filename, unicode):
  489. filename = filename.encode(filesystemencoding)
  490. return filename
  491. def defaultfilename(self, mode="open"):
  492. if self.filename:
  493. return os.path.split(self.filename)
  494. elif self.dirname:
  495. return self.dirname, ""
  496. else:
  497. try:
  498. pwd = os.getcwd()
  499. except os.error:
  500. pwd = ""
  501. return pwd, ""
  502. def asksavefile(self):
  503. dir, base = self.defaultfilename("save")
  504. if not self.savedialog:
  505. self.savedialog = tkFileDialog.SaveAs(master=self.text,
  506. filetypes=self.filetypes)
  507. filename = self.savedialog.show(initialdir=dir, initialfile=base)
  508. if isinstance(filename, unicode):
  509. filename = filename.encode(filesystemencoding)
  510. return filename
  511. def updaterecentfileslist(self,filename):
  512. "Update recent file list on all editor windows"
  513. self.editwin.update_recent_files_list(filename)
  514. def test():
  515. root = Tk()
  516. class MyEditWin:
  517. def __init__(self, text):
  518. self.text = text
  519. self.flist = None
  520. self.text.bind("<Control-o>", self.open)
  521. self.text.bind("<Control-s>", self.save)
  522. self.text.bind("<Alt-s>", self.save_as)
  523. self.text.bind("<Alt-z>", self.save_a_copy)
  524. def get_saved(self): return 0
  525. def set_saved(self, flag): pass
  526. def reset_undo(self): pass
  527. def open(self, event):
  528. self.text.event_generate("<<open-window-from-file>>")
  529. def save(self, event):
  530. self.text.event_generate("<<save-window>>")
  531. def save_as(self, event):
  532. self.text.event_generate("<<save-window-as-file>>")
  533. def save_a_copy(self, event):
  534. self.text.event_generate("<<save-copy-of-window-as-file>>")
  535. text = Text(root)
  536. text.pack()
  537. text.focus_set()
  538. editwin = MyEditWin(text)
  539. io = IOBinding(editwin)
  540. root.mainloop()
  541. if __name__ == "__main__":
  542. test()