Coverage for iomenu.py: 12%

311 statements  

« prev     ^ index     » next       coverage.py v7.2.5, created at 2023-05-11 13:22 -0700

1import io 

2import os 

3import shlex 

4import sys 

5import tempfile 

6import tokenize 

7 

8from tkinter import filedialog 

9from tkinter import messagebox 

10from tkinter.simpledialog import askstring 

11 

12from idlelib.config import idleConf 

13from idlelib.util import py_extensions 

14 

15py_extensions = ' '.join("*"+ext for ext in py_extensions) 

16encoding = 'utf-8' 

17errors = 'surrogatepass' if sys.platform == 'win32' else 'surrogateescape' 

18 

19 

20class IOBinding: 

21# One instance per editor Window so methods know which to save, close. 

22# Open returns focus to self.editwin if aborted. 

23# EditorWindow.open_module, others, belong here. 

24 

25 def __init__(self, editwin): 

26 self.editwin = editwin 

27 self.text = editwin.text 

28 self.__id_open = self.text.bind("<<open-window-from-file>>", self.open) 

29 self.__id_save = self.text.bind("<<save-window>>", self.save) 

30 self.__id_saveas = self.text.bind("<<save-window-as-file>>", 

31 self.save_as) 

32 self.__id_savecopy = self.text.bind("<<save-copy-of-window-as-file>>", 

33 self.save_a_copy) 

34 self.fileencoding = 'utf-8' 

35 self.__id_print = self.text.bind("<<print-window>>", self.print_window) 

36 

37 def close(self): 

38 # Undo command bindings 

39 self.text.unbind("<<open-window-from-file>>", self.__id_open) 

40 self.text.unbind("<<save-window>>", self.__id_save) 

41 self.text.unbind("<<save-window-as-file>>",self.__id_saveas) 

42 self.text.unbind("<<save-copy-of-window-as-file>>", self.__id_savecopy) 

43 self.text.unbind("<<print-window>>", self.__id_print) 

44 # Break cycles 

45 self.editwin = None 

46 self.text = None 

47 self.filename_change_hook = None 

48 

49 def get_saved(self): 

50 return self.editwin.get_saved() 

51 

52 def set_saved(self, flag): 

53 self.editwin.set_saved(flag) 

54 

55 def reset_undo(self): 

56 self.editwin.reset_undo() 

57 

58 filename_change_hook = None 

59 

60 def set_filename_change_hook(self, hook): 

61 self.filename_change_hook = hook 

62 

63 filename = None 

64 dirname = None 

65 

66 def set_filename(self, filename): 

67 if filename and os.path.isdir(filename): 

68 self.filename = None 

69 self.dirname = filename 

70 else: 

71 self.filename = filename 

72 self.dirname = None 

73 self.set_saved(1) 

74 if self.filename_change_hook: 

75 self.filename_change_hook() 

76 

77 def open(self, event=None, editFile=None): 

78 flist = self.editwin.flist 

79 # Save in case parent window is closed (ie, during askopenfile()). 

80 if flist: 

81 if not editFile: 

82 filename = self.askopenfile() 

83 else: 

84 filename=editFile 

85 if filename: 

86 # If editFile is valid and already open, flist.open will 

87 # shift focus to its existing window. 

88 # If the current window exists and is a fresh unnamed, 

89 # unmodified editor window (not an interpreter shell), 

90 # pass self.loadfile to flist.open so it will load the file 

91 # in the current window (if the file is not already open) 

92 # instead of a new window. 

93 if (self.editwin and 

94 not getattr(self.editwin, 'interp', None) and 

95 not self.filename and 

96 self.get_saved()): 

97 flist.open(filename, self.loadfile) 

98 else: 

99 flist.open(filename) 

100 else: 

101 if self.text: 

102 self.text.focus_set() 

103 return "break" 

104 

105 # Code for use outside IDLE: 

106 if self.get_saved(): 

107 reply = self.maybesave() 

108 if reply == "cancel": 

109 self.text.focus_set() 

110 return "break" 

111 if not editFile: 

112 filename = self.askopenfile() 

113 else: 

114 filename=editFile 

115 if filename: 

116 self.loadfile(filename) 

117 else: 

118 self.text.focus_set() 

119 return "break" 

120 

121 eol_convention = os.linesep # default 

122 

123 def loadfile(self, filename): 

124 try: 

125 try: 

126 with tokenize.open(filename) as f: 

127 chars = f.read() 

128 fileencoding = f.encoding 

129 eol_convention = f.newlines 

130 converted = False 

131 except (UnicodeDecodeError, SyntaxError): 

132 # Wait for the editor window to appear 

133 self.editwin.text.update() 

134 enc = askstring( 

135 "Specify file encoding", 

136 "The file's encoding is invalid for Python 3.x.\n" 

137 "IDLE will convert it to UTF-8.\n" 

138 "What is the current encoding of the file?", 

139 initialvalue='utf-8', 

140 parent=self.editwin.text) 

141 with open(filename, encoding=enc) as f: 

142 chars = f.read() 

143 fileencoding = f.encoding 

144 eol_convention = f.newlines 

145 converted = True 

146 except OSError as err: 

147 messagebox.showerror("I/O Error", str(err), parent=self.text) 

148 return False 

149 except UnicodeDecodeError: 

150 messagebox.showerror("Decoding Error", 

151 "File %s\nFailed to Decode" % filename, 

152 parent=self.text) 

153 return False 

154 

155 if not isinstance(eol_convention, str): 

156 # If the file does not contain line separators, it is None. 

157 # If the file contains mixed line separators, it is a tuple. 

158 if eol_convention is not None: 

159 messagebox.showwarning("Mixed Newlines", 

160 "Mixed newlines detected.\n" 

161 "The file will be changed on save.", 

162 parent=self.text) 

163 converted = True 

164 eol_convention = os.linesep # default 

165 

166 self.text.delete("1.0", "end") 

167 self.set_filename(None) 

168 self.fileencoding = fileencoding 

169 self.eol_convention = eol_convention 

170 self.text.insert("1.0", chars) 

171 self.reset_undo() 

172 self.set_filename(filename) 

173 if converted: 

174 # We need to save the conversion results first 

175 # before being able to execute the code 

176 self.set_saved(False) 

177 self.text.mark_set("insert", "1.0") 

178 self.text.yview("insert") 

179 self.updaterecentfileslist(filename) 

180 return True 

181 

182 def maybesave(self): 

183 if self.get_saved(): 

184 return "yes" 

185 message = "Do you want to save %s before closing?" % ( 

186 self.filename or "this untitled document") 

187 confirm = messagebox.askyesnocancel( 

188 title="Save On Close", 

189 message=message, 

190 default=messagebox.YES, 

191 parent=self.text) 

192 if confirm: 

193 reply = "yes" 

194 self.save(None) 

195 if not self.get_saved(): 

196 reply = "cancel" 

197 elif confirm is None: 

198 reply = "cancel" 

199 else: 

200 reply = "no" 

201 self.text.focus_set() 

202 return reply 

203 

204 def save(self, event): 

205 if not self.filename: 

206 self.save_as(event) 

207 else: 

208 if self.writefile(self.filename): 

209 self.set_saved(True) 

210 try: 

211 self.editwin.store_file_breaks() 

212 except AttributeError: # may be a PyShell 

213 pass 

214 self.text.focus_set() 

215 return "break" 

216 

217 def save_as(self, event): 

218 filename = self.asksavefile() 

219 if filename: 

220 if self.writefile(filename): 

221 self.set_filename(filename) 

222 self.set_saved(1) 

223 try: 

224 self.editwin.store_file_breaks() 

225 except AttributeError: 

226 pass 

227 self.text.focus_set() 

228 self.updaterecentfileslist(filename) 

229 return "break" 

230 

231 def save_a_copy(self, event): 

232 filename = self.asksavefile() 

233 if filename: 

234 self.writefile(filename) 

235 self.text.focus_set() 

236 self.updaterecentfileslist(filename) 

237 return "break" 

238 

239 def writefile(self, filename): 

240 text = self.fixnewlines() 

241 chars = self.encode(text) 

242 try: 

243 with open(filename, "wb") as f: 

244 f.write(chars) 

245 f.flush() 

246 os.fsync(f.fileno()) 

247 return True 

248 except OSError as msg: 

249 messagebox.showerror("I/O Error", str(msg), 

250 parent=self.text) 

251 return False 

252 

253 def fixnewlines(self): 

254 """Return text with os eols. 

255 

256 Add prompts if shell else final \n if missing. 

257 """ 

258 

259 if hasattr(self.editwin, "interp"): # Saving shell. 

260 text = self.editwin.get_prompt_text('1.0', self.text.index('end-1c')) 

261 else: 

262 if self.text.get("end-2c") != '\n': 

263 self.text.insert("end-1c", "\n") # Changes 'end-1c' value. 

264 text = self.text.get('1.0', "end-1c") 

265 if self.eol_convention != "\n": 

266 text = text.replace("\n", self.eol_convention) 

267 return text 

268 

269 def encode(self, chars): 

270 if isinstance(chars, bytes): 

271 # This is either plain ASCII, or Tk was returning mixed-encoding 

272 # text to us. Don't try to guess further. 

273 return chars 

274 # Preserve a BOM that might have been present on opening 

275 if self.fileencoding == 'utf-8-sig': 

276 return chars.encode('utf-8-sig') 

277 # See whether there is anything non-ASCII in it. 

278 # If not, no need to figure out the encoding. 

279 try: 

280 return chars.encode('ascii') 

281 except UnicodeEncodeError: 

282 pass 

283 # Check if there is an encoding declared 

284 try: 

285 encoded = chars.encode('ascii', 'replace') 

286 enc, _ = tokenize.detect_encoding(io.BytesIO(encoded).readline) 

287 return chars.encode(enc) 

288 except SyntaxError as err: 

289 failed = str(err) 

290 except UnicodeEncodeError: 

291 failed = "Invalid encoding '%s'" % enc 

292 messagebox.showerror( 

293 "I/O Error", 

294 "%s.\nSaving as UTF-8" % failed, 

295 parent=self.text) 

296 # Fallback: save as UTF-8, with BOM - ignoring the incorrect 

297 # declared encoding 

298 return chars.encode('utf-8-sig') 

299 

300 def print_window(self, event): 

301 confirm = messagebox.askokcancel( 

302 title="Print", 

303 message="Print to Default Printer", 

304 default=messagebox.OK, 

305 parent=self.text) 

306 if not confirm: 

307 self.text.focus_set() 

308 return "break" 

309 tempfilename = None 

310 saved = self.get_saved() 

311 if saved: 

312 filename = self.filename 

313 # shell undo is reset after every prompt, looks saved, probably isn't 

314 if not saved or filename is None: 

315 (tfd, tempfilename) = tempfile.mkstemp(prefix='IDLE_tmp_') 

316 filename = tempfilename 

317 os.close(tfd) 

318 if not self.writefile(tempfilename): 

319 os.unlink(tempfilename) 

320 return "break" 

321 platform = os.name 

322 printPlatform = True 

323 if platform == 'posix': #posix platform 

324 command = idleConf.GetOption('main','General', 

325 'print-command-posix') 

326 command = command + " 2>&1" 

327 elif platform == 'nt': #win32 platform 

328 command = idleConf.GetOption('main','General','print-command-win') 

329 else: #no printing for this platform 

330 printPlatform = False 

331 if printPlatform: #we can try to print for this platform 

332 command = command % shlex.quote(filename) 

333 pipe = os.popen(command, "r") 

334 # things can get ugly on NT if there is no printer available. 

335 output = pipe.read().strip() 

336 status = pipe.close() 

337 if status: 

338 output = "Printing failed (exit status 0x%x)\n" % \ 

339 status + output 

340 if output: 

341 output = "Printing command: %s\n" % repr(command) + output 

342 messagebox.showerror("Print status", output, parent=self.text) 

343 else: #no printing for this platform 

344 message = "Printing is not enabled for this platform: %s" % platform 

345 messagebox.showinfo("Print status", message, parent=self.text) 

346 if tempfilename: 

347 os.unlink(tempfilename) 

348 return "break" 

349 

350 opendialog = None 

351 savedialog = None 

352 

353 filetypes = ( 

354 ("Python files", py_extensions, "TEXT"), 

355 ("Text files", "*.txt", "TEXT"), 

356 ("All files", "*"), 

357 ) 

358 

359 defaultextension = '.py' if sys.platform == 'darwin' else '' 

360 

361 def askopenfile(self): 

362 dir, base = self.defaultfilename("open") 

363 if not self.opendialog: 

364 self.opendialog = filedialog.Open(parent=self.text, 

365 filetypes=self.filetypes) 

366 filename = self.opendialog.show(initialdir=dir, initialfile=base) 

367 return filename 

368 

369 def defaultfilename(self, mode="open"): 

370 if self.filename: 

371 return os.path.split(self.filename) 

372 elif self.dirname: 

373 return self.dirname, "" 

374 else: 

375 try: 

376 pwd = os.getcwd() 

377 except OSError: 

378 pwd = "" 

379 return pwd, "" 

380 

381 def asksavefile(self): 

382 dir, base = self.defaultfilename("save") 

383 if not self.savedialog: 

384 self.savedialog = filedialog.SaveAs( 

385 parent=self.text, 

386 filetypes=self.filetypes, 

387 defaultextension=self.defaultextension) 

388 filename = self.savedialog.show(initialdir=dir, initialfile=base) 

389 return filename 

390 

391 def updaterecentfileslist(self,filename): 

392 "Update recent file list on all editor windows" 

393 if self.editwin.flist: 

394 self.editwin.update_recent_files_list(filename) 

395 

396def _io_binding(parent): # htest # 

397 from tkinter import Toplevel, Text 

398 

399 root = Toplevel(parent) 

400 root.title("Test IOBinding") 

401 x, y = map(int, parent.geometry().split('+')[1:]) 

402 root.geometry("+%d+%d" % (x, y + 175)) 

403 class MyEditWin: 

404 def __init__(self, text): 

405 self.text = text 

406 self.flist = None 

407 self.text.bind("<Control-o>", self.open) 

408 self.text.bind('<Control-p>', self.print) 

409 self.text.bind("<Control-s>", self.save) 

410 self.text.bind("<Alt-s>", self.saveas) 

411 self.text.bind('<Control-c>', self.savecopy) 

412 def get_saved(self): return 0 

413 def set_saved(self, flag): pass 

414 def reset_undo(self): pass 

415 def open(self, event): 

416 self.text.event_generate("<<open-window-from-file>>") 

417 def print(self, event): 

418 self.text.event_generate("<<print-window>>") 

419 def save(self, event): 

420 self.text.event_generate("<<save-window>>") 

421 def saveas(self, event): 

422 self.text.event_generate("<<save-window-as-file>>") 

423 def savecopy(self, event): 

424 self.text.event_generate("<<save-copy-of-window-as-file>>") 

425 

426 text = Text(root) 

427 text.pack() 

428 text.focus_set() 

429 editwin = MyEditWin(text) 

430 IOBinding(editwin) 

431 

432if __name__ == "__main__": 432 ↛ 433line 432 didn't jump to line 433, because the condition on line 432 was never true

433 from unittest import main 

434 main('idlelib.idle_test.test_iomenu', verbosity=2, exit=False) 

435 

436 from idlelib.idle_test.htest import run 

437 run(_io_binding)