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
« 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.
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')
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):
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
303 #
304 # Close the window if this command completes successfully?
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-]+)')
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.
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.
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.
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.
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()
672 fileName = g.app.gui.runSaveFileDialog(c,
673 title="Save To",
674 filetypes=[("Leo files", "*.leo"),],
675 defaultextension=g.defaultLeoFileExtension(c))
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.
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---`.
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.
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---`.
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.
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.
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.
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.
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