Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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 lm.readGlobalSettingsFiles() 

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

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

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

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

49 c.initSettings(previousSettings) 

50 # Init the config classes. 

51 c.initConfigSettings() 

52 # Init the commander config ivars. 

53 c.reloadConfigurableSettings() 

54 # Reload settings in all configurable classes 

55 # c.redraw() 

56 # Redraw so a pasted temp node isn't visible 

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

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

59def restartLeo(self, event=None): 

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

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

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

63 # 1. Write .leoRecentFiles.txt. 

64 g.app.recentFilesManager.writeRecentFilesFile(c) 

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

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

67 if c.changed: 

68 veto = False 

69 try: 

70 c.promptingForClose = True 

71 veto = c.frame.promptForSave() 

72 finally: 

73 c.promptingForClose = False 

74 if veto: 

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

76 return 

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

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

79 # 4. Save session data. 

80 if g.app.sessionManager: 

81 g.app.sessionManager.save_snapshot() 

82 # 5. Close all unsaved outlines. 

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

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

85 frame = c.frame 

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

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

88 # Save the window state 

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

90 # This may remove frame from the window list. 

91 if frame in g.app.windowList: 

92 g.app.destroyWindow(frame) 

93 g.app.windowList.remove(frame) 

94 else: 

95 # #69. 

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

97 # 6. Complete the shutdown. 

98 g.app.finishQuit() 

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

100 args = ['-c'] + [z for z in lm.old_argv] 

101 if trace: 

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

103 sys.stdout.flush() 

104 sys.stderr.flush() 

105 os.execv(sys.executable, args) 

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

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

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

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

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

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

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

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

114def importAnyFile(self, event=None): 

115 """Import one or more files.""" 

116 c = self 

117 ic = c.importCommands 

118 types = [ 

119 ("All files", "*"), 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

134 ] 

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

136 title="Import File", 

137 filetypes=types, 

138 defaultextension=".py", 

139 multiple=True) 

140 c.bringToFront() 

141 if names: 

142 g.chdir(names[0]) 

143 else: 

144 names = [] 

145 if not names: 

146 if g.unitTesting: 

147 # a kludge for unit testing. 

148 c.init_error_dialogs() 

149 c.raise_error_dialogs(kind='read') 

150 return 

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

152 c.init_error_dialogs() 

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

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

155 if derived: 

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

157 for fn in others: 

158 junk, ext = g.os_path_splitext(fn) 

159 ext = ext.lower() # #1522 

160 if ext.startswith('.'): 

161 ext = ext[1:] 

162 if ext == 'csv': 

163 ic.importMindMap([fn]) 

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

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

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

167 # elif ext == 'json': 

168 # ic.importJSON([fn]) 

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

170 ic.importFreeMind([fn]) 

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

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

173 elif ext == 'more': 

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

175 elif ext == 'txt': 

176 # #1522: Create an @edit node. 

177 import_txt_file(c, fn) 

178 else: 

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

180 last = c.lastTopLevel() 

181 parent = last.insertAfter() 

182 parent.v.h = 'Imported Files' 

183 ic.importFilesCommand( 

184 files=[fn], 

185 parent=parent, 

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

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

188 ) 

189 c.redraw() 

190 c.raise_error_dialogs(kind='read') 

191 

192g.command_alias('importAtFile', importAnyFile) 

193g.command_alias('importAtRoot', importAnyFile) 

194g.command_alias('importCWEBFiles', importAnyFile) 

195g.command_alias('importDerivedFile', importAnyFile) 

196g.command_alias('importFlattenedOutline', importAnyFile) 

197g.command_alias('importMOREFiles', importAnyFile) 

198g.command_alias('importNowebFiles', importAnyFile) 

199g.command_alias('importTabFiles', importAnyFile) 

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

201def import_txt_file(c, fn): 

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

203 u = c.undoer 

204 g.setGlobalOpenDir(fn) 

205 undoData = u.beforeInsertNode(c.p) 

206 last = c.lastTopLevel() 

207 p = last.insertAfter() 

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

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

210 p.b = s 

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

212 c.setChanged() 

213 c.redraw(p) 

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

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

216@g.commander_command('new') 

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

218 """Create a new Leo window.""" 

219 t1 = time.process_time() 

220 from leo.core import leoApp 

221 lm = g.app.loadManager 

222 old_c = self 

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

224 self.outerUpdate() 

225 # Supress redraws until later. 

226 g.app.disable_redraw = True 

227 # Send all log messages to the new frame. 

228 g.app.setLog(None) 

229 g.app.lockLog() 

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

231 t2 = time.process_time() 

232 c = g.app.newCommander( 

233 fileName=None, 

234 gui=gui, 

235 previousSettings=leoApp.PreviousSettings( 

236 settingsDict=lm.globalSettingsDict, 

237 shortcutsDict=lm.globalBindingsDict, 

238 )) 

239 t3 = time.process_time() 

240 frame = c.frame 

241 g.app.unlockLog() 

242 if not old_c: 

243 frame.setInitialWindowGeometry() 

244 # #1643: This doesn't work. 

245 # g.app.restoreWindowState(c) 

246 frame.deiconify() 

247 frame.lift() 

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

249 # Resize the _new_ frame. 

250 c.frame.createFirstTreeNode() 

251 lm.createMenu(c) 

252 lm.finishOpen(c) 

253 g.app.writeWaitingLog(c) 

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

255 c.setLog() 

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

257 g.app.disable_redraw = False 

258 c.redraw() 

259 t4 = time.process_time() 

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

261 g.trace() 

262 print( 

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

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

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

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

267 ) 

268 return c # For unit tests and scripts. 

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

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

271def open_outline(self, event=None): 

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

273 c = self 

274 #@+others 

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

276 def open_completer(c, closeFlag, fileName): 

277 

278 c.bringToFront() 

279 c.init_error_dialogs() 

280 ok = False 

281 if fileName: 

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

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

284 if c2: 

285 c2.k.makeAllBindings() 

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

287 g.chdir(fileName) 

288 g.setGlobalOpenDir(fileName) 

289 if c2 and closeFlag: 

290 g.app.destroyWindow(c.frame) 

291 elif c.looksLikeDerivedFile(fileName): 

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

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

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

295 else: 

296 # otherwise, create an @edit node. 

297 ok = c.createNodeFromExternalFile(fileName) 

298 c.raise_error_dialogs(kind='write') 

299 g.app.runAlreadyOpenDialog(c) 

300 # openWithFileName sets focus if ok. 

301 if not ok: 

302 c.initialFocusHelper() 

303 #@-others 

304 # Defines open_completer function. 

305 

306 # 

307 # Close the window if this command completes successfully? 

308 

309 closeFlag = ( 

310 c.frame.startupWindow and 

311 # The window was open on startup 

312 not c.changed and not c.frame.saved and 

313 # The window has never been changed 

314 g.app.numberOfUntitledWindows == 1 

315 # Only one untitled window has ever been opened 

316 ) 

317 table = [ 

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

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

320 ("All files", "*"), 

321 ] 

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

323 if fileName: 

324 c.open_completer(c, closeFlag, fileName) 

325 return 

326 # Equivalent to legacy code. 

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

328 defaultextension=g.defaultLeoFileExtension(c), 

329 filetypes=table, 

330 title="Open", 

331 ) 

332 open_completer(c, closeFlag, fileName) 

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

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

335 

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

337def refreshFromDisk(self, event=None): 

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

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

340 c.nodeConflictList = [] 

341 fn = p.anyAtFileNodeName() 

342 shouldDelete = c.sqlite_connection is None 

343 if not fn: 

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

345 return 

346 # #1603. 

347 if os.path.isdir(fn): 

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

349 return 

350 b = u.beforeChangeTree(p) 

351 redraw_flag = True 

352 at = c.atFileCommands 

353 c.recreateGnxDict() 

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

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

356 word = p.h[0:i] 

357 if word == '@auto': 

358 # This includes @auto-* 

359 if shouldDelete: 

360 p.v._deleteAllChildren() 

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

362 p = at.readOneAtAutoNode(p) 

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

364 if shouldDelete: 

365 p.v._deleteAllChildren() 

366 at.read(p) 

367 elif word == '@clean': 

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

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

370 at.readOneAtCleanNode(p) 

371 else: 

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

373 p = at.readOneAtAutoNode(p) 

374 elif word == '@shadow': 

375 if shouldDelete: 

376 p.v._deleteAllChildren() 

377 at.read(p) 

378 elif word == '@edit': 

379 at.readOneAtEditNode(fn, p) 

380 # Always deletes children. 

381 elif word == '@asis': 

382 # Fix #1067. 

383 at.readOneAtAsisNode(fn, p) 

384 # Always deletes children. 

385 else: 

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

387 redraw_flag = False 

388 if redraw_flag: 

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

390 c.selectPosition(p) 

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

392 # Create the 'Recovered Nodes' tree. 

393 c.fileCommands.handleNodeConflicts() 

394 c.redraw() 

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

396@g.commander_command('pwd') 

397def pwd_command(self, event=None): 

398 """Print the current working directory.""" 

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

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

401@g.commander_command('save') 

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

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

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

405 """ 

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

407 the fileName kwarg is given. 

408 

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

410 """ 

411 c = self 

412 p = c.p 

413 # Do this now: w may go away. 

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

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

416 if inBody: 

417 p.saveCursorAndScroll() 

418 if g.app.disableSave: 

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

420 return 

421 c.init_error_dialogs() 

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

423 # This supports the leoBridge. 

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

425 if fileName: 

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

427 c.mFileName = fileName 

428 if not c.mFileName: 

429 c.frame.title = "" 

430 c.mFileName = "" 

431 if c.mFileName: 

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

433 g.app.syntax_error_files = [] 

434 c.fileCommands.save(c.mFileName) 

435 c.syntaxErrorDialog() 

436 else: 

437 root = c.rootPosition() 

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

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

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

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

442 fileName = None 

443 # Write the @edit node if needed. 

444 if root.isDirty(): 

445 c.atFileCommands.writeOneAtEditNode(root) 

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

447 else: 

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

449 if not fileName: 

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

451 title="Save", 

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

453 defaultextension=g.defaultLeoFileExtension(c)) 

454 c.bringToFront() 

455 if fileName: 

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

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

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

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

460 # 2013/08/04: use c.computeWindowTitle. 

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

462 # Bug fix in 4.4b2. 

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

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

465 c.fileCommands.save(c.mFileName) 

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

467 g.chdir(c.mFileName) 

468 # Done in FileCommands.save. 

469 # c.redraw_after_icons_changed() 

470 c.raise_error_dialogs(kind='write') 

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

472 if inBody: 

473 c.bodyWantsFocus() 

474 p.restoreCursorAndScroll() 

475 else: 

476 c.treeWantsFocus() 

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

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

479def saveAll(self, event=None): 

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

481 c = self 

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

483 for f in g.app.windowList: 

484 c2 = f.c 

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

486 c2.save() 

487 # Restore the present tab. 

488 dw = c.frame.top # A DynamicWindow 

489 dw.select(c) 

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

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

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

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

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

495 """ 

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

497 fileName kwarg is given. 

498 

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

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

501 """ 

502 c, p = self, self.p 

503 # Do this now: w may go away. 

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

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

506 if inBody: 

507 p.saveCursorAndScroll() 

508 if g.app.disableSave: 

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

510 return 

511 c.init_error_dialogs() 

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

513 if fileName: 

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

515 c.mFileName = fileName 

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

517 if not c.mFileName: 

518 c.frame.title = "" 

519 if not fileName: 

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

521 if not fileName: 

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

523 title="Save As", 

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

525 defaultextension=g.defaultLeoFileExtension(c)) 

526 c.bringToFront() 

527 if fileName: 

528 # Fix bug 998090: save file as doesn't remove entry from open file list. 

529 if c.mFileName: 

530 g.app.forgetOpenFile(c.mFileName) 

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

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

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

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

535 c.frame.setTitle(title) 

536 # 2013/08/04: use c.computeWindowTitle. 

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

538 # Bug fix in 4.4b2. 

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

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

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

542 c.fileCommands.saveAs(c.mFileName) 

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

544 g.chdir(c.mFileName) 

545 # Done in FileCommands.saveAs. 

546 # c.redraw_after_icons_changed() 

547 c.raise_error_dialogs(kind='write') 

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

549 if inBody: 

550 c.bodyWantsFocus() 

551 p.restoreCursorAndScroll() 

552 else: 

553 c.treeWantsFocus() 

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

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

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

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

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

559 """ 

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

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

562 

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

564 """ 

565 c, p = self, self.p 

566 # Do this now: w may go away. 

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

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

569 if inBody: 

570 p.saveCursorAndScroll() 

571 if g.app.disableSave: 

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

573 return 

574 c.init_error_dialogs() 

575 # Add fileName keyword arg for leoBridge scripts. 

576 if not fileName: 

577 # set local fileName, _not_ c.mFileName 

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

579 if not fileName: 

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

581 title="Save To", 

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

583 defaultextension=g.defaultLeoFileExtension(c)) 

584 c.bringToFront() 

585 if fileName: 

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

587 g.app.recentFilesManager.updateRecentFiles(fileName) 

588 g.chdir(fileName) 

589 c.raise_error_dialogs(kind='write') 

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

591 if inBody: 

592 c.bodyWantsFocus() 

593 p.restoreCursorAndScroll() 

594 else: 

595 c.treeWantsFocus() 

596 c.outerUpdate() 

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

598@g.commander_command('revert') 

599def revert(self, event=None): 

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

601 c = self 

602 # Make sure the user wants to Revert. 

603 fn = c.mFileName 

604 if not fn: 

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

606 if not g.os_path_exists(fn): 

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

608 return 

609 reply = g.app.gui.runAskYesNoDialog( 

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

611 c.bringToFront() 

612 if reply == "yes": 

613 g.app.loadManager.revertCommander(c) 

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

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

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

617def save_as_leojs(self, event=None): 

618 """ 

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

620 """ 

621 c = self 

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

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

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

625 defaultextension='.leojs') 

626 if not fileName: 

627 return 

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

629 fileName = f"{fileName}.leojs" 

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

631 c.saveTo(fileName=fileName) 

632 c.fileCommands.putSavedMessage(fileName) 

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

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

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

636def save_as_zipped(self, event=None): 

637 """ 

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

639 """ 

640 c = self 

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

642 title="Save As Zipped", 

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

644 defaultextension='.db') 

645 if not fileName: 

646 return 

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

648 fileName = f"{fileName}.db" 

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

650 c.saveTo(fileName=fileName) 

651 c.fileCommands.putSavedMessage(fileName) 

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

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

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

655def save_as_xml(self, event=None): 

656 """ 

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

658  

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

660 """ 

661 c = self 

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

663 title="Save As XML", 

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

665 defaultextension=g.defaultLeoFileExtension(c)) 

666 if not fileName: 

667 return 

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

669 fileName = f"{fileName}.leo" 

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

671 c.saveTo(fileName=fileName) 

672 c.fileCommands.putSavedMessage(fileName) 

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

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

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

676def exportHeadlines(self, event=None): 

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

678 c = self 

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

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

681 title="Export Headlines", 

682 filetypes=filetypes, 

683 defaultextension=".txt") 

684 c.bringToFront() 

685 if fileName: 

686 g.setGlobalOpenDir(fileName) 

687 g.chdir(fileName) 

688 c.importCommands.exportHeadlines(fileName) 

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

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

691def flattenOutline(self, event=None): 

692 """ 

693 Export the selected outline to an external file. 

694 The outline is represented in MORE format. 

695 """ 

696 c = self 

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

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

699 title="Flatten Selected Outline", 

700 filetypes=filetypes, 

701 defaultextension=".txt") 

702 c.bringToFront() 

703 if fileName: 

704 g.setGlobalOpenDir(fileName) 

705 g.chdir(fileName) 

706 c.importCommands.flattenOutline(fileName) 

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

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

709def flattenOutlineToNode(self, event=None): 

710 """ 

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

712 body text of the selected node. 

713 """ 

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

715 if not root.hasChildren(): 

716 return 

717 language = g.getLanguageAtPosition(c, root) 

718 if language: 

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

720 else: 

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

722 bunch = u.beforeChangeNodeContents(root) 

723 aList = [] 

724 for p in root.subtree(): 

725 if single: 

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

727 else: 

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

729 if p.b.strip(): 

730 lines = g.splitLines(p.b) 

731 aList.extend(lines) 

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

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

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

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

736def outlineToCWEB(self, event=None): 

737 """ 

738 Export the selected outline to an external file. 

739 The outline is represented in CWEB format. 

740 """ 

741 c = self 

742 filetypes = [ 

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

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

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

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

747 title="Outline To CWEB", 

748 filetypes=filetypes, 

749 defaultextension=".w") 

750 c.bringToFront() 

751 if fileName: 

752 g.setGlobalOpenDir(fileName) 

753 g.chdir(fileName) 

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

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

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

757def outlineToNoweb(self, event=None): 

758 """ 

759 Export the selected outline to an external file. 

760 The outline is represented in noweb format. 

761 """ 

762 c = self 

763 filetypes = [ 

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

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

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

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

768 title="Outline To Noweb", 

769 filetypes=filetypes, 

770 defaultextension=".nw") 

771 c.bringToFront() 

772 if fileName: 

773 g.setGlobalOpenDir(fileName) 

774 g.chdir(fileName) 

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

776 c.outlineToNowebDefaultFileName = fileName 

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

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

779def removeSentinels(self, event=None): 

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

781 c = self 

782 types = [ 

783 ("All files", "*"), 

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

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

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

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

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

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

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

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

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

793 title="Remove Sentinels", 

794 filetypes=types, 

795 defaultextension=".py", 

796 multiple=True) 

797 c.bringToFront() 

798 if names: 

799 g.chdir(names[0]) 

800 c.importCommands.removeSentinelsCommand(names) 

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

802@g.commander_command('weave') 

803def weave(self, event=None): 

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

805 c = self 

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

807 title="Weave", 

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

809 defaultextension=".txt") 

810 c.bringToFront() 

811 if fileName: 

812 g.setGlobalOpenDir(fileName) 

813 g.chdir(fileName) 

814 c.importCommands.weave(fileName) 

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

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

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

818def readAtAutoNodes(self, event=None): 

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

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

821 c.endEditing() 

822 c.init_error_dialogs() 

823 undoData = u.beforeChangeTree(p) 

824 c.importCommands.readAtAutoNodes() 

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

826 c.redraw() 

827 c.raise_error_dialogs(kind='read') 

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

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

830def readAtFileNodes(self, event=None): 

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

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

833 c.endEditing() 

834 undoData = u.beforeChangeTree(p) 

835 c.endEditing() 

836 c.atFileCommands.readAllSelected(p) 

837 # Force an update of the body pane. 

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

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

840 c.redraw() 

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

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

843def readAtShadowNodes(self, event=None): 

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

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

846 c.endEditing() 

847 c.init_error_dialogs() 

848 undoData = u.beforeChangeTree(p) 

849 c.atFileCommands.readAtShadowNodes(p) 

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

851 c.redraw() 

852 c.raise_error_dialogs(kind='read') 

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

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

855def readFileIntoNode(self, event=None): 

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

857 c = self 

858 undoType = 'Read File Into Node' 

859 c.endEditing() 

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

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

862 title="Read File Into Node", 

863 filetypes=filetypes, 

864 defaultextension=None) 

865 if not fileName: 

866 return 

867 s, e = g.readFileIntoString(fileName) 

868 if s is None: 

869 return 

870 g.chdir(fileName) 

871 s = '@nocolor\n' + s 

872 w = c.frame.body.wrapper 

873 p = c.insertHeadline(op_name=undoType) 

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

875 p.setBodyString(s) 

876 w.setAllText(s) 

877 c.redraw(p) 

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

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

880def readOutlineOnly(self, event=None): 

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

882 c = self 

883 c.endEditing() 

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

885 title="Read Outline Only", 

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

887 defaultextension=".leo") 

888 if not fileName: 

889 return 

890 try: 

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

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

893 theFile = open(fileName, 'r') 

894 g.chdir(fileName) 

895 c = g.app.newCommander(fileName) 

896 frame = c.frame 

897 frame.deiconify() 

898 frame.lift() 

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

900 except Exception: 

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

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

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

904def writeFileFromNode(self, event=None): 

905 """ 

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

907 Otherwise, prompt for a file name. 

908 """ 

909 c, p = self, self.p 

910 c.endEditing() 

911 h = p.h.rstrip() 

912 s = p.b 

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

914 if h.startswith(tag): 

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

916 else: 

917 fileName = None 

918 if not fileName: 

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

920 title='Write File From Node', 

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

922 defaultextension=None) 

923 if fileName: 

924 try: 

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

926 g.chdir(fileName) 

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

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

929 f.write(s) 

930 f.flush() 

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

932 except IOError: 

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

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

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

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

937def cleanRecentFiles(self, event=None): 

938 """ 

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

940 

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

942 nonexistent files from the recent files list. 

943 """ 

944 c = self 

945 g.app.recentFilesManager.cleanRecentFiles(c) 

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

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

948def clearRecentFiles(self, event=None): 

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

950 c = self 

951 g.app.recentFilesManager.clearRecentFiles(c) 

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

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

954def editRecentFiles(self, event=None): 

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

956 c = self 

957 g.app.recentFilesManager.editRecentFiles(c) 

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

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

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

961 c = self 

962 # Automatically close the previous window if... 

963 closeFlag = ( 

964 c.frame.startupWindow and 

965 # The window was open on startup 

966 not c.changed and not c.frame.saved and 

967 # The window has never been changed 

968 g.app.numberOfUntitledWindows == 1) 

969 # Only one untitled window has ever been opened. 

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

971 return 

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

973 if c2: 

974 g.app.makeAllBindings() 

975 if closeFlag and c2 and c2 != c: 

976 g.app.destroyWindow(c.frame) 

977 c2.setLog() 

978 g.doHook("recentfiles2", 

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

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

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

982def sortRecentFiles(self, event=None): 

983 """Sort the recent files list.""" 

984 c = self 

985 g.app.recentFilesManager.sortRecentFiles(c) 

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

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

988def writeEditedRecentFiles(self, event=None): 

989 """ 

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

991 menues. 

992 """ 

993 c = self 

994 g.app.recentFilesManager.writeEditedRecentFiles(c) 

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

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

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

998def updateRefLeoFile(self, event=None): 

999 """ 

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

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

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

1003 `---begin-private-area---`. 

1004 

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

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

1007 

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

1009 """ 

1010 c = self 

1011 c.fileCommands.save_ref() 

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

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

1014def readRefLeoFile(self, event=None): 

1015 """ 

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

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

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

1019 `---begin-private-area---`. 

1020 

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

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

1023 

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

1025 """ 

1026 c = self 

1027 c.fileCommands.updateFromRefFile() 

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

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

1030def setReferenceFile(self, event=None): 

1031 """ 

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

1033 document to which this outline will be connected. 

1034 

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

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

1037 to reference Leo file. 

1038 

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

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

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

1042 

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

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

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

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

1047 contents of reference Leo file. 

1048 """ 

1049 c = self 

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

1051 title="Select reference Leo file", 

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

1053 defaultextension=g.defaultLeoFileExtension(c)) 

1054 if not fileName: 

1055 return 

1056 c.fileCommands.setReferenceFile(fileName) 

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

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

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

1060def open_theme_file(self, event): 

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

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

1063 if not c: 

1064 return 

1065 # Get the file name. 

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

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

1068 title="Open Theme File", 

1069 filetypes=[ 

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

1071 ("All files", "*"), 

1072 ], 

1073 defaultextension=g.defaultLeoFileExtension(c), 

1074 startpath=themes_dir, 

1075 ) 

1076 if not fn: 

1077 return 

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

1079 os.chdir(leo_dir) 

1080 # 

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

1082 # #1564. Use execute_shell_commands. 

1083 # #1974: allow spaces in path. 

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

1085 g.execute_shell_commands(command) 

1086 os.chdir(leo_dir) 

1087#@-others 

1088#@-leo