PageRenderTime 55ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

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

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