Coverage for C:\Repos\leo-editor\leo\commands\commanderFileCommands.py: 20%

658 statements  

« prev     ^ index     » next       coverage.py v6.4, created at 2022-05-24 10:21 -0500

1# -*- coding: utf-8 -*- 

2#@+leo-ver=5-thin 

3#@+node:ekr.20171123095353.1: * @file ../commands/commanderFileCommands.py 

4#@@first 

5"""File commands that used to be defined in leoCommands.py""" 

6import os 

7import sys 

8import time 

9from leo.core import leoGlobals as g 

10from leo.core import leoImport 

11#@+others 

12#@+node:ekr.20170221033738.1: ** c_file.reloadSettings & helper 

13@g.commander_command('reload-settings') 

14def reloadSettings(self, event=None): 

15 """Reload settings in all commanders, or just c.""" 

16 lm = g.app.loadManager 

17 # Save any changes so they can be seen. 

18 for c2 in g.app.commanders(): 

19 if c2.isChanged(): 

20 c2.save() 

21 # Read leoSettings.leo and myLeoSettings.leo, using a null gui. 

22 lm.readGlobalSettingsFiles() 

23 for c in g.app.commanders(): 

24 # Read the local file, using a null gui. 

25 previousSettings = lm.getPreviousSettings(fn=c.mFileName) 

26 # Init the config classes. 

27 c.initSettings(previousSettings) 

28 # Init the commander config ivars. 

29 c.initConfigSettings() 

30 # Reload settings in all configurable classes 

31 c.reloadConfigurableSettings() 

32#@+node:ekr.20170221034501.1: *3* function: reloadSettingsHelper 

33def reloadSettingsHelper(c): 

34 """ 

35 Reload settings in all commanders, or just c. 

36 

37 A helper function for reload-settings and reload-all-settings. 

38 """ 

39 lm = g.app.loadManager 

40 # Save any changes so they can be seen. 

41 for c2 in g.app.commanders(): 

42 if c2.isChanged(): 

43 c2.save() 

44 # Read leoSettings.leo and myLeoSettings.leo, using a null gui. 

45 lm.readGlobalSettingsFiles() 

46 for c in g.app.commanders(): 

47 # Read the local file, using a null gui. 

48 previousSettings = lm.getPreviousSettings(fn=c.mFileName) 

49 # Init the config classes. 

50 c.initSettings(previousSettings) 

51 # Init the commander config ivars. 

52 c.initConfigSettings() 

53 # Reload settings in all configurable classes 

54 c.reloadConfigurableSettings() 

55#@+node:ekr.20200422075655.1: ** c_file.restartLeo 

56@g.commander_command('restart-leo') 

57def restartLeo(self, event=None): 

58 """Restart Leo, reloading all presently open outlines.""" 

59 c, lm = self, g.app.loadManager 

60 trace = 'shutdown' in g.app.debug 

61 # 1. Write .leoRecentFiles.txt. 

62 g.app.recentFilesManager.writeRecentFilesFile(c) 

63 # 2. Abort the restart if the user veto's any close. 

64 for c in g.app.commanders(): 

65 if c.changed: 

66 veto = False 

67 try: 

68 c.promptingForClose = True 

69 veto = c.frame.promptForSave() 

70 finally: 

71 c.promptingForClose = False 

72 if veto: 

73 g.es_print('Cancelling restart-leo command') 

74 return 

75 # 3. Officially begin the restart process. A flag for efc.ask. 

76 g.app.restarting = True # #1240. 

77 # 4. Save session data. 

78 if g.app.sessionManager: 

79 g.app.sessionManager.save_snapshot() 

80 # 5. Close all unsaved outlines. 

81 g.app.setLog(None) # Kill the log. 

82 for c in g.app.commanders(): 

83 frame = c.frame 

84 # This is similar to g.app.closeLeoWindow. 

85 g.doHook("close-frame", c=c) 

86 # Save the window state 

87 g.app.commander_cacher.commit() # store cache, but don't close it. 

88 # This may remove frame from the window list. 

89 if frame in g.app.windowList: 

90 g.app.destroyWindow(frame) 

91 g.app.windowList.remove(frame) 

92 else: 

93 # #69. 

94 g.app.forgetOpenFile(fn=c.fileName()) 

95 # 6. Complete the shutdown. 

96 g.app.finishQuit() 

97 # 7. Restart, restoring the original command line. 

98 args = ['-c'] + lm.old_argv 

99 if trace: 

100 g.trace('restarting with args', args) 

101 sys.stdout.flush() 

102 sys.stderr.flush() 

103 os.execv(sys.executable, args) 

104#@+node:ekr.20031218072017.2820: ** c_file.top level 

105#@+node:ekr.20031218072017.2833: *3* c_file.close 

106@g.commander_command('close-window') 

107def close(self, event=None, new_c=None): 

108 """Close the Leo window, prompting to save it if it has been changed.""" 

109 g.app.closeLeoWindow(self.frame, new_c=new_c) 

110#@+node:ekr.20110530124245.18245: *3* c_file.importAnyFile & helper 

111@g.commander_command('import-file') 

112def importAnyFile(self, event=None): 

113 """Import one or more files.""" 

114 c = self 

115 ic = c.importCommands 

116 types = [ 

117 ("All files", "*"), 

118 ("C/C++ files", "*.c"), 

119 ("C/C++ files", "*.cpp"), 

120 ("C/C++ files", "*.h"), 

121 ("C/C++ files", "*.hpp"), 

122 ("FreeMind files", "*.mm.html"), 

123 ("Java files", "*.java"), 

124 ("JavaScript files", "*.js"), 

125 # ("JSON files", "*.json"), 

126 ("Mindjet files", "*.csv"), 

127 ("MORE files", "*.MORE"), 

128 ("Lua files", "*.lua"), 

129 ("Pascal files", "*.pas"), 

130 ("Python files", "*.py"), 

131 ("Text files", "*.txt"), 

132 ] 

133 names = g.app.gui.runOpenFileDialog(c, 

134 title="Import File", 

135 filetypes=types, 

136 defaultextension=".py", 

137 multiple=True) 

138 c.bringToFront() 

139 if names: 

140 g.chdir(names[0]) 

141 else: 

142 names = [] 

143 if not names: 

144 if g.unitTesting: 

145 # a kludge for unit testing. 

146 c.init_error_dialogs() 

147 c.raise_error_dialogs(kind='read') 

148 return 

149 # New in Leo 4.9: choose the type of import based on the extension. 

150 c.init_error_dialogs() 

151 derived = [z for z in names if c.looksLikeDerivedFile(z)] 

152 others = [z for z in names if z not in derived] 

153 if derived: 

154 ic.importDerivedFiles(parent=c.p, paths=derived) 

155 for fn in others: 

156 junk, ext = g.os_path_splitext(fn) 

157 ext = ext.lower() # #1522 

158 if ext.startswith('.'): 

159 ext = ext[1:] 

160 if ext == 'csv': 

161 ic.importMindMap([fn]) 

162 elif ext in ('cw', 'cweb'): 

163 ic.importWebCommand([fn], "cweb") 

164 # Not useful. Use @auto x.json instead. 

165 # elif ext == 'json': 

166 # ic.importJSON([fn]) 

167 elif fn.endswith('mm.html'): 

168 ic.importFreeMind([fn]) 

169 elif ext in ('nw', 'noweb'): 

170 ic.importWebCommand([fn], "noweb") 

171 elif ext == 'more': 

172 leoImport.MORE_Importer(c).import_file(fn) # #1522. 

173 elif ext == 'txt': 

174 # #1522: Create an @edit node. 

175 import_txt_file(c, fn) 

176 else: 

177 # Make *sure* that parent.b is empty. 

178 last = c.lastTopLevel() 

179 parent = last.insertAfter() 

180 parent.v.h = 'Imported Files' 

181 ic.importFilesCommand( 

182 files=[fn], 

183 parent=parent, 

184 # Experimental: attempt to use permissive section ref logic. 

185 treeType='@auto', # was '@clean' 

186 ) 

187 c.redraw() 

188 c.raise_error_dialogs(kind='read') 

189 

190g.command_alias('importAtFile', importAnyFile) 

191g.command_alias('importAtRoot', importAnyFile) 

192g.command_alias('importCWEBFiles', importAnyFile) 

193g.command_alias('importDerivedFile', importAnyFile) 

194g.command_alias('importFlattenedOutline', importAnyFile) 

195g.command_alias('importMOREFiles', importAnyFile) 

196g.command_alias('importNowebFiles', importAnyFile) 

197g.command_alias('importTabFiles', importAnyFile) 

198#@+node:ekr.20200306043104.1: *4* function: import_txt_file 

199def import_txt_file(c, fn): 

200 """Import the .txt file into a new node.""" 

201 u = c.undoer 

202 g.setGlobalOpenDir(fn) 

203 undoData = u.beforeInsertNode(c.p) 

204 last = c.lastTopLevel() 

205 p = last.insertAfter() 

206 p.h = f"@edit {fn}" 

207 s, e = g.readFileIntoString(fn, kind='@edit') 

208 p.b = s 

209 u.afterInsertNode(p, 'Import', undoData) 

210 c.setChanged() 

211 c.redraw(p) 

212#@+node:ekr.20031218072017.1623: *3* c_file.new 

213@g.commander_command('file-new') 

214@g.commander_command('new') 

215def new(self, event=None, gui=None): 

216 """Create a new Leo window.""" 

217 t1 = time.process_time() 

218 from leo.core import leoApp 

219 lm = g.app.loadManager 

220 old_c = self 

221 # Clean out the update queue so it won't interfere with the new window. 

222 self.outerUpdate() 

223 # Supress redraws until later. 

224 g.app.disable_redraw = True 

225 # Send all log messages to the new frame. 

226 g.app.setLog(None) 

227 g.app.lockLog() 

228 # Retain all previous settings. Very important for theme code. 

229 t2 = time.process_time() 

230 c = g.app.newCommander( 

231 fileName=None, 

232 gui=gui, 

233 previousSettings=leoApp.PreviousSettings( 

234 settingsDict=lm.globalSettingsDict, 

235 shortcutsDict=lm.globalBindingsDict, 

236 )) 

237 t3 = time.process_time() 

238 frame = c.frame 

239 g.app.unlockLog() 

240 if not old_c: 

241 frame.setInitialWindowGeometry() 

242 # #1643: This doesn't work. 

243 # g.app.restoreWindowState(c) 

244 frame.deiconify() 

245 frame.lift() 

246 # Resize the _new_ frame. 

247 frame.resizePanesToRatio(frame.ratio, frame.secondary_ratio) 

248 c.frame.createFirstTreeNode() 

249 lm.createMenu(c) 

250 lm.finishOpen(c) 

251 g.app.writeWaitingLog(c) 

252 g.doHook("new", old_c=old_c, c=c, new_c=c) 

253 c.setLog() 

254 c.clearChanged() # Fix #387: Clear all dirty bits. 

255 g.app.disable_redraw = False 

256 c.redraw() 

257 t4 = time.process_time() 

258 if 'speed' in g.app.debug: 

259 g.trace() 

260 print( 

261 f" 1: {t2-t1:5.2f}\n" # 0.00 sec. 

262 f" 2: {t3-t2:5.2f}\n" # 0.36 sec: c.__init__ 

263 f" 3: {t4-t3:5.2f}\n" # 0.17 sec: Everything else. 

264 f"total: {t4-t1:5.2f}" 

265 ) 

266 return c # For unit tests and scripts. 

267#@+node:ekr.20031218072017.2821: *3* c_file.open_outline 

268@g.commander_command('open-outline') 

269def open_outline(self, event=None): 

270 """Open a Leo window containing the contents of a .leo file.""" 

271 c = self 

272 #@+others # Defines open_completer function. 

273 #@+node:ekr.20190518121302.1: *4* function: open_completer 

274 def open_completer(c, closeFlag, fileName): 

275 

276 c.bringToFront() 

277 c.init_error_dialogs() 

278 ok = False 

279 if fileName: 

280 if g.app.loadManager.isLeoFile(fileName): 

281 c2 = g.openWithFileName(fileName, old_c=c) 

282 if c2: 

283 # Fix #579: Key bindings don't take for commands defined in plugins. 

284 c2.k.makeAllBindings() 

285 g.chdir(fileName) 

286 g.setGlobalOpenDir(fileName) 

287 if c2 and closeFlag: 

288 g.app.destroyWindow(c.frame) 

289 elif c.looksLikeDerivedFile(fileName): 

290 # Create an @file node for files containing Leo sentinels. 

291 ok = c.importCommands.importDerivedFiles(parent=c.p, 

292 paths=[fileName], command='Open') 

293 else: 

294 # otherwise, create an @edit node. 

295 ok = c.createNodeFromExternalFile(fileName) 

296 c.raise_error_dialogs(kind='write') 

297 g.app.runAlreadyOpenDialog(c) 

298 # openWithFileName sets focus if ok. 

299 if not ok: 

300 c.initialFocusHelper() 

301 #@-others 

302 

303 # 

304 # Close the window if this command completes successfully? 

305 

306 closeFlag = ( 

307 c.frame.startupWindow and # The window was open on startup 

308 # The window has never been changed 

309 not c.changed and not c.frame.saved and 

310 # Only one untitled window has ever been opened 

311 g.app.numberOfUntitledWindows == 1 

312 ) 

313 table = [ 

314 ("Leo files", "*.leo *.db"), 

315 ("Python files", "*.py"), 

316 ("All files", "*"), 

317 ] 

318 fileName = ''.join(c.k.givenArgs) 

319 if fileName: 

320 open_completer(c, closeFlag, fileName) 

321 return 

322 # Equivalent to legacy code. 

323 fileName = g.app.gui.runOpenFileDialog(c, 

324 defaultextension=g.defaultLeoFileExtension(c), 

325 filetypes=table, 

326 title="Open", 

327 ) 

328 open_completer(c, closeFlag, fileName) 

329#@+node:ekr.20140717074441.17772: *3* c_file.refreshFromDisk 

330# refresh_pattern = re.compile(r'^(@[\w-]+)') 

331 

332@g.commander_command('refresh-from-disk') 

333def refreshFromDisk(self, event=None): 

334 """Refresh an @<file> node from disk.""" 

335 c, p, u = self, self.p, self.undoer 

336 c.nodeConflictList = [] 

337 fn = p.anyAtFileNodeName() 

338 shouldDelete = c.sqlite_connection is None 

339 if not fn: 

340 g.warning(f"not an @<file> node: {p.h!r}") 

341 return 

342 # #1603. 

343 if os.path.isdir(fn): 

344 g.warning(f"not a file: {fn!r}") 

345 return 

346 b = u.beforeChangeTree(p) 

347 redraw_flag = True 

348 at = c.atFileCommands 

349 # Fix bug 1090950 refresh from disk: cut node ressurection. 

350 c.recreateGnxDict() 

351 i = g.skip_id(p.h, 0, chars='@') 

352 word = p.h[0:i] 

353 if word == '@auto': 

354 # This includes @auto-* 

355 if shouldDelete: 

356 p.v._deleteAllChildren() 

357 # Fix #451: refresh-from-disk selects wrong node. 

358 p = at.readOneAtAutoNode(p) 

359 elif word in ('@thin', '@file'): 

360 if shouldDelete: 

361 p.v._deleteAllChildren() 

362 at.read(p) 

363 elif word == '@clean': 

364 # Wishlist 148: use @auto parser if the node is empty. 

365 if p.b.strip() or p.hasChildren(): 

366 at.readOneAtCleanNode(p) 

367 else: 

368 # Fix #451: refresh-from-disk selects wrong node. 

369 p = at.readOneAtAutoNode(p) 

370 elif word == '@shadow': 

371 if shouldDelete: 

372 p.v._deleteAllChildren() 

373 at.read(p) 

374 elif word == '@edit': 

375 at.readOneAtEditNode(fn, p) # Always deletes children. 

376 elif word == '@asis': 

377 # Fix #1067. 

378 at.readOneAtAsisNode(fn, p) # Always deletes children. 

379 else: 

380 g.es_print(f"can not refresh from disk\n{p.h!r}") 

381 redraw_flag = False 

382 if redraw_flag: 

383 # Fix #451: refresh-from-disk selects wrong node. 

384 c.selectPosition(p) 

385 u.afterChangeTree(p, command='refresh-from-disk', bunch=b) 

386 # Create the 'Recovered Nodes' tree. 

387 c.fileCommands.handleNodeConflicts() 

388 c.redraw() 

389#@+node:ekr.20210610083257.1: *3* c_file.pwd 

390@g.commander_command('pwd') 

391def pwd_command(self, event=None): 

392 """Print the current working directory.""" 

393 g.es_print('pwd:', os.getcwd()) 

394#@+node:ekr.20031218072017.2834: *3* c_file.save 

395@g.commander_command('save') 

396@g.commander_command('file-save') 

397@g.commander_command('save-file') 

398def save(self, event=None, fileName=None): 

399 """ 

400 Save a Leo outline to a file, using the existing file name unless 

401 the fileName kwarg is given. 

402 

403 kwarg: a file name, for use by scripts using Leo's bridge. 

404 """ 

405 c = self 

406 p = c.p 

407 # Do this now: w may go away. 

408 w = g.app.gui.get_focus(c) 

409 inBody = g.app.gui.widget_name(w).startswith('body') 

410 if inBody: 

411 p.saveCursorAndScroll() 

412 if g.app.disableSave: 

413 g.es("save commands disabled", color="purple") 

414 return 

415 c.init_error_dialogs() 

416 # 2013/09/28: use the fileName keyword argument if given. 

417 # This supports the leoBridge. 

418 # Make sure we never pass None to the ctor. 

419 if fileName: 

420 c.frame.title = g.computeWindowTitle(fileName) 

421 c.mFileName = fileName 

422 if not c.mFileName: 

423 c.frame.title = "" 

424 c.mFileName = "" 

425 if c.mFileName: 

426 # Calls c.clearChanged() if no error. 

427 g.app.syntax_error_files = [] 

428 c.fileCommands.save(c.mFileName) 

429 c.syntaxErrorDialog() 

430 else: 

431 root = c.rootPosition() 

432 if not root.next() and root.isAtEditNode(): 

433 # There is only a single @edit node in the outline. 

434 # A hack to allow "quick edit" of non-Leo files. 

435 # See https://bugs.launchpad.net/leo-editor/+bug/381527 

436 fileName = None 

437 # Write the @edit node if needed. 

438 if root.isDirty(): 

439 c.atFileCommands.writeOneAtEditNode(root) 

440 c.clearChanged() # Clears all dirty bits. 

441 else: 

442 fileName = ''.join(c.k.givenArgs) 

443 if not fileName: 

444 fileName = g.app.gui.runSaveFileDialog(c, 

445 title="Save", 

446 filetypes=[("Leo files", "*.leo *.db"),], 

447 defaultextension=g.defaultLeoFileExtension(c)) 

448 c.bringToFront() 

449 if fileName: 

450 # Don't change mFileName until the dialog has suceeded. 

451 c.mFileName = g.ensure_extension(fileName, g.defaultLeoFileExtension(c)) 

452 c.frame.title = c.computeWindowTitle(c.mFileName) 

453 c.frame.setTitle(c.computeWindowTitle(c.mFileName)) 

454 c.openDirectory = c.frame.openDirectory = g.os_path_dirname(c.mFileName) 

455 if hasattr(c.frame, 'top'): 

456 c.frame.top.leo_master.setTabName(c, c.mFileName) 

457 c.fileCommands.save(c.mFileName) 

458 g.app.recentFilesManager.updateRecentFiles(c.mFileName) 

459 g.chdir(c.mFileName) 

460 # FileCommands.save calls c.redraw_after_icons_changed() 

461 c.raise_error_dialogs(kind='write') 

462 # *Safely* restore focus, without using the old w directly. 

463 if inBody: 

464 c.bodyWantsFocus() 

465 p.restoreCursorAndScroll() 

466 else: 

467 c.treeWantsFocus() 

468#@+node:ekr.20110228162720.13980: *3* c_file.saveAll 

469@g.commander_command('save-all') 

470def saveAll(self, event=None): 

471 """Save all open tabs windows/tabs.""" 

472 c = self 

473 c.save() # Force a write of the present window. 

474 for f in g.app.windowList: 

475 c2 = f.c 

476 if c2 != c and c2.isChanged(): 

477 c2.save() 

478 # Restore the present tab. 

479 dw = c.frame.top # A DynamicWindow 

480 dw.select(c) 

481#@+node:ekr.20031218072017.2835: *3* c_file.saveAs 

482@g.commander_command('save-as') 

483@g.commander_command('file-save-as') 

484@g.commander_command('save-file-as') 

485def saveAs(self, event=None, fileName=None): 

486 """ 

487 Save a Leo outline to a file, prompting for a new filename unless the 

488 fileName kwarg is given. 

489 

490 kwarg: a file name, for use by file-save-as-zipped, 

491 file-save-as-unzipped and scripts using Leo's bridge. 

492 """ 

493 c, p = self, self.p 

494 # Do this now: w may go away. 

495 w = g.app.gui.get_focus(c) 

496 inBody = g.app.gui.widget_name(w).startswith('body') 

497 if inBody: 

498 p.saveCursorAndScroll() 

499 if g.app.disableSave: 

500 g.es("save commands disabled", color="purple") 

501 return 

502 c.init_error_dialogs() 

503 # 2013/09/28: add fileName keyword arg for leoBridge scripts. 

504 if fileName: 

505 c.frame.title = g.computeWindowTitle(fileName) 

506 c.mFileName = fileName 

507 # Make sure we never pass None to the ctor. 

508 if not c.mFileName: 

509 c.frame.title = "" 

510 if not fileName: 

511 fileName = ''.join(c.k.givenArgs) 

512 if not fileName: 

513 fileName = g.app.gui.runSaveFileDialog(c, 

514 title="Save As", 

515 filetypes=[("Leo files", "*.leo *.db *.leojs"),], 

516 defaultextension=g.defaultLeoFileExtension(c)) 

517 c.bringToFront() 

518 if fileName: 

519 # #998090: save file as doesn't remove entry from open file list. 

520 g.trace(fileName) 

521 if c.mFileName: 

522 g.app.forgetOpenFile(c.mFileName) 

523 # Don't change mFileName until the dialog has suceeded. 

524 if fileName.endswith(('.leo', '.db', '.leojs')): 

525 c.mFileName = fileName 

526 else: 

527 c.mFileName = g.ensure_extension(fileName, g.defaultLeoFileExtension(c)) 

528 # Part of the fix for https://bugs.launchpad.net/leo-editor/+bug/1194209 

529 c.frame.title = title = c.computeWindowTitle(c.mFileName) 

530 c.frame.setTitle(title) 

531 c.openDirectory = c.frame.openDirectory = g.os_path_dirname(c.mFileName) 

532 # Calls c.clearChanged() if no error. 

533 if hasattr(c.frame, 'top'): 

534 c.frame.top.leo_master.setTabName(c, c.mFileName) 

535 c.fileCommands.saveAs(c.mFileName) 

536 g.app.recentFilesManager.updateRecentFiles(c.mFileName) 

537 g.chdir(c.mFileName) 

538 # FileCommands.saveAs calls c.redraw_after_icons_changed() 

539 c.raise_error_dialogs(kind='write') 

540 # *Safely* restore focus, without using the old w directly. 

541 if inBody: 

542 c.bodyWantsFocus() 

543 p.restoreCursorAndScroll() 

544 else: 

545 c.treeWantsFocus() 

546#@+node:ekr.20031218072017.2836: *3* c_file.saveTo 

547@g.commander_command('save-to') 

548@g.commander_command('file-save-to') 

549@g.commander_command('save-file-to') 

550def saveTo(self, event=None, fileName=None, silent=False): 

551 """ 

552 Save a Leo outline to a file, prompting for a new file name unless the 

553 fileName kwarg is given. Leave the file name of the Leo outline unchanged. 

554 

555 kwarg: a file name, for use by scripts using Leo's bridge. 

556 """ 

557 c, p = self, self.p 

558 # Do this now: w may go away. 

559 w = g.app.gui.get_focus(c) 

560 inBody = g.app.gui.widget_name(w).startswith('body') 

561 if inBody: 

562 p.saveCursorAndScroll() 

563 if g.app.disableSave: 

564 g.es("save commands disabled", color="purple") 

565 return 

566 c.init_error_dialogs() 

567 # Add fileName keyword arg for leoBridge scripts. 

568 if not fileName: 

569 # set local fileName, _not_ c.mFileName 

570 fileName = ''.join(c.k.givenArgs) 

571 if not fileName: 

572 fileName = g.app.gui.runSaveFileDialog(c, 

573 title="Save To", 

574 filetypes=[("Leo files", "*.leo *.db"),], 

575 defaultextension=g.defaultLeoFileExtension(c)) 

576 c.bringToFront() 

577 if fileName: 

578 c.fileCommands.saveTo(fileName, silent=silent) 

579 g.app.recentFilesManager.updateRecentFiles(fileName) 

580 g.chdir(fileName) 

581 c.raise_error_dialogs(kind='write') 

582 # *Safely* restore focus, without using the old w directly. 

583 if inBody: 

584 c.bodyWantsFocus() 

585 p.restoreCursorAndScroll() 

586 else: 

587 c.treeWantsFocus() 

588 c.outerUpdate() 

589#@+node:ekr.20031218072017.2837: *3* c_file.revert 

590@g.commander_command('revert') 

591def revert(self, event=None): 

592 """Revert the contents of a Leo outline to last saved contents.""" 

593 c = self 

594 # Make sure the user wants to Revert. 

595 fn = c.mFileName 

596 if not fn: 

597 g.es('can not revert unnamed file.') 

598 if not g.os_path_exists(fn): 

599 g.es(f"Can not revert unsaved file: {fn}") 

600 return 

601 reply = g.app.gui.runAskYesNoDialog( 

602 c, 'Revert', f"Revert to previous version of {fn}?") 

603 c.bringToFront() 

604 if reply == "yes": 

605 g.app.loadManager.revertCommander(c) 

606#@+node:ekr.20210316075815.1: *3* c_file.save-as-leojs 

607@g.commander_command('file-save-as-leojs') 

608@g.commander_command('save-file-as-leojs') 

609def save_as_leojs(self, event=None): 

610 """ 

611 Save a Leo outline as a JSON (.leojs) file with a new file name. 

612 """ 

613 c = self 

614 fileName = g.app.gui.runSaveFileDialog(c, 

615 title="Save As JSON (.leojs)", 

616 filetypes=[("Leo files", "*.leojs")], 

617 defaultextension='.leojs') 

618 if not fileName: 

619 return 

620 if not fileName.endswith('.leojs'): 

621 fileName = f"{fileName}.leojs" 

622 # Leo 6.4: Using save-to instead of save-as allows two versions of the file. 

623 c.saveTo(fileName=fileName) 

624 c.fileCommands.putSavedMessage(fileName) 

625#@+node:ekr.20070413045221: *3* c_file.save-as-zipped 

626@g.commander_command('file-save-as-zipped') 

627@g.commander_command('save-file-as-zipped') 

628def save_as_zipped(self, event=None): 

629 """ 

630 Save a Leo outline as a zipped (.db) file with a new file name. 

631 """ 

632 c = self 

633 fileName = g.app.gui.runSaveFileDialog(c, 

634 title="Save As Zipped", 

635 filetypes=[("Leo files", "*.db")], 

636 defaultextension='.db') 

637 if not fileName: 

638 return 

639 if not fileName.endswith('.db'): 

640 fileName = f"{fileName}.db" 

641 # Leo 6.4: Using save-to instead of save-as allows two versions of the file. 

642 c.saveTo(fileName=fileName) 

643 c.fileCommands.putSavedMessage(fileName) 

644#@+node:ekr.20210316075357.1: *3* c_file.save-as-xml 

645@g.commander_command('file-save-as-xml') 

646@g.commander_command('save-file-as-xml') 

647def save_as_xml(self, event=None): 

648 """ 

649 Save a Leo outline as a .leo file with a new file name. 

650 

651 Useful for converting a .leo.db file to a .leo file. 

652 """ 

653 c = self 

654 fileName = g.app.gui.runSaveFileDialog(c, 

655 title="Save As XML", 

656 filetypes=[("Leo files", "*.leo")], 

657 defaultextension=g.defaultLeoFileExtension(c)) 

658 if not fileName: 

659 return 

660 if not fileName.endswith('.leo'): 

661 fileName = f"{fileName}.leo" 

662 # Leo 6.4: Using save-to instead of save-as allows two versions of the file. 

663 c.saveTo(fileName=fileName) 

664 c.fileCommands.putSavedMessage(fileName) 

665#@+node:tom.20220310092720.1: *3* c_file.save-node-as-xml 

666@g.commander_command('save-node-as-xml') 

667def save_node_as_xml_outline(self, event=None): 

668 """Save a node with its subtree as a Leo outline file.""" 

669 c = event.c 

670 xml = c.fileCommands.outline_to_clipboard_string() 

671 

672 fileName = g.app.gui.runSaveFileDialog(c, 

673 title="Save To", 

674 filetypes=[("Leo files", "*.leo"),], 

675 defaultextension=g.defaultLeoFileExtension(c)) 

676 

677 if fileName: 

678 with open(fileName, 'w', encoding='utf-8') as f: 

679 f.write(xml) 

680#@+node:ekr.20031218072017.2849: ** Export 

681#@+node:ekr.20031218072017.2850: *3* c_file.exportHeadlines 

682@g.commander_command('export-headlines') 

683def exportHeadlines(self, event=None): 

684 """Export all headlines to an external file.""" 

685 c = self 

686 filetypes = [("Text files", "*.txt"), ("All files", "*")] 

687 fileName = g.app.gui.runSaveFileDialog(c, 

688 title="Export Headlines", 

689 filetypes=filetypes, 

690 defaultextension=".txt") 

691 c.bringToFront() 

692 if fileName: 

693 g.setGlobalOpenDir(fileName) 

694 g.chdir(fileName) 

695 c.importCommands.exportHeadlines(fileName) 

696#@+node:ekr.20031218072017.2851: *3* c_file.flattenOutline 

697@g.commander_command('flatten-outline') 

698def flattenOutline(self, event=None): 

699 """ 

700 Export the selected outline to an external file. 

701 The outline is represented in MORE format. 

702 """ 

703 c = self 

704 filetypes = [("Text files", "*.txt"), ("All files", "*")] 

705 fileName = g.app.gui.runSaveFileDialog(c, 

706 title="Flatten Selected Outline", 

707 filetypes=filetypes, 

708 defaultextension=".txt") 

709 c.bringToFront() 

710 if fileName: 

711 g.setGlobalOpenDir(fileName) 

712 g.chdir(fileName) 

713 c.importCommands.flattenOutline(fileName) 

714#@+node:ekr.20141030120755.12: *3* c_file.flattenOutlineToNode 

715@g.commander_command('flatten-outline-to-node') 

716def flattenOutlineToNode(self, event=None): 

717 """ 

718 Append the body text of all descendants of the selected node to the 

719 body text of the selected node. 

720 """ 

721 c, root, u = self, self.p, self.undoer 

722 if not root.hasChildren(): 

723 return 

724 language = g.getLanguageAtPosition(c, root) 

725 if language: 

726 single, start, end = g.set_delims_from_language(language) 

727 else: 

728 single, start, end = '#', None, None 

729 bunch = u.beforeChangeNodeContents(root) 

730 aList = [] 

731 for p in root.subtree(): 

732 if single: 

733 aList.append(f"\n\n===== {single} {p.h}\n\n") 

734 else: 

735 aList.append(f"\n\n===== {start} {p.h} {end}\n\n") 

736 if p.b.strip(): 

737 lines = g.splitLines(p.b) 

738 aList.extend(lines) 

739 root.b = root.b.rstrip() + '\n' + ''.join(aList).rstrip() + '\n' 

740 u.afterChangeNodeContents(root, 'flatten-outline-to-node', bunch) 

741#@+node:ekr.20031218072017.2857: *3* c_file.outlineToCWEB 

742@g.commander_command('outline-to-cweb') 

743def outlineToCWEB(self, event=None): 

744 """ 

745 Export the selected outline to an external file. 

746 The outline is represented in CWEB format. 

747 """ 

748 c = self 

749 filetypes = [ 

750 ("CWEB files", "*.w"), 

751 ("Text files", "*.txt"), 

752 ("All files", "*")] 

753 fileName = g.app.gui.runSaveFileDialog(c, 

754 title="Outline To CWEB", 

755 filetypes=filetypes, 

756 defaultextension=".w") 

757 c.bringToFront() 

758 if fileName: 

759 g.setGlobalOpenDir(fileName) 

760 g.chdir(fileName) 

761 c.importCommands.outlineToWeb(fileName, "cweb") 

762#@+node:ekr.20031218072017.2858: *3* c_file.outlineToNoweb 

763@g.commander_command('outline-to-noweb') 

764def outlineToNoweb(self, event=None): 

765 """ 

766 Export the selected outline to an external file. 

767 The outline is represented in noweb format. 

768 """ 

769 c = self 

770 filetypes = [ 

771 ("Noweb files", "*.nw"), 

772 ("Text files", "*.txt"), 

773 ("All files", "*")] 

774 fileName = g.app.gui.runSaveFileDialog(c, 

775 title="Outline To Noweb", 

776 filetypes=filetypes, 

777 defaultextension=".nw") 

778 c.bringToFront() 

779 if fileName: 

780 g.setGlobalOpenDir(fileName) 

781 g.chdir(fileName) 

782 c.importCommands.outlineToWeb(fileName, "noweb") 

783 c.outlineToNowebDefaultFileName = fileName 

784#@+node:ekr.20031218072017.2859: *3* c_file.removeSentinels 

785@g.commander_command('remove-sentinels') 

786def removeSentinels(self, event=None): 

787 """Import one or more files, removing any sentinels.""" 

788 c = self 

789 types = [ 

790 ("All files", "*"), 

791 ("C/C++ files", "*.c"), 

792 ("C/C++ files", "*.cpp"), 

793 ("C/C++ files", "*.h"), 

794 ("C/C++ files", "*.hpp"), 

795 ("Java files", "*.java"), 

796 ("Lua files", "*.lua"), 

797 ("Pascal files", "*.pas"), 

798 ("Python files", "*.py")] 

799 names = g.app.gui.runOpenFileDialog(c, 

800 title="Remove Sentinels", 

801 filetypes=types, 

802 defaultextension=".py", 

803 multiple=True) 

804 c.bringToFront() 

805 if names: 

806 g.chdir(names[0]) 

807 c.importCommands.removeSentinelsCommand(names) 

808#@+node:ekr.20031218072017.2860: *3* c_file.weave 

809@g.commander_command('weave') 

810def weave(self, event=None): 

811 """Simulate a literate-programming weave operation by writing the outline to a text file.""" 

812 c = self 

813 fileName = g.app.gui.runSaveFileDialog(c, 

814 title="Weave", 

815 filetypes=[("Text files", "*.txt"), ("All files", "*")], 

816 defaultextension=".txt") 

817 c.bringToFront() 

818 if fileName: 

819 g.setGlobalOpenDir(fileName) 

820 g.chdir(fileName) 

821 c.importCommands.weave(fileName) 

822#@+node:ekr.20031218072017.2838: ** Read/Write 

823#@+node:ekr.20070806105721.1: *3* c_file.readAtAutoNodes 

824@g.commander_command('read-at-auto-nodes') 

825def readAtAutoNodes(self, event=None): 

826 """Read all @auto nodes in the presently selected outline.""" 

827 c, p, u = self, self.p, self.undoer 

828 c.endEditing() 

829 c.init_error_dialogs() 

830 undoData = u.beforeChangeTree(p) 

831 c.importCommands.readAtAutoNodes() 

832 u.afterChangeTree(p, 'Read @auto Nodes', undoData) 

833 c.redraw() 

834 c.raise_error_dialogs(kind='read') 

835#@+node:ekr.20031218072017.1839: *3* c_file.readAtFileNodes 

836@g.commander_command('read-at-file-nodes') 

837def readAtFileNodes(self, event=None): 

838 """Read all @file nodes in the presently selected outline.""" 

839 c, p, u = self, self.p, self.undoer 

840 c.endEditing() 

841 undoData = u.beforeChangeTree(p) 

842 c.endEditing() 

843 c.atFileCommands.readAllSelected(p) 

844 # Force an update of the body pane. 

845 c.setBodyString(p, p.b) # Not a do-nothing! 

846 u.afterChangeTree(p, 'Read @file Nodes', undoData) 

847 c.redraw() 

848#@+node:ekr.20080801071227.4: *3* c_file.readAtShadowNodes 

849@g.commander_command('read-at-shadow-nodes') 

850def readAtShadowNodes(self, event=None): 

851 """Read all @shadow nodes in the presently selected outline.""" 

852 c, p, u = self, self.p, self.undoer 

853 c.endEditing() 

854 c.init_error_dialogs() 

855 undoData = u.beforeChangeTree(p) 

856 c.atFileCommands.readAtShadowNodes(p) 

857 u.afterChangeTree(p, 'Read @shadow Nodes', undoData) 

858 c.redraw() 

859 c.raise_error_dialogs(kind='read') 

860#@+node:ekr.20070915134101: *3* c_file.readFileIntoNode 

861@g.commander_command('read-file-into-node') 

862def readFileIntoNode(self, event=None): 

863 """Read a file into a single node.""" 

864 c = self 

865 undoType = 'Read File Into Node' 

866 c.endEditing() 

867 filetypes = [("All files", "*"), ("Python files", "*.py"), ("Leo files", "*.leo"),] 

868 fileName = g.app.gui.runOpenFileDialog(c, 

869 title="Read File Into Node", 

870 filetypes=filetypes, 

871 defaultextension=None) 

872 if not fileName: 

873 return 

874 s, e = g.readFileIntoString(fileName) 

875 if s is None: 

876 return 

877 g.chdir(fileName) 

878 s = '@nocolor\n' + s 

879 w = c.frame.body.wrapper 

880 p = c.insertHeadline(op_name=undoType) 

881 p.setHeadString('@read-file-into-node ' + fileName) 

882 p.setBodyString(s) 

883 w.setAllText(s) 

884 c.redraw(p) 

885#@+node:ekr.20031218072017.2839: *3* c_file.readOutlineOnly 

886@g.commander_command('read-outline-only') 

887def readOutlineOnly(self, event=None): 

888 """Open a Leo outline from a .leo file, but do not read any derived files.""" 

889 c = self 

890 c.endEditing() 

891 fileName = g.app.gui.runOpenFileDialog(c, 

892 title="Read Outline Only", 

893 filetypes=[("Leo files", "*.leo"), ("All files", "*")], 

894 defaultextension=".leo") 

895 if not fileName: 

896 return 

897 try: 

898 # pylint: disable=assignment-from-no-return 

899 # Can't use 'with" because readOutlineOnly closes the file. 

900 theFile = open(fileName, 'r') 

901 g.chdir(fileName) 

902 c = g.app.newCommander(fileName) 

903 frame = c.frame 

904 frame.deiconify() 

905 frame.lift() 

906 c.fileCommands.readOutlineOnly(theFile, fileName) # closes file. 

907 except Exception: 

908 g.es("can not open:", fileName) 

909#@+node:ekr.20070915142635: *3* c_file.writeFileFromNode 

910@g.commander_command('write-file-from-node') 

911def writeFileFromNode(self, event=None): 

912 """ 

913 If node starts with @read-file-into-node, use the full path name in the headline. 

914 Otherwise, prompt for a file name. 

915 """ 

916 c, p = self, self.p 

917 c.endEditing() 

918 h = p.h.rstrip() 

919 s = p.b 

920 tag = '@read-file-into-node' 

921 if h.startswith(tag): 

922 fileName = h[len(tag) :].strip() 

923 else: 

924 fileName = None 

925 if not fileName: 

926 fileName = g.app.gui.runSaveFileDialog(c, 

927 title='Write File From Node', 

928 filetypes=[("All files", "*"), ("Python files", "*.py"), ("Leo files", "*.leo")], 

929 defaultextension=None) 

930 if fileName: 

931 try: 

932 with open(fileName, 'w') as f: 

933 g.chdir(fileName) 

934 if s.startswith('@nocolor\n'): 

935 s = s[len('@nocolor\n') :] 

936 f.write(s) 

937 f.flush() 

938 g.blue('wrote:', fileName) 

939 except IOError: 

940 g.error('can not write %s', fileName) 

941#@+node:ekr.20031218072017.2079: ** Recent Files 

942#@+node:tbrown.20080509212202.6: *3* c_file.cleanRecentFiles 

943@g.commander_command('clean-recent-files') 

944def cleanRecentFiles(self, event=None): 

945 """ 

946 Remove items from the recent files list that no longer exist. 

947 

948 This almost never does anything because Leo's startup logic removes 

949 nonexistent files from the recent files list. 

950 """ 

951 c = self 

952 g.app.recentFilesManager.cleanRecentFiles(c) 

953#@+node:ekr.20031218072017.2080: *3* c_file.clearRecentFiles 

954@g.commander_command('clear-recent-files') 

955def clearRecentFiles(self, event=None): 

956 """Clear the recent files list, then add the present file.""" 

957 c = self 

958 g.app.recentFilesManager.clearRecentFiles(c) 

959#@+node:vitalije.20170703115710.1: *3* c_file.editRecentFiles 

960@g.commander_command('edit-recent-files') 

961def editRecentFiles(self, event=None): 

962 """Opens recent files list in a new node for editing.""" 

963 c = self 

964 g.app.recentFilesManager.editRecentFiles(c) 

965#@+node:ekr.20031218072017.2081: *3* c_file.openRecentFile 

966@g.commander_command('open-recent-file') 

967def openRecentFile(self, event=None, fn=None): 

968 c = self 

969 # Automatically close the previous window if... 

970 closeFlag = ( 

971 c.frame.startupWindow and # The window was open on startup 

972 # The window has never been changed 

973 not c.changed and not c.frame.saved and 

974 # Only one untitled window has ever been opened. 

975 g.app.numberOfUntitledWindows == 1) 

976 if g.doHook("recentfiles1", c=c, p=c.p, v=c.p, fileName=fn, closeFlag=closeFlag): 

977 return 

978 c2 = g.openWithFileName(fn, old_c=c) 

979 if c2: 

980 g.app.makeAllBindings() 

981 if closeFlag and c2 and c2 != c: 

982 g.app.destroyWindow(c.frame) 

983 c2.setLog() 

984 g.doHook("recentfiles2", 

985 c=c2, p=c2.p, v=c2.p, fileName=fn, closeFlag=closeFlag) 

986#@+node:tbrown.20080509212202.8: *3* c_file.sortRecentFiles 

987@g.commander_command('sort-recent-files') 

988def sortRecentFiles(self, event=None): 

989 """Sort the recent files list.""" 

990 c = self 

991 g.app.recentFilesManager.sortRecentFiles(c) 

992#@+node:vitalije.20170703115710.2: *3* c_file.writeEditedRecentFiles 

993@g.commander_command('write-edited-recent-files') 

994def writeEditedRecentFiles(self, event=None): 

995 """ 

996 Write content of "edit_headline" node as recentFiles and recreates 

997 menues. 

998 """ 

999 c = self 

1000 g.app.recentFilesManager.writeEditedRecentFiles(c) 

1001#@+node:vitalije.20170831154859.1: ** Reference outline commands 

1002#@+node:vitalije.20170831154830.1: *3* c_file.updateRefLeoFile 

1003@g.commander_command('update-ref-file') 

1004def updateRefLeoFile(self, event=None): 

1005 """ 

1006 Saves only the **public part** of this outline to the reference Leo 

1007 file. The public part consists of all nodes above the **special 

1008 separator node**, a top-level node whose headline is 

1009 `---begin-private-area---`. 

1010 

1011 Below this special node is **private area** where one can freely make 

1012 changes that should not be copied (published) to the reference Leo file. 

1013 

1014 **Note**: Use the set-reference-file command to create the separator node. 

1015 """ 

1016 c = self 

1017 c.fileCommands.save_ref() 

1018#@+node:vitalije.20170831154840.1: *3* c_file.readRefLeoFile 

1019@g.commander_command('read-ref-file') 

1020def readRefLeoFile(self, event=None): 

1021 """ 

1022 This command *completely replaces* the **public part** of this outline 

1023 with the contents of the reference Leo file. The public part consists 

1024 of all nodes above the top-level node whose headline is 

1025 `---begin-private-area---`. 

1026 

1027 Below this special node is **private area** where one can freely make 

1028 changes that should not be copied (published) to the reference Leo file. 

1029 

1030 **Note**: Use the set-reference-file command to create the separator node. 

1031 """ 

1032 c = self 

1033 c.fileCommands.updateFromRefFile() 

1034#@+node:vitalije.20170831154850.1: *3* c_file.setReferenceFile 

1035@g.commander_command('set-reference-file') 

1036def setReferenceFile(self, event=None): 

1037 """ 

1038 Shows a file open dialog allowing you to select a **reference** Leo 

1039 document to which this outline will be connected. 

1040 

1041 This command creates a **special separator node**, a top-level node 

1042 whose headline is `---begin-private-area---` and whose body is the path 

1043 to reference Leo file. 

1044 

1045 The separator node splits the outline into two parts. The **public 

1046 part** consists of all nodes above the separator node. The **private 

1047 part** consists of all nodes below the separator node. 

1048 

1049 The update-ref-file and read-ref-file commands operate on the **public 

1050 part** of the outline. The update-ref-file command saves *only* the 

1051 public part of the outline to reference Leo file. The read-ref-file 

1052 command *completely replaces* the public part of the outline with the 

1053 contents of reference Leo file. 

1054 """ 

1055 c = self 

1056 fileName = g.app.gui.runOpenFileDialog(c, 

1057 title="Select reference Leo file", 

1058 filetypes=[("Leo files", "*.leo *.db"),], 

1059 defaultextension=g.defaultLeoFileExtension(c)) 

1060 if not fileName: 

1061 return 

1062 c.fileCommands.setReferenceFile(fileName) 

1063#@+node:ekr.20180312043352.1: ** Themes 

1064#@+node:ekr.20180312043352.2: *3* c_file.open_theme_file 

1065@g.commander_command('open-theme-file') 

1066def open_theme_file(self, event): 

1067 """Open a theme file in a new session and apply the theme.""" 

1068 c = event and event.get('c') 

1069 if not c: 

1070 return 

1071 # Get the file name. 

1072 themes_dir = g.os_path_finalize_join(g.app.loadDir, '..', 'themes') 

1073 fn = g.app.gui.runOpenFileDialog(c, 

1074 title="Open Theme File", 

1075 filetypes=[ 

1076 ("Leo files", "*.leo *.db"), 

1077 ("All files", "*"), 

1078 ], 

1079 defaultextension=g.defaultLeoFileExtension(c), 

1080 startpath=themes_dir, 

1081 ) 

1082 if not fn: 

1083 return 

1084 leo_dir = g.os_path_finalize_join(g.app.loadDir, '..', '..') 

1085 os.chdir(leo_dir) 

1086 # 

1087 # #1425: Open the theme file in a separate process. 

1088 # #1564. Use execute_shell_commands. 

1089 # #1974: allow spaces in path. 

1090 command = f'"{g.sys.executable}" "{g.app.loadDir}/runLeo.py" "{fn}"' 

1091 g.execute_shell_commands(command) 

1092 os.chdir(leo_dir) 

1093#@-others 

1094#@-leo