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

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

655 statements  

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 treeType='@auto', # was '@clean' 

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

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 frame.resizePanesToRatio(frame.ratio, frame.secondary_ratio) 

247 # Resize the _new_ frame. 

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 

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 c2.k.makeAllBindings() 

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

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 # Defines open_completer function. 

303 

304 # 

305 # Close the window if this command completes successfully? 

306 

307 closeFlag = ( 

308 c.frame.startupWindow and 

309 # The window was open on startup 

310 not c.changed and not c.frame.saved and 

311 # The window has never been changed 

312 g.app.numberOfUntitledWindows == 1 

313 # Only one untitled window has ever been opened 

314 ) 

315 table = [ 

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

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

318 ("All files", "*"), 

319 ] 

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

321 if fileName: 

322 c.open_completer(c, closeFlag, fileName) 

323 return 

324 # Equivalent to legacy code. 

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

326 defaultextension=g.defaultLeoFileExtension(c), 

327 filetypes=table, 

328 title="Open", 

329 ) 

330 open_completer(c, closeFlag, fileName) 

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

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

333 

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

335def refreshFromDisk(self, event=None): 

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

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

338 c.nodeConflictList = [] 

339 fn = p.anyAtFileNodeName() 

340 shouldDelete = c.sqlite_connection is None 

341 if not fn: 

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

343 return 

344 # #1603. 

345 if os.path.isdir(fn): 

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

347 return 

348 b = u.beforeChangeTree(p) 

349 redraw_flag = True 

350 at = c.atFileCommands 

351 c.recreateGnxDict() 

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

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

354 word = p.h[0:i] 

355 if word == '@auto': 

356 # This includes @auto-* 

357 if shouldDelete: 

358 p.v._deleteAllChildren() 

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

360 p = at.readOneAtAutoNode(p) 

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

362 if shouldDelete: 

363 p.v._deleteAllChildren() 

364 at.read(p) 

365 elif word == '@clean': 

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

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

368 at.readOneAtCleanNode(p) 

369 else: 

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

371 p = at.readOneAtAutoNode(p) 

372 elif word == '@shadow': 

373 if shouldDelete: 

374 p.v._deleteAllChildren() 

375 at.read(p) 

376 elif word == '@edit': 

377 at.readOneAtEditNode(fn, p) 

378 # Always deletes children. 

379 elif word == '@asis': 

380 # Fix #1067. 

381 at.readOneAtAsisNode(fn, p) 

382 # Always deletes children. 

383 else: 

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

385 redraw_flag = False 

386 if redraw_flag: 

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

388 c.selectPosition(p) 

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

390 # Create the 'Recovered Nodes' tree. 

391 c.fileCommands.handleNodeConflicts() 

392 c.redraw() 

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

394@g.commander_command('pwd') 

395def pwd_command(self, event=None): 

396 """Print the current working directory.""" 

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

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

399@g.commander_command('save') 

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

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

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

403 """ 

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

405 the fileName kwarg is given. 

406 

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

408 """ 

409 c = self 

410 p = c.p 

411 # Do this now: w may go away. 

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

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

414 if inBody: 

415 p.saveCursorAndScroll() 

416 if g.app.disableSave: 

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

418 return 

419 c.init_error_dialogs() 

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

421 # This supports the leoBridge. 

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

423 if fileName: 

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

425 c.mFileName = fileName 

426 if not c.mFileName: 

427 c.frame.title = "" 

428 c.mFileName = "" 

429 if c.mFileName: 

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

431 g.app.syntax_error_files = [] 

432 c.fileCommands.save(c.mFileName) 

433 c.syntaxErrorDialog() 

434 else: 

435 root = c.rootPosition() 

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

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

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

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

440 fileName = None 

441 # Write the @edit node if needed. 

442 if root.isDirty(): 

443 c.atFileCommands.writeOneAtEditNode(root) 

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

445 else: 

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

447 if not fileName: 

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

449 title="Save", 

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

451 defaultextension=g.defaultLeoFileExtension(c)) 

452 c.bringToFront() 

453 if fileName: 

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

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

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

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

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

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

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

461 c.fileCommands.save(c.mFileName) 

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

463 g.chdir(c.mFileName) 

464 # FileCommands.save calls c.redraw_after_icons_changed() 

465 c.raise_error_dialogs(kind='write') 

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

467 if inBody: 

468 c.bodyWantsFocus() 

469 p.restoreCursorAndScroll() 

470 else: 

471 c.treeWantsFocus() 

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

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

474def saveAll(self, event=None): 

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

476 c = self 

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

478 for f in g.app.windowList: 

479 c2 = f.c 

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

481 c2.save() 

482 # Restore the present tab. 

483 dw = c.frame.top # A DynamicWindow 

484 dw.select(c) 

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

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

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

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

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

490 """ 

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

492 fileName kwarg is given. 

493 

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

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

496 """ 

497 c, p = self, self.p 

498 # Do this now: w may go away. 

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

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

501 if inBody: 

502 p.saveCursorAndScroll() 

503 if g.app.disableSave: 

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

505 return 

506 c.init_error_dialogs() 

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

508 if fileName: 

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

510 c.mFileName = fileName 

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

512 if not c.mFileName: 

513 c.frame.title = "" 

514 if not fileName: 

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

516 if not fileName: 

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

518 title="Save As", 

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

520 defaultextension=g.defaultLeoFileExtension(c)) 

521 c.bringToFront() 

522 if fileName: 

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

524 if c.mFileName: 

525 g.app.forgetOpenFile(c.mFileName) 

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

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 

972 # The window was open on startup 

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

974 # The window has never been changed 

975 g.app.numberOfUntitledWindows == 1) 

976 # Only one untitled window has ever been opened. 

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

978 return 

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

980 if c2: 

981 g.app.makeAllBindings() 

982 if closeFlag and c2 and c2 != c: 

983 g.app.destroyWindow(c.frame) 

984 c2.setLog() 

985 g.doHook("recentfiles2", 

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

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

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

989def sortRecentFiles(self, event=None): 

990 """Sort the recent files list.""" 

991 c = self 

992 g.app.recentFilesManager.sortRecentFiles(c) 

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

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

995def writeEditedRecentFiles(self, event=None): 

996 """ 

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

998 menues. 

999 """ 

1000 c = self 

1001 g.app.recentFilesManager.writeEditedRecentFiles(c) 

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

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

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

1005def updateRefLeoFile(self, event=None): 

1006 """ 

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

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

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

1010 `---begin-private-area---`. 

1011 

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

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

1014 

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

1016 """ 

1017 c = self 

1018 c.fileCommands.save_ref() 

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

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

1021def readRefLeoFile(self, event=None): 

1022 """ 

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

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

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

1026 `---begin-private-area---`. 

1027 

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

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

1030 

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

1032 """ 

1033 c = self 

1034 c.fileCommands.updateFromRefFile() 

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

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

1037def setReferenceFile(self, event=None): 

1038 """ 

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

1040 document to which this outline will be connected. 

1041 

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

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

1044 to reference Leo file. 

1045 

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

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

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

1049 

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

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

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

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

1054 contents of reference Leo file. 

1055 """ 

1056 c = self 

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

1058 title="Select reference Leo file", 

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

1060 defaultextension=g.defaultLeoFileExtension(c)) 

1061 if not fileName: 

1062 return 

1063 c.fileCommands.setReferenceFile(fileName) 

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

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

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

1067def open_theme_file(self, event): 

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

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

1070 if not c: 

1071 return 

1072 # Get the file name. 

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

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

1075 title="Open Theme File", 

1076 filetypes=[ 

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

1078 ("All files", "*"), 

1079 ], 

1080 defaultextension=g.defaultLeoFileExtension(c), 

1081 startpath=themes_dir, 

1082 ) 

1083 if not fn: 

1084 return 

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

1086 os.chdir(leo_dir) 

1087 # 

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

1089 # #1564. Use execute_shell_commands. 

1090 # #1974: allow spaces in path. 

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

1092 g.execute_shell_commands(command) 

1093 os.chdir(leo_dir) 

1094#@-others 

1095#@-leo