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

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.
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')
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):
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.
306 #
307 # Close the window if this command completes successfully?
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-]+)')
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.
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.
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.
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.
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.
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---`.
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.
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---`.
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.
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.
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.
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.
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