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
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
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 treeType='@auto', # was '@clean'
185 # Experimental: attempt to use permissive section ref logic.
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 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):
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.
304 #
305 # Close the window if this command completes successfully?
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-]+)')
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.
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.
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.
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
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---`.
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.
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---`.
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.
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.
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.
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.
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