# -*- coding: utf-8 -*-
#@+leo-ver=5-thin
#@+node:ekr.20031218072017.2608: * @file leoApp.py
#@@first
#@+<< imports >>
#@+node:ekr.20120219194520.10463: ** << imports >> (leoApp)
import leo.core.leoGlobals as g
import leo.core.leoExternalFiles as leoExternalFiles
try:
import builtins # Python 3
except ImportError:
import __builtin__ as builtins # Python 2.
# import glob
import importlib
import io
import os
import optparse
import subprocess
import string
import sys
# import time
import traceback
import zipfile
import platform
if g.isPython3:
StringIO = io.StringIO
else:
import cStringIO
StringIO = cStringIO.StringIO
import sqlite3
#@-<< imports >>
#@+others
#@+node:ekr.20161026122804.1: ** class IdleTimeManager
[docs]class IdleTimeManager(object):
'''
A singleton class to manage idle-time handling. This class handles all
details of running code at idle time, including running 'idle' hooks.
Any code can call g.app.idleTimeManager.add_callback(callback) to cause
the callback to be called at idle time forever.
'''
def __init__(self):
'''Ctor for IdleTimeManager class.'''
self.callback_list = []
self.timer = None
#@+others
#@+node:ekr.20161026125611.1: *3* itm.add_callback
[docs] def add_callback(self, callback):
'''Add a callback to be called at every idle time.'''
self.callback_list.append(callback)
#@+node:ekr.20161026124810.1: *3* itm.on_idle
on_idle_count = 0
[docs] def on_idle(self, timer):
'''IdleTimeManager: Run all idle-time callbacks.'''
if not g.app: return
if g.app.killed: return
self.on_idle_count += 1
# Handle the registered callbacks.
for callback in self.callback_list:
try:
callback()
except Exception:
g.es_exception()
g.es_print('removing callback: %s' % callback)
self.callback_list.remove(callback)
# Handle idle-time hooks.
g.app.pluginsController.on_idle()
#@+node:ekr.20161028034808.1: *3* itm.start
[docs] def start (self):
'''Start the idle-time timer.'''
self.timer = g.IdleTime(
self.on_idle,
delay=500,
tag='IdleTimeManager.on_idle')
if self.timer:
self.timer.start()
#@-others
#@+node:ekr.20120209051836.10241: ** class LeoApp
[docs]class LeoApp(object):
"""A class representing the Leo application itself.
Ivars of this class are Leo's global variables."""
#@+others
#@+node:ekr.20150509193643.1: *3* app.Birth & startup
#@+node:ekr.20031218072017.1416: *4* app.__init__ (helpers contain language dicts)
def __init__(self):
'''
Ctor for LeoApp class. These ivars are Leo's global vars.
leoGlobals.py contains global switches to be set by hand.
'''
#@+<< LeoApp: command-line arguments >>
#@+node:ekr.20161028035755.1: *5* << LeoApp: command-line arguments >>
self.batchMode = False
# True: run in batch mode.
self.debug = []
# A list of switches to be enabled.
self.diff = False
# True: run Leo in diff mode.
self.enablePlugins = True
# True: run start1 hook to load plugins. --no-plugins
self.failFast = False
# True: Use the failfast option in unit tests.
self.gui = None
# The gui class.
self.guiArgName = None
# The gui name given in --gui option.
self.ipython_inited = False
# True if leoIpython.py imports succeeded.
self.isTheme = False
# True: load files as theme files (ignore myLeoSettings.leo).
self.listen_to_log_flag = False
# True: execute listen-to-log command.
self.qt_use_tabs = False
# True: allow tabbed main window.
self.restore_session = False
# True: restore session on startup.
self.save_session = False
# True: save session on close.
self.silentMode = False
# True: no signon.
self.start_fullscreen = False
# For qt_frame plugin.
self.start_maximized = False
# For qt_frame plugin.
self.start_minimized = False
# For qt_frame plugin.
self.trace_binding = None
# The name of a binding to trace, or None.
self.trace_setting = None
# The name of a setting to trace, or None.
self.translateToUpperCase = False
# Never set to True.
self.useIpython = False
# True: add support for IPython.
self.use_psyco = False
# True: use psyco optimization.
self.use_splash_screen = True
# True: put up a splash screen.
#@-<< LeoApp: command-line arguments >>
#@+<< LeoApp: Debugging & statistics >>
#@+node:ekr.20161028035835.1: *5* << LeoApp: Debugging & statistics >>
self.count = 0
# General purpose debugging count.
self.debug_dict = {}
# For general use.
self.disable_redraw = False
# True: disable all redraws.
self.disableSave = False
# May be set by plugins.
self.idle_timers = []
# A list of IdleTime instances, so they persist.
self.log_listener = None
# The process created by the 'listen-for-log' command.
self.positions = 0
# The number of positions generated.
self.scanErrors = 0
# The number of errors seen by g.scanError.
self.structure_errors = 0
# Set by p.safeMoveToThreadNext.
self.statsDict = {}
# dict used by g.stat, g.clear_stats, g.print_stats.
self.validate_outline = False
# True: enables c.validate_outline. (slow)
#@-<< LeoApp: Debugging & statistics >>
#@+<< LeoApp: error messages >>
#@+node:ekr.20161028035902.1: *5* << LeoApp: error messages >>
self.menuWarningsGiven = False
# True: supress warnings in menu code.
self.unicodeErrorGiven = True
# True: suppres unicode tracebacks.
#@-<< LeoApp: error messages >>
#@+<< LeoApp: global directories >>
#@+node:ekr.20161028035924.1: *5* << LeoApp: global directories >>
self.extensionsDir = None
# The leo/extensions directory
self.globalConfigDir = None
# leo/config directory
self.globalOpenDir = None
# The directory last used to open a file.
self.homeDir = None
# The user's home directory.
self.homeLeoDir = None
# The user's home/.leo directory.
self.loadDir = None
# The leo/core directory.
self.machineDir = None
# The machine-specific directory.
#@-<< LeoApp: global directories >>
#@+<< LeoApp: global data >>
#@+node:ekr.20161028035956.1: *5* << LeoApp: global data >>
self.atAutoNames = set()
# The set of all @auto spellings.
self.atFileNames = set()
# The set of all built-in @<file> spellings.
self.globalKillBuffer = []
# The global kill buffer.
self.globalRegisters = {}
# The global register list.
self.leoID = None
# The id part of gnx's.
self.loadedThemes = []
# List of loaded theme.leo files.
# This is used by the 'new' command.
self.lossage = []
# List of last 100 keystrokes.
self.paste_c = None
# The commander that pasted the last outline.
self.spellDict = None
# The singleton PyEnchant spell dict.
self.numberOfUntitledWindows = 0
# Number of opened untitled windows.
self.windowList = []
# Global list of all frames.
self.realMenuNameDict = {}
# Translations of menu names.
#@-<< LeoApp: global data >>
#@+<< LeoApp: global controller/manager objects >>
#@+node:ekr.20161028040028.1: *5* << LeoApp: global controller/manager objects >>
# Most of these are defined in initApp.
self.backgroundProcessManager = None
# The singleton BackgroundProcessManager instance.
self.config = None
# The singleton leoConfig instance.
self.db = None
# The singleton leoCacher instance.
self.externalFilesController = None
# The singleton ExternalFilesController instance.
self.idleTimeManager = None
# The singleton IdleTimeManager instance.
self.ipk = None
# python kernel instance
self.loadManager = None
# The singleton LoadManager instance.
# self.logManager = None
# The singleton LogManager instance.
# self.openWithManager = None
# The singleton OpenWithManager instance.
self.nodeIndices = None
# The singleton nodeIndices instance.
self.pluginsController = None
# The singleton PluginsManager instance.
self.sessionManager = None
# The singleton SessionManager instance.
# The Commands class...
self.commandName = None
# The name of the command being executed.
self.commandInterruptFlag = False
# True: command within a command.
#@-<< LeoApp: global controller/manager objects >>
#@+<< LeoApp: global reader/writer data >>
#@+node:ekr.20170302075110.1: *5* << LeoApp: global reader/writer data >>
# From leoAtFile.py.
self.atAutoWritersDict = {}
self.writersDispatchDict = {}
# From leoImport.py
self.atAutoDict = {}
# Keys are @auto names, values are scanner classes.
self.classDispatchDict = {}
#@-<< LeoApp: global reader/writer data >>
#@+<< LeoApp: global status vars >>
#@+node:ekr.20161028040054.1: *5* << LeoApp: global status vars >>
self.already_open_files = []
# A list of file names that *might* be open in another
# copy of Leo.
self.dragging = False
# True: dragging.
self.allow_delayed_see = False
# True: pqsh.reformat_blocks_helper calls w.seeInsertPoint
self.inBridge = False
# True: running from leoBridge module.
self.inScript = False
# True: executing a script.
self.initing = True
# True: we are initiing the app.
self.initComplete = False
# True: late bindings are not allowed.
self.killed = False
# True: we are about to destroy the root window.
self.openingSettingsFile = False
# True, opening a settings file.
self.preReadFlag = False
# True: we are pre-reading a settings file.
self.quitting = False
# True: quitting. Locks out some events.
self.reverting = False
# True: executing the revert command.
self.syntax_error_files = []
#@-<< LeoApp: global status vars >>
#@+<< LeoApp: the global log >>
#@+node:ekr.20161028040141.1: *5* << LeoApp: the global log >>
# To be moved to the LogManager.
self.log = None
# The LeoFrame containing the present log.
self.logInited = False
# False: all log message go to logWaiting list.
self.logIsLocked = False
# True: no changes to log are allowed.
self.logWaiting = []
# List of tuples (s, color, newline) waiting to go to a log.
self.printWaiting = []
# Queue of messages to be sent to the printer.
self.signon = ''
self.signon2 = ''
#@-<< LeoApp: the global log >>
#@+<< LeoApp: global theme data >>
#@+node:ekr.20180319152119.1: *5* << LeoApp: global theme data >>
self.theme_directory = None
# The directory from which the theme file was loaded, if any.
# Set only by LM.readGlobalSettingsFiles.
# Used by the StyleSheetManager class.
# Not necessary **provided** that theme .leo files
# set @string theme-name to the name of the .leo file.
if 0:
self.theme_color = None
self.theme_name = None
#@-<< LeoApp: global theme data >>
#@+<< LeoApp: global types >>
#@+node:ekr.20161028040204.1: *5* << LeoApp: global types >>
import leo.core.leoFrame as leoFrame
import leo.core.leoGui as leoGui
self.nullGui = leoGui.NullGui()
self.nullLog = leoFrame.NullLog()
#@-<< LeoApp: global types >>
#@+<< LeoApp: plugins and event handlers >>
#@+node:ekr.20161028040229.1: *5* << LeoApp: plugins and event handlers >>
self.hookError = False
# True: suppress further calls to hooks.
# g.doHook sets g.app.hookError on all exceptions.
# Scripts may reset g.app.hookError to try again.
self.hookFunction = None
# Application wide hook function.
self.idle_time_hooks_enabled = True
# True: idle-time hooks are enabled.
#@-<< LeoApp: plugins and event handlers >>
#@+<< LeoApp: scripting ivars >>
#@+node:ekr.20161028040303.1: *5* << LeoApp: scripting ivars >>
self.searchDict = {}
# For communication between find/change scripts.
self.scriptDict = {}
# For use by scripts. Cleared before running each script.
self.scriptResult = None
# For use by leoPymacs.
self.permanentScriptDict = {}
# For use by scrips. Never cleared automatically.
#@-<< LeoApp: scripting ivars >>
#@+<< LeoApp: unit testing ivars >>
#@+node:ekr.20161028040330.1: *5* << LeoApp: unit testing ivars >>
self.isExternalUnitTest = False
# True: we are running a unit test externally.
self.runningAllUnitTests = False
# True: we are running all unit tests (Only for local tests).
self.suppressImportChecks = False
# Used only in basescanner.py
# True: suppress importCommands.check
self.unitTestDict = {}
# For communication between unit tests and code.
self.unitTestGui = None
# A way to override the gui in external unit tests.
self.unitTesting = False
# True if unit testing.
self.unitTestMenusDict = {}
# Created in LeoMenu.createMenuEntries for a unit test.
# keys are command names. values are sets of strokes.
#@-<< LeoApp: unit testing ivars >>
# Define all global data.
self.init_at_auto_names()
self.init_at_file_names()
self.define_global_constants()
self.define_language_delims_dict()
self.define_language_extension_dict()
self.define_extension_dict()
self.define_delegate_language_dict()
#@+node:ekr.20141102043816.5: *5* app.define_delegate_language_dict
[docs] def define_delegate_language_dict(self):
self.delegate_language_dict = {
# Keys are new language names.
# Values are existing languages in leo/modes.
"less": "css",
"hbs": "html",
"handlebars": "html",
"rust": "c",
# "vue": "c",
}
#@+node:ekr.20120522160137.9911: *5* app.define_extension_dict
#@@nobeautify
[docs] def define_extension_dict(self):
# Keys are extensions, values are languages
self.extension_dict = {
# "ada": "ada",
"ada": "ada95", # modes/ada95.py exists.
"ahk": "autohotkey",
"aj": "aspect_j",
"apdl": "apdl",
"as": "actionscript", # jason 2003-07-03
"asp": "asp",
"awk": "awk",
"b": "b",
"bas": "rapidq", # fil 2004-march-11
"bash": "shellscript",
"bat": "batch",
"bbj": "bbj",
"bcel": "bcel",
"bib": "bibtex",
"c": "c",
"c++": "cplusplus",
"cbl": "cobol", # Only one extension is valid: .cob
"cfg": "config",
"cfm": "coldfusion",
"clj": "clojure", # 2013/09/25: Fix bug 879338.
"ch": "chill", # Other extensions, .c186,.c286
"coffee": "coffeescript",
"conf": "apacheconf",
"cpp": "cpp",
"css": "css",
"d": "d",
"dart": "dart",
"e": "eiffel",
"el": "elisp",
"eml": "mail",
"erl": "erlang",
"f": "fortran",
"f90": "fortran90",
"factor": "factor",
"forth": "forth",
"g": "antlr",
"groovy": "groovy",
"h": "c", # 2012/05/23.
"handlebars": "html", # McNab.
"hbs": "html", # McNab.
"hs": "haskell",
"html": "html",
"hx": "haxe",
"i": "swig",
"i4gl": "i4gl",
"icn": "icon",
"idl": "idl",
"inf": "inform",
"info": "texinfo",
"ini": "ini",
"io": "io",
"ipynb": "jupyter",
"iss": "inno_setup",
"java": "java",
"jhtml": "jhtml",
"jmk": "jmk",
"js": "javascript", # For javascript import test.
"jsp": "javaserverpage",
# "jsp": "jsp",
"ksh": "kshell",
"kv": "kivy", # PeckJ 2014/05/05
"less": "css", # McNab
"lua": "lua", # ddm 13/02/06
"ly": "lilypond",
"m": "matlab",
"mak": "makefile",
"md": "md", # PeckJ 2013/02/07
"ml": "ml",
"mm": "objective_c", # Only one extension is valid: .m
"mod": "modula3",
"mpl": "maple",
"mqsc": "mqsc",
"nqc": "nqc",
"nsi": "nsi", # EKR: 2010/10/27
# "nsi": "nsis2",
"nw": "noweb",
"occ": "occam",
"otl": "vimoutline", # TL 8/25/08 Vim's outline plugin
"p": "pascal",
# "p": "pop11", # Conflicts with pascal.
"php": "php",
"pike": "pike",
"pl": "perl",
"pl1": "pl1",
"po": "gettext",
"pod": "perlpod",
"pov": "povray",
"prg": "foxpro",
"pro": "prolog",
"ps": "postscript",
"psp": "psp",
"ptl": "ptl",
"py": "python",
"pyx": "cython", # Other extensions, .pyd,.pyi
# "pyx": "pyrex",
# "r": "r", # modes/r.py does not exist.
"r": "rebol", # jason 2003-07-03
"rb": "ruby", # thyrsus 2008-11-05
"rest": "rst",
"rex": "objectrexx",
"rhtml": "rhtml",
"rib": "rib",
"sas": "sas",
"scala": "scala",
"scm": "scheme",
"scpt": "applescript",
"sgml": "sgml",
"sh": "shell", # DS 4/1/04. modes/shell.py exists.
"shtml": "shtml",
"sm": "smalltalk",
"splus": "splus",
"sql": "plsql", # qt02537 2005-05-27
"sqr": "sqr",
"ss": "ssharp",
"ssi": "shtml",
"tcl": "tcl", # modes/tcl.py exists.
# "tcl": "tcltk",
"tex": "latex",
# "tex": "tex",
"tpl": "tpl",
"ts": "typescript",
"txt": "plain",
# "txt": "text",
# "txt": "unknown", # Set when @comment is seen.
"uc": "uscript",
"v": "verilog",
"vbs": "vbscript",
"vhd": "vhdl",
"vhdl": "vhdl",
"vim": "vim",
"vtl": "velocity",
"w": "cweb",
"wiki": "moin",
"xml": "xml",
"xom": "omnimark",
"xsl": "xsl",
"yaml": "yaml",
"vue": "javascript",
"zpt": "zpt",
}
# These aren't real languages, or have no delims...
# cvs_commit, dsssl, embperl, freemarker, hex, jcl,
# patch, phpsection, progress, props, pseudoplain,
# relax_ng_compact, rtf, svn_commit.
# These have extensions which conflict with other languages.
# assembly_macro32: .asm or .a
# assembly_mcs51: .asm or .a
# assembly_parrot: .asm or .a
# assembly_r2000: .asm or .a
# assembly_x86: .asm or .a
# squidconf: .conf
# rpmspec: .rpm
# Extra language extensions, used to associate extensions with mode files.
# Used by importCommands.languageForExtension.
# Keys are extensions, values are corresponding mode file (without .py)
# A value of 'none' is a signal to unit tests that no extension file exists.
self.extra_extension_dict = {
'pod' : 'perl',
'unknown_language': 'none',
'w' : 'none', # cweb
}
#@+node:ekr.20031218072017.1417: *5* app.define_global_constants
[docs] def define_global_constants(self):
# self.prolog_string = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
self.prolog_prefix_string = "<?xml version=\"1.0\" encoding="
self.prolog_postfix_string = "?>"
self.prolog_namespace_string = 'xmlns:leo="http://edreamleo.org/namespaces/leo-python-editor/1.1"'
#@+node:ekr.20120522160137.9909: *5* app.define_language_delims_dict
#@@nobeautify
[docs] def define_language_delims_dict(self):
self.language_delims_dict = {
# Internally, lower case is used for all language names.
# Keys are languages, values are 1,2 or 3-tuples of delims.
"actionscript" : "// /* */", # jason 2003-07-03
"ada" : "--",
"ada95" : "--",
"ahk" : ";",
"antlr" : "// /* */",
"apacheconf" : "#",
"apdl" : "!",
"applescript" : "-- (* *)",
"asp" : "<!-- -->",
"aspect_j" : "// /* */",
"assembly_macro32" : ";",
"assembly_mcs51" : ";",
"assembly_parrot" : "#",
"assembly_r2000" : "#",
"assembly_x86" : ";",
"autohotkey" : "; /* */", # TL - AutoHotkey language
"awk" : "#",
"b" : "// /* */",
"batch" : "REM_", # Use the REM hack.
"bbj" : "/* */",
"bcel" : "// /* */",
"bibtex" : "%",
"c" : "// /* */", # C, C++ or objective C.
"chill" : "/* */",
"clojure" : ";", # 2013/09/25: Fix bug 879338.
"cobol" : "*",
"coldfusion" : "<!-- -->",
"coffeescript" : "#", # 2016/02/26.
"config" : "#", # Leo 4.5.1
"cplusplus" : "// /* */",
"cpp" : "// /* */",# C++.
"csharp" : "// /* */", # C#
"css" : "/* */", # 4/1/04
"cweb" : "@q@ @>", # Use the "cweb hack"
"cython" : "#",
"d" : "// /* */",
"dart" : "// /* */", # Leo 5.0.
"doxygen" : "#",
"eiffel" : "--",
"elisp" : ";",
"erlang" : "%",
"factor" : "!_ ( )", # Use the rem hack.
"forth" : "\\_ _(_ _)", # Use the "REM hack"
"fortran" : "C",
"fortran90" : "!",
"foxpro" : "&&",
"gettext" : "# ",
"groovy" : "// /* */",
"handlebars" : "<!-- -->", # McNab: delegate to html.
"haskell" : "--_ {-_ _-}",
"haxe" : "// /* */",
"hbs" : "<!-- -->", # McNab: delegate to html.
"html" : "<!-- -->",
"i4gl" : "-- { }",
"icon" : "#",
"idl" : "// /* */",
"inform" : "!",
"ini" : ";",
"inno_setup" : ";",
"interlis" : "/* */",
"io" : "// */",
"java" : "// /* */",
"javascript" : "// /* */", # EKR: 2011/11/12: For javascript import test.
"javaserverpage" : "<%-- --%>", # EKR: 2011/11/25 (See also, jsp)
"jhtml" : "<!-- -->",
"jmk" : "#",
"jsp" : "<%-- --%>",
"jupyter" : "<%-- --%>", # Default to markdown?
"kivy" : "#", # PeckJ 2014/05/05
"kshell" : "#", # Leo 4.5.1.
"latex" : "%",
"less" : "/* */", # NcNab: delegate to css.
"lilypond" : "% %{ %}",
"lisp" : ";", # EKR: 2010/09/29
"lotos" : "(* *)",
"lua" : "--", # ddm 13/02/06
"mail" : ">",
"makefile" : "#",
"maple" : "//",
"markdown" : "<!-- -->", # EKR, 2018/03/03: html comments.
"matlab" : "%", # EKR: 2011/10/21
"md" : "<!-- -->", # PeckJ: 2013/02/08
"ml" : "(* *)",
"modula3" : "(* *)",
"moin" : "##",
"mqsc" : "*",
"netrexx" : "-- /* */",
"noweb" : "%", # EKR: 2009-01-30. Use Latex for doc chunks.
"nqc" : "// /* */",
"nsi" : ";", # EKR: 2010/10/27
"nsis2" : ";",
"objective_c" : "// /* */",
"objectrexx" : "-- /* */",
"occam" : "--",
"omnimark" : ";",
"pascal" : "// { }",
"perl" : "#",
"perlpod" : "# __=pod__ __=cut__", # 9/25/02: The perlpod hack.
"php" : "// /* */", # 6/23/07: was "//",
"pike" : "// /* */",
"pl1" : "/* */",
"plain" : "#", # We must pick something.
"plsql" : "-- /* */", # SQL scripts qt02537 2005-05-27
"pop11" : ";;; /* */",
"postscript" : "%",
"povray" : "// /* */",
"powerdynamo" : "// <!-- -->",
"prolog" : "% /* */",
"psp" : "<!-- -->",
"ptl" : "#",
"pvwave" : ";",
"pyrex" : "#",
"python" : "#",
"r" : "#",
"rapidq" : "'", # fil 2004-march-11
"rebol" : ";", # jason 2003-07-03
"redcode" : ";",
"rest" : ".._",
"rhtml" : "<%# %>",
"rib" : "#",
"rpmspec" : "#",
"rst" : ".._",
"rust" : "// /* */",
"ruby" : "#", # thyrsus 2008-11-05
"rview" : "// /* */",
"sas" : "* /* */",
"scala" : "// /* */",
"scheme" : "; #| |#",
"sdl_pr" : "/* */",
"sgml" : "<!-- -->",
"shell" : "#", # shell scripts
"shellscript" : "#",
"shtml" : "<!-- -->",
"smalltalk" : '" "', # Comments are enclosed in double quotes(!!)
"smi_mib" : "--",
"splus" : "#",
"sqr" : "!",
"squidconf" : "#",
"ssharp" : "#",
"swig" : "// /* */",
"tcl" : "#",
"tcltk" : "#",
"tex" : "%", # Bug fix: 2008-1-30: Fixed Mark Edginton's bug.
"text" : "#", # We must pick something.
"texinfo" : "@c",
"tpl" : "<!-- -->",
"tsql" : "-- /* */",
"typescript" : "// /* */", # For typescript import test.
"unknown" : "#", # Set when @comment is seen.
"unknown_language" : '#--unknown-language--', # For unknown extensions in @shadow files.
"uscript" : "// /* */",
"vbscript" : "'",
"velocity" : "## #* *#",
"verilog" : "// /* */",
"vhdl" : "--",
"vim" : "\"",
"vimoutline" : "#", # TL 8/25/08 Vim's outline plugin
"xml" : "<!-- -->",
"xsl" : "<!-- -->",
"xslt" : "<!-- -->",
"yaml" : "#",
"zpt" : "<!-- -->",
# These aren't real languages, or have no delims...
# "cvs_commit" : "",
# "dsssl" : "; <!-- -->",
# "embperl" : "<!-- -->", # Internal colorizing state.
# "freemarker" : "",
# "hex" : "",
# "jcl" : "",
# "patch" : "",
# "phpsection" : "<!-- -->", # Internal colorizing state.
# "props" : "#", # Unknown language.
# "pseudoplain" : "",
# "relax_ng_compact" : "#", # An xml schema.
# "rtf" : "",
# "svn_commit" : "",
}
#@+node:ekr.20120522160137.9910: *5* app.define_language_extension_dict
#@@nobeautify
[docs] def define_language_extension_dict(self):
# Used only by g.app.externalFilesController.get_ext.
# Keys are languages, values are extensions.
self.language_extension_dict = {
"actionscript" : "as", # jason 2003-07-03
"ada" : "ada",
"ada95" : "ada",
"ahk" : "ahk",
"antlr" : "g",
"apacheconf" : "conf",
"apdl" : "apdl",
"applescript" : "scpt",
"asp" : "asp",
"aspect_j" : "aj",
"autohotkey" : "ahk", # TL - AutoHotkey language
"awk" : "awk",
"b" : "b",
"batch" : "bat", # Leo 4.5.1.
"bbj" : "bbj",
"bcel" : "bcel",
"bibtex" : "bib",
"c" : "c",
"chill" : "ch", # Only one extension is valid: .c186, .c286
"clojure" : "clj", # 2013/09/25: Fix bug 879338.
"cobol" : "cbl", # Only one extension is valid: .cob
"coldfusion" : "cfm",
"coffeescript" : "coffee",
"config" : "cfg",
"cplusplus" : "c++",
"cpp" : "cpp",
"css" : "css", # 4/1/04
"cweb" : "w",
"cython" : "pyx", # Only one extension is valid at present: .pyi, .pyd.
"d" : "d",
"dart" : "dart",
"eiffel" : "e",
"elisp" : "el",
"erlang" : "erl",
"factor" : "factor",
"forth" : "forth",
"fortran" : "f",
"fortran90" : "f90",
"foxpro" : "prg",
"gettext" : "po",
"groovy" : "groovy",
"haskell" : "hs",
"haxe" : "hx",
"html" : "html",
"i4gl" : "i4gl",
"icon" : "icn",
"idl" : "idl",
"inform" : "inf",
"ini" : "ini",
"inno_setup" : "iss",
"io" : "io",
"java" : "java",
"javascript" : "js", # EKR: 2011/11/12: For javascript import test.
"javaserverpage": "jsp", # EKR: 2011/11/25
"jhtml" : "jhtml",
"jmk" : "jmk",
"jsp" : "jsp",
"jupyter" : "ipynb",
"kivy" : "kv", # PeckJ 2014/05/05
"kshell" : "ksh", # Leo 4.5.1.
"latex" : "tex", # 1/8/04
"lilypond" : "ly",
"lua" : "lua", # ddm 13/02/06
"mail" : "eml",
"makefile" : "mak",
"maple" : "mpl",
"matlab" : "m",
"md" : "md", # PeckJ: 2013/02/07
"ml" : "ml",
"modula3" : "mod",
"moin" : "wiki",
"mqsc" : "mqsc",
"noweb" : "nw",
"nqc" : "nqc",
"nsi" : "nsi", # EKR: 2010/10/27
"nsis2" : "nsi",
"objective_c" : "mm", # Only one extension is valid: .m
"objectrexx" : "rex",
"occam" : "occ",
"omnimark" : "xom",
"pascal" : "p",
"perl" : "pl",
"perlpod" : "pod",
"php" : "php",
"pike" : "pike",
"pl1" : "pl1",
"plain" : "txt",
"plsql" : "sql", # qt02537 2005-05-27
# "pop11" : "p", # Conflicts with pascall.
"postscript" : "ps",
"povray" : "pov",
"prolog" : "pro",
"psp" : "psp",
"ptl" : "ptl",
"pyrex" : "pyx",
"python" : "py",
"r" : "r",
"rapidq" : "bas", # fil 2004-march-11
"rebol" : "r", # jason 2003-07-03
"rhtml" : "rhtml",
"rib" : "rib",
"rst" : "rest",
"ruby" : "rb", # thyrsus 2008-11-05
"sas" : "sas",
"scala" : "scala",
"scheme" : "scm",
"sgml" : "sgml",
"shell" : "sh", # DS 4/1/04
"shellscript" : "bash",
"shtml" : "ssi", # Only one extension is valid: .shtml
"smalltalk" : "sm",
"splus" : "splus",
"sqr" : "sqr",
"ssharp" : "ss",
"swig" : "i",
"tcl" : "tcl",
"tcltk" : "tcl",
"tex" : "tex",
"texinfo" : "info",
"text" : "txt",
"tpl" : "tpl",
"tsql" : "sql", # A guess.
"typescript" : "ts",
"unknown" : "txt", # Set when @comment is seen.
"uscript" : "uc",
"vbscript" : "vbs",
"velocity" : "vtl",
"verilog" : "v",
"vhdl" : "vhd", # Only one extension is valid: .vhdl
"vim" : "vim",
"vimoutline" : "otl", # TL 8/25/08 Vim's outline plugin
"xml" : "xml",
"xsl" : "xsl",
"xslt" : "xsl",
"yaml" : "yaml",
"zpt" : "zpt",
}
# These aren't real languages, or have no delims...
# cvs_commit, dsssl, embperl, freemarker, hex, jcl,
# patch, phpsection, progress, props, pseudoplain,
# relax_ng_compact, rtf, svn_commit.
# These have extensions which conflict with other languages.
# assembly_macro32: .asm or .a
# assembly_mcs51: .asm or .a
# assembly_parrot: .asm or .a
# assembly_r2000: .asm or .a
# assembly_x86: .asm or .a
# squidconf: .conf
# rpmspec: .rpm
#@+node:ekr.20140729162415.18086: *5* app.init_at_auto_names
[docs] def init_at_auto_names(self):
'''Init the app.atAutoNames set.'''
self.atAutoNames = set([
"@auto-rst", "@auto",
])
#@+node:ekr.20140729162415.18091: *5* app.init_at_file_names
[docs] def init_at_file_names(self):
'''Init the app.atFileNames set.'''
self.atFileNames = set([
"@asis",
"@edit",
"@file-asis", "@file-thin", "@file-nosent", "@file",
"@clean", "@nosent",
"@shadow",
"@thin",
])
#@+node:ekr.20150509193629.1: *4* app.cmd (decorator)
[docs] def cmd(name):
'''Command decorator for the LeoApp class.'''
# pylint: disable=no-self-argument
return g.new_cmd_decorator(name, ['g', 'app'])
#@+node:ekr.20090717112235.6007: *4* app.computeSignon
[docs] def computeSignon(self):
import leo.core.leoVersion as leoVersion
app = self
build, date = leoVersion.build, leoVersion.date
guiVersion = app.gui.getFullVersion() if app.gui else 'no gui!'
leoVer = leoVersion.version
n1, n2, n3, junk, junk = sys.version_info
if sys.platform.startswith('win'):
sysVersion = 'Windows '
try:
# peckj 20140416: determine true OS architecture
# the following code should return the proper architecture
# regardless of whether or not the python architecture matches
# the OS architecture (i.e. python 32-bit on windows 64-bit will return 64-bit)
v = platform.win32_ver()
release, winbuild, sp, ptype = v
true_platform = os.environ['PROCESSOR_ARCHITECTURE']
try:
true_platform = os.environ['PROCESSOR_ARCHITEw6432']
except KeyError:
pass
sysVersion = 'Windows %s %s (build %s) %s' % (
release, true_platform, winbuild, sp)
except Exception:
pass
else: sysVersion = sys.platform
branch, commit = g.gitInfo()
if not branch or not commit:
app.signon1 = 'Not running from a git repo'
else:
app.signon1 = 'Git repo info: branch = %s, commit = %s' % (
branch, commit)
app.signon = 'Leo %s' % leoVer
if build:
app.signon += ', build '+build
if date:
app.signon += ', '+date
app.signon2 = 'Python %s.%s.%s, %s\n%s' % (
n1, n2, n3, guiVersion, sysVersion)
# Leo 5.6: print the signon immediately:
if not app.silentMode:
print('')
if sys.stdout.encoding and sys.stdout.encoding.lower() != 'utf-8':
print('Note: sys.stdout.encoding is not UTF-8')
print('Encoding is: %r' % sys.stdout.encoding)
print('See: https://stackoverflow.com/questions/14109024')
print('')
print(app.signon)
print(app.signon1)
print(app.signon2)
print('** isPython3: %s' % g.isPython3)
print('** caching %s' % ('enabled' if g.enableDB else 'disabled'))
print('')
#@+node:ekr.20100831090251.5838: *4* app.createXGui
#@+node:ekr.20100831090251.5840: *5* app.createCursesGui
[docs] def createCursesGui(self, fileName='', verbose=False):
try:
import leo.plugins.cursesGui2 as cursesGui2
ok = cursesGui2.init()
except ImportError:
ok = False
if ok:
g.app.gui = cursesGui2.LeoCursesGui()
else:
print('can not create curses gui.')
#@+node:ekr.20090619065122.8593: *5* app.createDefaultGui
[docs] def createDefaultGui(self, fileName='', verbose=False):
"""A convenience routines for plugins to create the default gui class."""
app = self
argName = app.guiArgName
if g.in_bridge:
# print('createDefaultGui: g.in_bridge: %s' % g.in_bridge)
return # The bridge will create the gui later.
if app.gui:
return # This method can be called twice if we had to get .leoID.txt.
if argName in ('qt', 'qttabs'): # 2011/06/15.
app.createQtGui(fileName, verbose=verbose)
elif argName == 'null':
g.app.gui = g.app.nullGui
elif argName in ('console', 'curses'):
app.createCursesGui()
elif argName == 'text':
app.createTextGui()
if not app.gui:
print('createDefaultGui: Leo requires Qt to be installed.')
#@+node:ekr.20031218072017.1938: *5* app.createNullGuiWithScript
[docs] def createNullGuiWithScript(self, script=None):
app = self
app.batchMode = True
app.gui = g.app.nullGui
app.gui.setScript(script)
#@+node:ekr.20090202191501.1: *5* app.createQtGui
[docs] def createQtGui(self, fileName='', verbose=False):
# Do NOT omit fileName param: it is used in plugin code.
"""A convenience routines for plugins to create the Qt gui class."""
app = self
try:
from leo.core.leoQt import Qt
import leo.plugins.qt_gui as qt_gui
try:
from leo.plugins.editpane.editpane import edit_pane_test_open, edit_pane_csv
g.command('edit-pane-test-open')(edit_pane_test_open)
g.command('edit-pane-csv')(edit_pane_csv)
except ImportError:
print('Failed to import editpane')
except ImportError:
Qt = None
if Qt:
qt_gui.init()
if app.gui and fileName and verbose:
print('Qt Gui created in %s' % fileName)
else:
print('createQtGui: can not create Qt gui.')
#@+node:ekr.20170419093747.1: *5* app.createTextGui (was createCursesGui)
[docs] def createTextGui(self, fileName='', verbose=False):
app = self
app.pluginsController.loadOnePlugin('leo.plugins.cursesGui', verbose=verbose)
#@+node:ekr.20090126063121.3: *5* app.createWxGui
[docs] def createWxGui(self, fileName='', verbose=False):
# Do NOT omit fileName param: it is used in plugin code.
"""A convenience routines for plugins to create the wx gui class."""
app = self
app.pluginsController.loadOnePlugin('leo.plugins.wxGui', verbose=verbose)
if fileName and verbose:
print('wxGui created in %s' % fileName)
#@+node:ville.20090620122043.6275: *4* app.setGlobalDb
[docs] def setGlobalDb(self):
""" Create global pickleshare db
Usable by::
g.app.db['hello'] = [1,2,5]
"""
# Fixes bug 670108.
import leo.core.leoCache as leoCache
g.app.cacher = cacher = leoCache.Cacher()
g.app.db = cacher.initGlobalDB()
#@+node:ekr.20031218072017.1978: *4* app.setLeoID & helpers
[docs] def setLeoID(self, useDialog=True, verbose=True):
'''Get g.app.leoID from various sources.'''
self.leoID = None
assert self == g.app
verbose = verbose and not g.unitTesting and not self.silentMode
table = (
self.setIDFromSys,
self.setIDFromFile,
self.setIDFromEnv,
)
for func in table:
func(verbose)
if self.leoID:
break
else:
if useDialog:
self.setIdFromDialog()
if self.leoID:
self.setIDFile()
return self.leoID
#@+node:ekr.20031218072017.1979: *5* app.setIDFromSys
[docs] def setIDFromSys(self, verbose):
'''
Attempt to set g.app.leoID from sys.leoID.
This might be set by in Python's sitecustomize.py file.
'''
id_ = getattr(sys, "leoID", None)
if id_:
# Careful: periods in the id field of a gnx will corrupt the .leo file!
self.leoID = id_.replace('.', '-')
if verbose:
g.red("leoID=", self.leoID, spaces=False)
#@+node:ekr.20031218072017.1980: *5* app.setIDFromFile
[docs] def setIDFromFile(self, verbose):
'''Attempt to set g.app.leoID from leoID.txt.'''
tag = ".leoID.txt"
for theDir in (self.homeLeoDir, self.globalConfigDir, self.loadDir):
if not theDir:
continue # Do not use the current directory!
fn = g.os_path_join(theDir, tag)
try:
with open(fn, 'r') as f:
s = f.readline().strip()
if not s:
continue
# Careful: periods in gnx will corrupt the .leo file!
self.leoID = s.replace('.', '-')
except IOError:
pass
except Exception:
g.error('unexpected exception in app.setLeoID')
g.es_exception()
#@+node:ekr.20060211140947.1: *5* app.setIDFromEnv
[docs] def setIDFromEnv(self, verbose):
'''Set leoID from environment vars.'''
try:
id_ = os.getenv('USER')
if id_:
if verbose:
g.blue("setting leoID from os.getenv('USER'):", repr(id_))
# Careful: periods in the gnx would corrupt the .leo file!
self.leoID = id_.replace('.', '-')
except Exception:
pass
#@+node:ekr.20031218072017.1981: *5* app.setIdFromDialog
[docs] def setIdFromDialog(self):
'''Get leoID from a dialog.'''
# Don't put up a splash screen.
# It would obscure the coming dialog.
self.use_splash_screen = False
# New in 4.1: get an id for gnx's. Plugins may set g.app.leoID.
if self.gui is None:
# Create the Qt gui if it exists.
self.createDefaultGui(fileName='g.app.setLeoId', verbose=False)
if self.gui is None: # Neither gui could be created: this should never happen.
g.es_debug("Please enter LeoID (e.g. your username, 'johndoe'...)")
# pylint: disable=no-member
f = builtins.input if g.isPython3 else builtins.raw_input
# Suppress pyflakes complaint.
leoid = f('LeoID: ')
else:
leoid = self.gui.runAskLeoIDDialog()
# Bug fix: 2/6/05: put result in g.app.leoID.
# Careful: periods in the id field of a gnx will corrupt the .leo file!
self.leoID = leoid.replace('.', '-')
g.blue('leoID=', repr(self.leoID), spaces=False)
#@+node:ekr.20031218072017.1982: *5* app.setIDFile
[docs] def setIDFile(self):
'''Create leoID.txt.'''
tag = ".leoID.txt"
for theDir in (self.homeLeoDir, self.globalConfigDir, self.loadDir):
if theDir:
try:
fn = g.os_path_join(theDir, tag)
f = open(fn, 'w')
s = self.leoID
if not g.isPython3:
s = g.toEncodedString(s, encoding='utf-8', reportErrors=True)
f.write(s)
f.close()
if g.os_path_exists(fn):
g.error('', tag, 'created in', theDir)
return
except IOError:
pass
g.error('can not create', tag, 'in', theDir)
#@+node:ekr.20031218072017.1847: *4* app.setLog, lockLog, unlocklog
[docs] def setLog(self, log):
"""set the frame to which log messages will go"""
# print("app.setLog: %s %s" % (log, g.callers()))
# print("app.setLog: %s" % log)
if not self.logIsLocked:
self.log = log
[docs] def lockLog(self):
"""Disable changes to the log"""
# print("app.lockLog:")
self.logIsLocked = True
[docs] def unlockLog(self):
"""Enable changes to the log"""
# print("app.unlockLog:")
self.logIsLocked = False
#@+node:ekr.20031218072017.2619: *4* app.writeWaitingLog
[docs] def writeWaitingLog(self, c):
'''Write all waiting lines to the log.'''
#
# Do not call g.es, g.es_print, g.pr or g.trace here!
app = self
if not c or not c.exists:
return
if g.unitTesting:
app.logWaiting = []
g.app.setLog(None) # Prepare to requeue for other commanders.
return
# Write the signon to the log: similar to self.computeSignon().
p3 = 'isPython3: %s' % g.isPython3
caching = 'caching %s' % ('enabled' if g.enableDB else 'disabled')
table = [
('Leo Log Window', 'red'),
(app.signon, None),
(app.signon1, None),
(app.signon2, None),
(p3, None),
(caching, None),
]
table.reverse()
c.setLog()
app.logInited = True # Prevent recursive call.
if not app.silentMode:
# Write the signon.
for s, color in table:
if s:
app.logWaiting.insert(0, (s, color, True),)
# Write all the queued log entries.
for msg in app.logWaiting:
s, color, newline = msg[:3]
kwargs = {} if len(msg) < 4 else msg[3]
kwargs = {k:v for k,v in kwargs.items() if k not in ('color', 'newline')}
g.es('', s, color=color, newline=newline, **kwargs)
if hasattr(c.frame.log, 'scrollToEnd'):
g.app.gui.runAtIdle(c.frame.log.scrollToEnd)
app.logWaiting = []
# Essential when opening multiple files...
g.app.setLog(None)
#@+node:ekr.20171127111053.1: *3* app.Closing
#@+node:ekr.20031218072017.2609: *4* app.closeLeoWindow
[docs] def closeLeoWindow(self, frame, new_c=None, finish_quit=True):
"""
Attempt to close a Leo window.
Return False if the user veto's the close.
finish_quit - usually True, close Leo when last file closes, but
False when closing an already-open-elsewhere file
during initial load, so UI remains for files
further along the command line.
"""
c = frame.c
if 'shutdown' in g.app.debug:
g.pr('closeLeoWindow: changed: %s %s' % (c.changed, c.shortFileName()))
c.endEditing() # Commit any open edits.
if c.promptingForClose:
# There is already a dialog open asking what to do.
return False
g.app.recentFilesManager.writeRecentFilesFile(c)
# Make sure .leoRecentFiles.txt is written.
if c.changed:
c.promptingForClose = True
veto = frame.promptForSave()
c.promptingForClose = False
if veto: return False
g.app.setLog(None) # no log until we reactive a window.
g.doHook("close-frame", c=c)
c.cacher.commit() # store cache
# This may remove frame from the window list.
if frame in g.app.windowList:
g.app.destroyWindow(frame)
g.app.windowList.remove(frame)
else:
# Fix bug https://github.com/leo-editor/leo-editor/issues/69
g.app.forgetOpenFile(fn=c.fileName(), force=True)
if g.app.windowList:
c2 = new_c or g.app.windowList[0].c
g.app.selectLeoWindow(c2)
elif finish_quit and not g.app.unitTesting:
g.app.finishQuit()
return True # The window has been closed.
#@+node:ekr.20031218072017.2612: *4* app.destroyAllOpenWithFiles
[docs] def destroyAllOpenWithFiles(self):
'''Remove temp files created with the Open With command.'''
if 'shutdown' in g.app.debug:
g.pr('destroyAllOpenWithFiles')
if g.app.externalFilesController:
g.app.externalFilesController.shut_down()
g.app.externalFilesController = None
#@+node:ekr.20031218072017.2615: *4* app.destroyWindow
[docs] def destroyWindow(self, frame):
'''Destroy all ivars in a Leo frame.'''
if 'shutdown' in g.app.debug:
g.pr('destroyWindow: %s' % frame.c.shortFileName())
if g.app.externalFilesController:
g.app.externalFilesController.destroy_frame(frame)
if frame in g.app.windowList:
# g.pr('destroyWindow', (g.app.windowList)
g.app.forgetOpenFile(frame.c.fileName())
# force the window to go away now.
# Important: this also destroys all the objects of the commander.
frame.destroySelf()
#@+node:ekr.20031218072017.1732: *4* app.finishQuit
[docs] def finishQuit(self):
# forceShutdown may already have fired the "end1" hook.
if 'shutdown' in g.app.debug:
g.pr('finishQuit')
if not g.app.killed:
g.doHook("end1")
g.app.cacher.commit()
if g.app.ipk:
g.app.ipk.cleanup_consoles()
self.destroyAllOpenWithFiles()
# if trace: g.pr('app.finishQuit: setting g.app.killed: %s' % g.callers())
g.app.killed = True
# Disable all further hooks and events.
# Alas, "idle" events can still be called
# even after the following code.
if g.app.gui:
g.app.gui.destroySelf()
# Calls qtApp.quit()
#@+node:ekr.20031218072017.2616: *4* app.forceShutdown
[docs] def forceShutdown(self):
"""
Forces an immediate shutdown of Leo at any time.
In particular, may be called from plugins during startup.
"""
trace = 'shutdown' in g.app.debug
app = self
if trace:
g.pr('forceShutdown')
for c in app.commanders():
app.forgetOpenFile(c.fileName(), force=True)
# Wait until everything is quiet before really quitting.
if trace: g.pr('forceShutdown: before end1')
g.doHook("end1")
if trace: g.pr('forceShutdown: after end1')
self.log = None # Disable writeWaitingLog
self.killed = True # Disable all further hooks.
for w in self.windowList[:]:
if trace: g.pr('forceShutdown: %s' % w)
self.destroyWindow(w)
if trace: g.pr('before finishQuit')
self.finishQuit()
#@+node:ekr.20031218072017.2617: *4* app.onQuit
[docs] @cmd('exit-leo')
@cmd('quit-leo')
def onQuit(self, event=None):
'''Exit Leo, prompting to save unsaved outlines first.'''
g.app.quitting = True
# if trace: print('onQuit',g.app.save_session,g.app.sessionManager)
if g.app.save_session and g.app.sessionManager:
g.app.sessionManager.save_snapshot()
while g.app.windowList:
w = g.app.windowList[0]
if not g.app.closeLeoWindow(w):
break
if g.app.windowList:
g.app.quitting = False # If we get here the quit has been disabled.
#@+node:ville.20090602181814.6219: *3* app.commanders
[docs] def commanders(self):
""" Return list of currently active controllers """
return [f.c for f in g.app.windowList]
#@+node:ekr.20120427064024.10068: *3* app.Detecting already-open files
#@+node:ekr.20120427064024.10064: *4* app.checkForOpenFile
[docs] def checkForOpenFile(self, c, fn):
'''Warn if fn is already open and add fn to already_open_files list.'''
d, tag = g.app.db, 'open-leo-files'
if g.app.reverting:
# Fix #302: revert to saved doesn't reset external file change monitoring
g.app.already_open_files = []
if d is None or g.app.unitTesting or g.app.batchMode or g.app.reverting or g.app.inBridge:
return
aList = g.app.db.get(tag) or []
if fn in aList:
# The file may be open in another copy of Leo, or not:
# another Leo may have been killed prematurely.
# Put the file on the global list.
# A dialog will warn the user such files later.
if fn not in g.app.already_open_files:
g.es('may be open in another Leo:', color='red')
g.es(fn)
g.app.already_open_files.append(fn)
else:
g.app.rememberOpenFile(fn)
#@+node:ekr.20120427064024.10066: *4* app.forgetOpenFile
[docs] def forgetOpenFile(self, fn, force=False):
'''Forget the open file, so that is no longer considered open.'''
trace = 'shutdown' in g.app.debug
d, tag = g.app.db, 'open-leo-files'
if not d or not fn:
# Fix https://github.com/leo-editor/leo-editor/issues/69
return
if not force and (d is None or g.app.unitTesting or g.app.batchMode or g.app.reverting):
return
aList = d.get(tag) or []
if fn in aList:
aList.remove(fn)
if trace:
g.pr('forgetOpenFile: %s' % g.shortFileName(fn))
d[tag] = aList
else:
if trace: g.pr('forgetOpenFile: did not remove: %s' % (fn))
#@+node:ekr.20120427064024.10065: *4* app.rememberOpenFile
[docs] def rememberOpenFile(self, fn):
#
# Do not call g.trace, etc. here.
d, tag = g.app.db, 'open-leo-files'
if d is None or g.app.unitTesting or g.app.batchMode or g.app.reverting:
pass
elif g.app.preReadFlag:
pass
else:
aList = d.get(tag) or []
# It's proper to add duplicates to this list.
aList.append(fn)
d[tag] = aList
#@+node:ekr.20150621062355.1: *4* app.runAlreadyOpenDialog
[docs] def runAlreadyOpenDialog(self, c):
'''Warn about possibly already-open files.'''
if g.app.already_open_files:
aList = sorted(set(g.app.already_open_files))
g.app.already_open_files = []
g.app.gui.dismiss_splash_screen()
message = (
'The following files may already be open\n'
'in another copy of Leo:\n\n' +
'\n'.join(aList))
g.app.gui.runAskOkDialog(c,
title='Already Open Files',
message=message,
text="Ok")
#@+node:ekr.20171127111141.1: *3* app.Import utils
#@+node:ekr.20140727180847.17985: *4* app.scanner_for_at_auto
[docs] def scanner_for_at_auto(self, c, p, **kwargs):
'''A factory returning a scanner function for p, an @auto node.'''
d = g.app.atAutoDict
for key in d.keys():
# pylint: disable=cell-var-from-loop
aClass = d.get(key)
if aClass and g.match_word(p.h, 0, key):
def scanner_for_at_auto_cb(c, parent, s, **kwargs):
try:
ic = c.importCommands
scanner = aClass(importCommands=ic, **kwargs)
return scanner.run(s, parent)
except Exception:
g.es_print('Exception running', aClass.__name__)
g.es_exception()
return None
scanner_for_at_auto_cb.scanner_name = aClass.__name__
# For traces in ic.createOutline.
return scanner_for_at_auto_cb
return None
#@+node:ekr.20140130172810.15471: *4* app.scanner_for_ext
[docs] def scanner_for_ext(self, c, ext, **kwargs):
'''A factory returning a scanner function for the given file extension.'''
aClass = g.app.classDispatchDict.get(ext)
if aClass:
def scanner_for_ext_cb(c, parent, s, **kwargs):
try:
ic = c.importCommands
scanner = aClass(importCommands=ic, **kwargs)
return scanner.run(s, parent)
except Exception:
g.es_print('Exception running', aClass.__name__)
g.es_exception()
return None
scanner_for_ext_cb.scanner_name = aClass.__name__
# For traces in ic.createOutline.
return scanner_for_ext_cb
else:
return None
#@+node:ekr.20170429152049.1: *3* app.listenToLog
[docs] @cmd('listen-to-log')
@cmd('log-listen')
def listenToLog(self, event=None):
'''
A socket listener, listening on localhost. See:
https://docs.python.org/2/howto/logging-cookbook.html#sending-and-receiving-logging-events-across-a-network
Start this listener first, then start the broadcaster.
leo/plugins/cursesGui2.py is a typical broadcaster.
'''
app = self
# Kill any previous listener.
if app.log_listener:
g.es_print('Killing previous listener')
try:
app.log_listener.kill()
except Exception:
g.es_exception()
app.log_listener = None
# Start a new listener.
g.es_print('Starting log_listener.py')
path = g.os_path_finalize_join(app.loadDir,
'..', 'external', 'log_listener.py')
app.log_listener = subprocess.Popen(
[sys.executable, path],
shell=False,
universal_newlines=True,
)
#@+node:ekr.20171118024827.1: *3* app.makeAllBindings
[docs] def makeAllBindings(self):
'''
LeoApp.makeAllBindings:
Call c.k.makeAllBindings for all open commanders c.
'''
app = self
for c in app.commanders():
c.k.makeAllBindings()
#@+node:ekr.20031218072017.2188: *3* app.newCommander
[docs] def newCommander(self, fileName, relativeFileName=None, gui=None, previousSettings=None):
"""Create a commander and its view frame for the Leo main window."""
# Create the commander and its subcommanders.
# This takes about 3/4 sec when called by the leoBridge module.
import leo.core.leoCommands as leoCommands
return leoCommands.Commands(fileName, relativeFileName, gui, previousSettings)
#@+node:ekr.20120304065838.15588: *3* app.selectLeoWindow
[docs] def selectLeoWindow(self, c):
frame = c.frame
frame.deiconify()
frame.lift()
c.setLog()
master = hasattr(frame.top, 'leo_master') and frame.top.leo_master
if master: # 2011/11/21: selecting the new tab ensures focus is set.
# frame.top.leo_master is a TabbedTopLevel.
master.select(c)
if 1: # 2016/04/09
c.initialFocusHelper()
else:
c.bodyWantsFocus()
c.outerUpdate()
#@-others
#@+node:ekr.20120209051836.10242: ** class LoadManager
[docs]class LoadManager(object):
'''A class to manage loading .leo files, including configuration files.'''
#@+others
#@+node:ekr.20120214060149.15851: *3* LM.ctor
def __init__(self):
#
# Global settings & shortcuts dicts...
# The are the defaults for computing settings and shortcuts for all loaded files.
#
self.globalSettingsDict = None
# A g.TypedDict: the join of settings in leoSettings.leo & myLeoSettings.leo
self.globalBindingsDict = None
# A g.TypedDictOfLists: the join of shortcuts in leoSettings.leo & myLeoSettings.leo.
#
# LoadManager ivars corresponding to user options...
#
self.files = []
# List of files to be loaded.
self.options = {}
# Dictionary of user options. Keys are option names.
self.old_argv = []
# A copy of sys.argv for debugging.
self.more_cmdline_files = False
# True when more files remain on the command line to be
# loaded. If the user is answering "No" to each file as Leo asks
# "file already open, open again", this must be False for
# a complete exit to be appropriate (finish_quit=True param for
# closeLeoWindow())
#@+node:ekr.20120211121736.10812: *3* LM.Directory & file utils
#@+node:ekr.20120219154958.10481: *4* LM.completeFileName
[docs] def completeFileName(self, fileName):
fileName = g.toUnicode(fileName)
fileName = g.os_path_finalize(fileName)
# 2011/10/12: don't add .leo to *any* file.
return fileName
#@+node:ekr.20120209051836.10372: *4* LM.computeLeoSettingsPath
[docs] def computeLeoSettingsPath(self):
'''Return the full path to leoSettings.leo.'''
# lm = self
join = g.os_path_finalize_join
settings_fn = 'leoSettings.leo'
table = (
# First, leoSettings.leo in the home directories.
join(g.app.homeDir, settings_fn),
join(g.app.homeLeoDir, settings_fn),
# Last, leoSettings.leo in leo/config directory.
join(g.app.globalConfigDir, settings_fn)
)
for path in table:
if g.os_path_exists(path):
break
else:
path = None
return path
#@+node:ekr.20120209051836.10373: *4* LM.computeMyLeoSettingsPath
[docs] def computeMyLeoSettingsPath(self):
'''
Return the full path to myLeoSettings.leo.
The "footnote": Get the local directory from lm.files[0]
'''
lm = self
join = g.os_path_finalize_join
settings_fn = 'myLeoSettings.leo'
# This seems pointless: we need a machine *directory*.
# For now, however, we'll keep the existing code as is.
machine_fn = lm.computeMachineName() + settings_fn
# First, compute the directory of the first loaded file.
# All entries in lm.files are full, absolute paths.
localDir = g.os_path_dirname(lm.files[0]) if lm.files else None
table = (
# First, myLeoSettings.leo in the local directory
join(localDir, settings_fn),
# Next, myLeoSettings.leo in the home directories.
join(g.app.homeDir, settings_fn),
join(g.app.homeLeoDir, settings_fn),
# Next, <machine-name>myLeoSettings.leo in the home directories.
join(g.app.homeDir, machine_fn),
join(g.app.homeLeoDir, machine_fn),
# Last, leoSettings.leo in leo/config directory.
join(g.app.globalConfigDir, settings_fn),
)
for path in table:
if g.os_path_exists(path):
break
else:
path = None
return path
#@+node:ekr.20120209051836.10252: *4* LM.computeStandardDirectories & helpers
[docs] def computeStandardDirectories(self):
'''Compute the locations of standard directories and
set the corresponding ivars.'''
lm = self
g.app.loadDir = lm.computeLoadDir()
g.app.leoDir = lm.computeLeoDir()
g.app.homeDir = lm.computeHomeDir()
g.app.homeLeoDir = lm.computeHomeLeoDir()
g.app.globalConfigDir = lm.computeGlobalConfigDir()
g.app.extensionsDir = g.os_path_finalize_join(g.app.loadDir, '..', 'extensions')
g.app.testDir = g.os_path_finalize_join(g.app.loadDir, '..', 'test')
#@+node:ekr.20120209051836.10253: *5* LM.computeGlobalConfigDir
[docs] def computeGlobalConfigDir(self):
leo_config_dir = getattr(sys, 'leo_config_directory', None)
if leo_config_dir:
theDir = leo_config_dir
else:
theDir = g.os_path_join(g.app.loadDir, "..", "config")
if theDir:
theDir = g.os_path_finalize(theDir)
if (
not theDir or
not g.os_path_exists(theDir) or
not g.os_path_isdir(theDir)
):
theDir = None
return theDir
#@+node:ekr.20120209051836.10254: *5* LM.computeHomeDir
[docs] def computeHomeDir(self):
"""Returns the user's home directory."""
home = os.path.expanduser("~")
# Windows searches the HOME, HOMEPATH and HOMEDRIVE
# environment vars, then gives up.
if home and len(home) > 1 and home[0] == '%' and home[-1] == '%':
# Get the indirect reference to the true home.
home = os.getenv(home[1: -1], default=None)
if home:
# Important: This returns the _working_ directory if home is None!
# This was the source of the 4.3 .leoID.txt problems.
home = g.os_path_finalize(home)
if (
not g.os_path_exists(home) or
not g.os_path_isdir(home)
):
home = None
return home
#@+node:ekr.20120209051836.10260: *5* LM.computeHomeLeoDir
[docs] def computeHomeLeoDir(self):
# lm = self
homeLeoDir = g.os_path_finalize_join(g.app.homeDir, '.leo')
if not g.os_path_exists(homeLeoDir):
g.makeAllNonExistentDirectories(homeLeoDir, force=True)
return homeLeoDir
#@+node:ekr.20120209051836.10255: *5* LM.computeLeoDir
[docs] def computeLeoDir(self):
# lm = self
loadDir = g.app.loadDir
return g.os_path_dirname(loadDir)
# We don't want the result in sys.path
#@+node:ekr.20120209051836.10256: *5* LM.computeLoadDir
[docs] def computeLoadDir(self):
"""Returns the directory containing leo.py."""
import sys
try:
# Fix a hangnail: on Windows the drive letter returned by
# __file__ is randomly upper or lower case!
# The made for an ugly recent files list.
path = g.__file__ # was leo.__file__
if path:
# Possible fix for bug 735938:
# Do the following only if path exists.
#@+<< resolve symlinks >>
#@+node:ekr.20120209051836.10257: *6* << resolve symlinks >>
if path.endswith('pyc'):
srcfile = path[: -1]
if os.path.islink(srcfile):
path = os.path.realpath(srcfile)
#@-<< resolve symlinks >>
if sys.platform == 'win32':
if len(path) > 2 and path[1] == ':':
# Convert the drive name to upper case.
path = path[0].upper() + path[1:]
path = g.os_path_finalize(path)
loadDir = g.os_path_dirname(path)
else: loadDir = None
if (
not loadDir or
not g.os_path_exists(loadDir) or
not g.os_path_isdir(loadDir)
):
loadDir = os.getcwd()
# From Marc-Antoine Parent.
if loadDir.endswith("Contents/Resources"):
loadDir += "/leo/plugins"
else:
g.pr("Exception getting load directory")
loadDir = g.os_path_finalize(loadDir)
return loadDir
except Exception:
print("Exception getting load directory")
raise
#@+node:ekr.20120213164030.10697: *5* LM.computeMachineName
[docs] def computeMachineName(self):
'''Return the name of the current machine, i.e, HOSTNAME.'''
# This is prepended to leoSettings.leo or myLeoSettings.leo
# to give the machine-specific setting name.
# How can this be worth doing??
try:
import os
name = os.getenv('HOSTNAME')
if not name:
name = os.getenv('COMPUTERNAME')
if not name:
import socket
name = socket.gethostname()
except Exception:
name = ''
return name
#@+node:ekr.20180318120148.1: *4* LM.computeThemeDirectories
[docs] def computeThemeDirectories(self):
'''
Return a list of *existing* directories that might contain theme .leo files.
'''
join = g.os_path_finalize_join
home = g.app.homeDir
leo = join(g.app.loadDir, '..')
table = [
home,
join(home, 'themes'),
join(home, '.leo'),
join(home, '.leo', 'themes'),
join(leo, 'themes'),
]
return [g.os_path_normslashes(z) for z in table if g.os_path_exists(z)]
# Make sure home has normalized slashes.
#@+node:ekr.20180318133620.1: *4* LM.computeThemeFilePath & helper
[docs] def computeThemeFilePath(self):
'''Return the absolute path to the theme .leo file.'''
lm = self
resolve = self.resolve_theme_path
# Step 1: Use the --theme file if it exists
path = resolve(lm.options.get('theme_path'), tag='--theme')
if path: return path
# Step 2: look for the @string theme-name setting in the first loaded file.
# This is a hack, but especially useful for test*.leo files in leo/themes.
path = lm.files and lm.files[0]
if path and g.os_path_exists(path):
# Tricky: we must call lm.computeLocalSettings *here*.
theme_c = lm.openSettingsFile(path)
if not theme_c:
return None # Fix #843.
settings_d, junk_shortcuts_d = lm.computeLocalSettings(
c=theme_c,
settings_d=lm.globalSettingsDict,
bindings_d=lm.globalBindingsDict,
localFlag=False,
)
setting = settings_d.get_string_setting('theme-name')
if setting:
tag = theme_c.shortFileName()
path = resolve(setting, tag=tag)
if path: return path
# Finally, use the setting in myLeoSettings.leo.
setting = lm.globalSettingsDict.get_string_setting('theme-name')
tag = 'myLeoSettings.leo'
return resolve(setting, tag=tag)
#@+node:ekr.20180321124503.1: *5* LM.resolve_theme_path
[docs] def resolve_theme_path(self, fn, tag):
'''Search theme directories for the given .leo file.'''
if not fn:
return None
if not fn.endswith('.leo'):
fn += '.leo'
for directory in self.computeThemeDirectories():
path = g.os_path_join(directory, fn)
# Normalizes slashes, etc.
if g.os_path_exists(path):
return path
print('theme .leo file not found: %s' % fn)
return None
#@+node:ekr.20120211121736.10772: *4* LM.computeWorkbookFileName
[docs] def computeWorkbookFileName(self):
'''
Return the name of the workbook.
Return None *only* if:
1. The workbook does not exist.
2. We are unit testing or in batch mode.
'''
# lm = self
fn = g.app.config.getString(setting='default_leo_file')
# The default is ~/.leo/workbook.leo
if not fn:
fn = g.os_path_finalize('~/.leo/workbook.leo')
fn = g.os_path_finalize(fn)
if not fn:
return None
elif g.os_path_exists(fn):
return fn
elif g.unitTesting or g.app.batchMode:
# 2017/02/18: unit tests must not create a workbook.
# Neither should batch mode operation.
return None
elif g.os_path_isabs(fn):
# Create the file.
g.error('Using default leo file name:\n%s' % (fn))
return fn
else:
# It's too risky to open a default file if it is relative.
return None
#@+node:ekr.20120219154958.10485: *4* LM.reportDirectories
[docs] def reportDirectories(self, verbose):
'''Report directories.'''
if not verbose: return
if 1: # old
for kind, theDir in (
('current', g.os_path_abspath(os.curdir)),
("load", g.app.loadDir),
("global config", g.app.globalConfigDir),
("home", g.app.homeDir),
):
# g.blue calls g.es_print, and that's annoying.
g.es("%s dir:" % (kind), theDir, color='blue')
else:
aList = (
'homeDir', 'homeLeoDir',
'leoDir', 'loadDir',
'extensionsDir', 'globalConfigDir')
for ivar in aList:
val = getattr(g.app, ivar)
g.trace('%20s' % (ivar), val)
#@+node:ekr.20120215062153.10740: *3* LM.Settings
#@+node:ekr.20120130101219.10182: *4* LM.computeBindingLetter
[docs] def computeBindingLetter(self, kind):
# lm = self
if not kind:
return 'D'
table = (
('M', 'myLeoSettings.leo'),
(' ', 'leoSettings.leo'),
('F', '.leo'),
)
for letter, kind2 in table:
if kind.lower().endswith(kind2.lower()):
return letter
if kind == 'register-command' or kind.find('mode') > -1:
return '@'
else:
return 'D'
#@+node:ekr.20120223062418.10421: *4* LM.computeLocalSettings
[docs] def computeLocalSettings(self, c, settings_d, bindings_d, localFlag):
'''
Merge the settings dicts from c's outline into *new copies of*
settings_d and bindings_d.
'''
lm = self
shortcuts_d2, settings_d2 = lm.createSettingsDicts(c, localFlag)
assert bindings_d
assert settings_d
if settings_d2:
if g.app.trace_setting:
key = g.app.config.munge(g.app.trace_setting)
val = settings_d2.d.get(key)
if val:
fn = g.shortFileName(val.path)
g.es_print('--trace-setting: in %20s: @%s %s=%s' % (
fn, val.kind, g.app.trace_setting, val.val))
settings_d = settings_d.copy()
settings_d.update(settings_d2)
if shortcuts_d2:
bindings_d = lm.mergeShortcutsDicts(c, bindings_d, shortcuts_d2, localFlag)
return settings_d, bindings_d
#@+node:ekr.20121126202114.3: *4* LM.createDefaultSettingsDicts
[docs] def createDefaultSettingsDicts(self):
'''Create lm.globalSettingsDict & lm.globalBindingsDict.'''
settings_d = g.app.config.defaultsDict
assert isinstance(settings_d, g.TypedDict), settings_d
settings_d.setName('lm.globalSettingsDict')
bindings_d = g.TypedDictOfLists(
name='lm.globalBindingsDict',
keyType=type('s'),
valType=g.BindingInfo)
return settings_d, bindings_d
#@+node:ekr.20120214165710.10726: *4* LM.createSettingsDicts
[docs] def createSettingsDicts(self, c, localFlag, theme=False):
import leo.core.leoConfig as leoConfig
parser = leoConfig.SettingsTreeParser(c, localFlag)
# returns the *raw* shortcutsDict, not a *merged* shortcuts dict.
shortcutsDict, settingsDict = parser.traverse(theme=theme)
return shortcutsDict, settingsDict
#@+node:ekr.20120223062418.10414: *4* LM.getPreviousSettings
[docs] def getPreviousSettings(self, fn):
'''
Return the settings in effect for fn. Typically, this involves
pre-reading fn.
'''
lm = self
settingsName = 'settings dict for %s' % g.shortFileName(fn)
shortcutsName = 'shortcuts dict for %s' % g.shortFileName(fn)
# A special case: settings in leoSettings.leo do *not* override
# the global settings, that is, settings in myLeoSettings.leo.
isLeoSettings = g.shortFileName(fn).lower() == 'leosettings.leo'
exists = g.os_path_exists(fn)
if fn and exists and lm.isLeoFile(fn) and not isLeoSettings:
# Open the file usinging a null gui.
try:
g.app.preReadFlag = True
c = lm.openSettingsFile(fn)
finally:
g.app.preReadFlag = False
# Merge the settings from c into *copies* of the global dicts.
d1, d2 = lm.computeLocalSettings(c,
lm.globalSettingsDict, lm.globalBindingsDict, localFlag=True)
# d1 and d2 are copies.
d1.setName(settingsName)
d2.setName(shortcutsName)
else:
# Get the settings from the globals settings dicts.
d1 = lm.globalSettingsDict.copy(settingsName)
d2 = lm.globalBindingsDict.copy(shortcutsName)
return PreviousSettings(d1, d2)
#@+node:ekr.20120214132927.10723: *4* LM.mergeShortcutsDicts & helpers
[docs] def mergeShortcutsDicts(self, c, old_d, new_d, localFlag):
'''
Create a new dict by overriding all shortcuts in old_d by shortcuts in new_d.
Both old_d and new_d remain unchanged.
'''
lm = self
if not old_d: return new_d
if not new_d: return old_d
bi_list = new_d.get(g.app.trace_setting)
if bi_list:
# This code executed only if g.app.trace_setting exists.
for bi in bi_list:
fn = bi.kind.split(' ')[-1]
stroke = c.k.prettyPrintKey(bi.stroke)
if bi.pane and bi.pane != 'all':
pane = ' in %s panes' % bi.pane
else:
pane = ''
inverted_old_d = lm.invert(old_d)
inverted_new_d = lm.invert(new_d)
# #510 & #327: always honor --trace-binding here.
if g.app.trace_binding:
binding = g.app.trace_binding
# First, see if the binding is for a command. (Doesn't work for plugin commands).
if localFlag and binding in c.k.killedBindings:
g.es_print('--trace-binding: %s sets %s to None' % (
c.shortFileName(), binding))
elif localFlag and binding in c.commandsDict:
d = c.k.computeInverseBindingDict()
g.trace('--trace-binding: %20s binds %s to %s' % (
c.shortFileName(), binding, d.get(binding) or []))
else:
binding = g.app.trace_binding
stroke = g.KeyStroke(binding)
bi_list = inverted_new_d.get(stroke)
if bi_list:
print('')
for bi in bi_list:
fn = bi.kind.split(' ')[-1] # bi.kind #
stroke2 = c.k.prettyPrintKey(stroke)
if bi.pane and bi.pane != 'all':
pane = ' in %s panes' % bi.pane
else:
pane = ''
g.es_print('--trace-binding: %20s binds %s to %-20s%s' % (
fn, stroke2, bi.commandName, pane))
print('')
# Fix bug 951921: check for duplicate shortcuts only in the new file.
lm.checkForDuplicateShortcuts(c, inverted_new_d)
inverted_old_d.update(inverted_new_d) # Updates inverted_old_d in place.
result = lm.uninvert(inverted_old_d)
return result
#@+node:ekr.20120311070142.9904: *5* LM.checkForDuplicateShortcuts
[docs] def checkForDuplicateShortcuts(self, c, d):
'''
Check for duplicates in an "inverted" dictionary d
whose keys are strokes and whose values are lists of BindingInfo nodes.
Duplicates happen only if panes conflict.
'''
# lm = self
# Fix bug 951921: check for duplicate shortcuts only in the new file.
for ks in sorted(list(d.keys())):
duplicates, panes = [], ['all']
aList = d.get(ks)
# A list of bi objects.
aList2 = [z for z in aList if not z.pane.startswith('mode')]
if len(aList) > 1:
for bi in aList2:
if bi.pane in panes:
duplicates.append(bi)
else:
panes.append(bi.pane)
if duplicates:
bindings = list(set([z.stroke.s for z in duplicates]))
kind = 'duplicate, (not conflicting)' if len(bindings) == 1 else 'conflicting'
g.es_print('%s key bindings in %s' % (kind, c.shortFileName()))
for bi in aList2:
g.es_print('%6s %s %s' % (
bi.pane, bi.stroke.s, bi.commandName))
#@+node:ekr.20120214132927.10724: *5* LM.invert
[docs] def invert(self, d):
'''
Invert a shortcut dict whose keys are command names,
returning a dict whose keys are strokes.
'''
result = g.TypedDictOfLists(
name='inverted %s' % d.name(),
keyType=g.KeyStroke,
valType=g.BindingInfo)
for commandName in d.keys():
for bi in d.get(commandName, []):
stroke = bi.stroke # This is canonicalized.
bi.commandName = commandName # Add info.
assert stroke
result.add(stroke, bi)
return result
#@+node:ekr.20120214132927.10725: *5* LM.uninvert
[docs] def uninvert(self, d):
'''
Uninvert an inverted shortcut dict whose keys are strokes,
returning a dict whose keys are command names.
'''
assert d.keyType == g.KeyStroke, d.keyType
result = g.TypedDictOfLists(
name='uninverted %s' % d.name(),
keyType=type('commandName'),
valType=g.BindingInfo)
for stroke in d.keys():
for bi in d.get(stroke, []):
commandName = bi.commandName
assert commandName
result.add(commandName, bi)
return result
#@+node:ekr.20120222103014.10312: *4* LM.openSettingsFile
[docs] def openSettingsFile(self, fn):
'''
Open a settings file with a null gui. Return the commander.
The caller must init the c.config object.
'''
lm = self
if not fn: return None
giveMessage = (
not g.app.unitTesting and
not g.app.silentMode and
not g.app.batchMode)
# and not g.app.inBridge
def message(s):
# This occurs early in startup, so use the following.
if giveMessage:
if not g.isPython3:
s = g.toEncodedString(s, 'ascii')
g.blue(s)
theFile = lm.openLeoOrZipFile(fn)
if not theFile:
return None # Fix #843.
message('reading settings in %s' % (fn))
# Changing g.app.gui here is a major hack. It is necessary.
oldGui = g.app.gui
g.app.gui = g.app.nullGui
c = g.app.newCommander(fn)
frame = c.frame
frame.log.enable(False)
g.app.lockLog()
g.app.openingSettingsFile = True
try:
ok = c.fileCommands.openLeoFile(theFile, fn,
readAtFileNodesFlag=False, silent=True)
# closes theFile.
finally:
g.app.openingSettingsFile = False
g.app.unlockLog()
c.openDirectory = frame.openDirectory = g.os_path_dirname(fn)
g.app.gui = oldGui
return c if ok else None
#@+node:ekr.20120213081706.10382: *4* LM.readGlobalSettingsFiles
[docs] def readGlobalSettingsFiles(self):
'''Read leoSettings.leo and myLeoSettings.leo using a null gui.'''
trace = 'themes' in g.app.debug
lm = self
# Open the standard settings files with a nullGui.
# Important: their commanders do not exist outside this method!
paths = [lm.computeLeoSettingsPath(), lm.computeMyLeoSettingsPath()]
old_commanders = g.app.commanders()
commanders = [lm.openSettingsFile(path) for path in paths]
commanders = [z for z in commanders if z]
settings_d, bindings_d = lm.createDefaultSettingsDicts()
for c in commanders:
# Merge the settings dicts from c's outline into
# *new copies of* settings_d and bindings_d.
settings_d, bindings_d = lm.computeLocalSettings(
c, settings_d, bindings_d, localFlag=False)
# Adjust the name.
bindings_d.setName('lm.globalBindingsDict')
lm.globalSettingsDict = settings_d
lm.globalBindingsDict = bindings_d
# Add settings from --theme or @string theme-name files.
# This must be done *after* reading myLeoSettigns.leo.
theme_path = lm.computeThemeFilePath()
if theme_path:
theme_c = lm.openSettingsFile(theme_path)
if theme_c:
# Merge theme_c's settings into globalSettingsDict.
settings_d, junk_shortcuts_d = lm.computeLocalSettings(
theme_c, settings_d, bindings_d, localFlag=False)
lm.globalSettingsDict = settings_d
# Set global vars
g.app.theme_directory = g.os_path_dirname(theme_path)
# Used by the StyleSheetManager.
if 0:
# Not necessary **provided** that theme .leo files
# set @string theme-name to the name of the .leo file.
g.app.theme_color = settings_d.get_string_setting('color-theme')
g.app.theme_name = settings_d.get_string_setting('theme-name')
if trace:
print('')
g.trace('=====\n')
print(' g.app.theme_path: %s' % g.app.theme_directory)
print(' g.app.theme_name: %s' % g.app.theme_name)
print('g.app.theme_color: %s' % g.app.theme_color)
print('')
# Clear the cache entries for the commanders.
# This allows this method to be called outside the startup logic.
for c in commanders:
if c not in old_commanders:
g.app.forgetOpenFile(c.fileName())
#@+node:ekr.20120214165710.10838: *4* LM.traceSettingsDict
[docs] def traceSettingsDict(self, d, verbose=False):
if verbose:
print(d)
for key in sorted(list(d.keys())):
gs = d.get(key)
print('%35s %17s %s' % (key, g.shortFileName(gs.path), gs.val))
if d: print('')
else:
# print(d)
print('%s %s' % (d.name(), len(d.d.keys())))
#@+node:ekr.20120214165710.10822: *4* LM.traceShortcutsDict
[docs] def traceShortcutsDict(self, d, verbose=False):
if verbose:
print(d)
for key in sorted(list(d.keys())):
val = d.get(key)
# print('%20s %s' % (key,val.dump()))
print('%35s %s' % (key, [z.stroke for z in val]))
if d: print('')
else:
print(d)
#@+node:ekr.20120219154958.10452: *3* LM.load & helpers
[docs] def load(self, fileName=None, pymacs=None):
'''Load the indicated file'''
lm = self
# Phase 1: before loading plugins.
# Scan options, set directories and read settings.
print('') # Give some separation for the coming traces.
if not lm.isValidPython(): return
lm.doPrePluginsInit(fileName, pymacs)
# sets lm.options and lm.files
if lm.options.get('version'):
print(g.app.signon)
return
if not g.app.gui:
return
# Phase 2: load plugins: the gui has already been set.
g.doHook("start1")
if g.app.killed: return
g.app.idleTimeManager.start()
# Phase 3: after loading plugins. Create one or more frames.
if lm.options.get('script') and not self.files:
ok = True
else:
ok = lm.doPostPluginsInit()
# Fix #579: Key bindings don't take for commands defined in plugins
g.app.makeAllBindings()
if ok and g.app.diff:
lm.doDiff()
if ok:
g.es('') # Clears horizontal scrolling in the log pane.
if g.app.listen_to_log_flag:
g.app.listenToLog()
g.app.gui.runMainLoop()
# For scripts, the gui is a nullGui.
# and the gui.setScript has already been called.
#@+node:ekr.20150225133846.7: *4* LM.doDiff
[docs] def doDiff(self):
'''Support --diff option after loading Leo.'''
if len(self.old_argv[2:]) == 2:
pass # c.editFileCommands.compareAnyTwoFiles gives a message.
else:
# This is an unusual situation.
g.es('--diff mode. sys.argv[2:]...', color='red')
for z in self.old_argv[2:]:
g.es(g.shortFileName(z) if z else repr(z), color='blue')
commanders = g.app.commanders()
if len(commanders) == 2:
c = commanders[0]
c.editFileCommands.compareAnyTwoFiles(event=None)
#@+node:ekr.20120219154958.10487: *4* LM.doPostPluginsInit & helpers
[docs] def doPostPluginsInit(self):
'''Create a Leo window for each file in the lm.files list.'''
# Clear g.app.initing _before_ creating commanders.
lm = self
g.app.initing = False # "idle" hooks may now call g.app.forceShutdown.
# Create the main frame. Show it and all queued messages.
c = c1 = None
if lm.files:
for n, fn in enumerate(lm.files):
lm.more_cmdline_files = n < len(lm.files) - 1
c = lm.loadLocalFile(fn, gui=g.app.gui, old_c=None)
# Returns None if the file is open in another instance of Leo.
if not c1: c1 = c
if g.app.restore_session:
m = g.app.sessionManager
if m:
aList = m.load_snapshot()
if aList:
m.load_session(c1, aList)
# tag:#659.
if g.app.windowList:
c = c1 = g.app.windowList[0].c
else:
c = c1 = None
if not c1 or not g.app.windowList:
c1 = lm.openEmptyWorkBook()
# Fix bug #199.
g.app.runAlreadyOpenDialog(c1)
# Put the focus in the first-opened file.
fileName = lm.files[0] if lm.files else None
c = c1
# For qttabs gui, select the first-loaded tab.
if hasattr(g.app.gui, 'frameFactory'):
factory = g.app.gui.frameFactory
if factory and hasattr(factory, 'setTabForCommander'):
factory.setTabForCommander(c)
if not c:
return False # Force an immediate exit.
# Fix bug 844953: tell Unity which menu to use.
# if c: c.enableMenuBar()
# Do the final inits.
g.app.logInited = True
g.app.initComplete = True
if c: c.setLog()
# print('doPostPluginsInit: ***** set log')
p = c.p if c else None
g.doHook("start2", c=c, p=p, fileName=fileName)
if c: lm.initFocusAndDraw(c, fileName)
screenshot_fn = lm.options.get('screenshot_fn')
if screenshot_fn:
lm.make_screen_shot(screenshot_fn)
return False # Force an immediate exit.
else:
return True
#@+node:ekr.20120219154958.10488: *5* LM.initFocusAndDraw
[docs] def initFocusAndDraw(self, c, fileName):
def init_focus_handler(timer, c=c, p=c.p):
'''Idle-time handler for initFocusAndDraw'''
c.initialFocusHelper()
c.outerUpdate()
timer.stop()
# This must happen after the code in getLeoFile.
timer = g.IdleTime(init_focus_handler, delay=0.1, tag='getLeoFile')
if timer:
timer.start()
else:
# Default code.
c.selectPosition(c.p)
c.initialFocusHelper()
c.k.showStateAndMode()
c.outerUpdate()
#@+node:ekr.20120219154958.10489: *5* LM.make_screen_shot
[docs] def make_screen_shot(self, fn):
'''Create a screenshot of the present Leo outline and save it to path.'''
if g.app.gui.guiName() == 'qt':
m = g.loadOnePlugin('screenshots')
m.make_screen_shot(fn)
#@+node:ekr.20131028155339.17098: *5* LM.openEmptyWorkBook
[docs] def openEmptyWorkBook(self):
'''Open an empty frame and paste the contents of CheatSheet.leo into it.'''
lm = self
# Create an empty frame.
fn = lm.computeWorkbookFileName()
c = lm.loadLocalFile(fn, gui=g.app.gui, old_c=None)
# Open the cheatsheet, but not in batch mode.
if not g.app.batchMode and not g.os_path_exists(fn):
# Paste the contents of CheetSheet.leo into c.
c2 = c.openCheatSheet(redraw=False)
if c2:
for p2 in c2.rootPosition().self_and_siblings():
c2.selectPosition(p2)
c2.copyOutline()
p = c.pasteOutline()
c.selectPosition(p)
p.contract()
p.clearDirty()
c2.close(new_c=c)
root = c.rootPosition()
if root.h == g.shortFileName(fn):
root.doDelete(newNode=root.next())
p = g.findNodeAnywhere(c, "Leo's cheat sheet")
if p:
c.selectPosition(p)
p.expand()
c.target_language = 'rest'
# Settings not parsed the first time.
c.setChanged(False)
c.redraw()
return c
#@+node:ekr.20120219154958.10477: *4* LM.doPrePluginsInit & helpers
[docs] def doPrePluginsInit(self, fileName, pymacs):
''' Scan options, set directories and read settings.'''
lm = self
lm.computeStandardDirectories()
lm.adjustSysPath()
# A do-nothing.
# Scan the options as early as possible.
lm.options = options = lm.scanOptions(fileName, pymacs)
# also sets lm.files.
if options.get('version'):
g.app.computeSignon()
return
script = options.get('script')
verbose = script is None
# Init the app.
lm.initApp(verbose)
lm.reportDirectories(verbose)
# Read settings *after* setting g.app.config and *before* opening plugins.
# This means if-gui has effect only in per-file settings.
lm.readGlobalSettingsFiles()
# reads only standard settings files, using a null gui.
# uses lm.files[0] to compute the local directory
# that might contain myLeoSettings.leo.
# Read the recent files file.
localConfigFile = lm.files[0] if lm.files else None
g.app.recentFilesManager.readRecentFiles(localConfigFile)
g.app.setGlobalDb()
# Create the gui after reading options and settings.
lm.createGui(pymacs)
# We can't print the signon until we know the gui.
g.app.computeSignon() # Set app.signon/signon2 for commanders.
#@+node:ekr.20170302093006.1: *5* LM.createAllImporetersData & helpers (new)
[docs] def createAllImporetersData(self):
'''
New in Leo 5.5:
Create global data structures describing importers and writers.
'''
assert g.app.loadDir
# This is the only data required.
self.createWritersData()
# Was an AtFile method.
self.createImporterData()
# Was a LeoImportCommands method.
#@+node:ekr.20140724064952.18037: *6* LM.createImporterData & helper
[docs] def createImporterData(self):
'''Create the data structures describing importer plugins.'''
# Allow plugins to be defined in ~/.leo/plugins.
plugins1 = g.os_path_finalize_join(g.app.homeDir, '.leo', 'plugins')
plugins2 = g.os_path_finalize_join(g.app.loadDir, '..', 'plugins')
for kind, plugins in (('home', plugins1), ('leo', plugins2)):
pattern = g.os_path_finalize_join(
g.app.loadDir, '..', 'plugins', 'importers', '*.py')
for fn in g.glob_glob(pattern):
sfn = g.shortFileName(fn)
if sfn != '__init__.py':
try:
module_name = sfn[: -3]
# Important: use importlib to give imported modules
# their fully qualified names.
m = importlib.import_module(
'leo.plugins.importers.%s' % module_name)
self.parse_importer_dict(sfn, m)
except Exception:
g.warning('can not import leo.plugins.importers.%s' % (
module_name))
#@+node:ekr.20140723140445.18076: *7* LM.parse_importer_dict
[docs] def parse_importer_dict(self, sfn, m):
'''
Set entries in g.app.classDispatchDict, g.app.atAutoDict and
g.app.atAutoNames using entries in m.importer_dict.
'''
importer_d = getattr(m, 'importer_dict', None)
if importer_d:
at_auto = importer_d.get('@auto', [])
scanner_class = importer_d.get('class', None)
# scanner_name = scanner_class.__name__
extensions = importer_d.get('extensions', [])
if at_auto:
# Make entries for each @auto type.
d = g.app.atAutoDict
for s in at_auto:
d[s] = scanner_class
g.app.atAutoDict[s] = scanner_class
g.app.atAutoNames.add(s)
if extensions:
# Make entries for each extension.
d = g.app.classDispatchDict
for ext in extensions:
d[ext] = scanner_class
elif sfn not in (
# These are base classes, not real plugins.
'basescanner.py',
'linescanner.py',
):
g.warning('leo/plugins/importers/%s has no importer_dict' % sfn)
#@+node:ekr.20140728040812.17990: *6* LM.createWritersData & helper
[docs] def createWritersData(self):
'''Create the data structures describing writer plugins.'''
trace = False and 'createWritersData' not in g.app.debug_dict
# Do *not* remove this trace.
if trace:
# Suppress multiple traces.
g.app.debug_dict['createWritersData'] = True
g.app.writersDispatchDict = {}
g.app.atAutoWritersDict = {}
plugins1 = g.os_path_finalize_join(g.app.homeDir, '.leo', 'plugins')
plugins2 = g.os_path_finalize_join(g.app.loadDir, '..', 'plugins')
for kind, plugins in (('home', plugins1), ('leo', plugins2)):
pattern = g.os_path_finalize_join(g.app.loadDir,
'..', 'plugins', 'writers', '*.py')
for fn in g.glob_glob(pattern):
sfn = g.shortFileName(fn)
if sfn != '__init__.py':
try:
# Important: use importlib to give imported modules their fully qualified names.
m = importlib.import_module('leo.plugins.writers.%s' % sfn[: -3])
self.parse_writer_dict(sfn, m)
except Exception:
g.es_exception()
g.warning('can not import leo.plugins.writers.%s' % sfn)
if trace:
g.trace('LM.writersDispatchDict')
g.printDict(g.app.writersDispatchDict)
g.trace('LM.atAutoWritersDict')
g.printDict(g.app.atAutoWritersDict)
# Creates problems: https://github.com/leo-editor/leo-editor/issues/40
#@+node:ekr.20140728040812.17991: *7* LM.parse_writer_dict
[docs] def parse_writer_dict(self, sfn, m):
'''
Set entries in g.app.writersDispatchDict and g.app.atAutoWritersDict
using entries in m.writers_dict.
'''
writer_d = getattr(m, 'writer_dict', None)
if writer_d:
at_auto = writer_d.get('@auto', [])
scanner_class = writer_d.get('class', None)
extensions = writer_d.get('extensions', [])
if at_auto:
# Make entries for each @auto type.
d = g.app.atAutoWritersDict
for s in at_auto:
aClass = d.get(s)
if aClass and aClass != scanner_class:
g.trace('%s: duplicate %s class %s in %s:' % (
sfn, s, aClass.__name__, m.__file__))
else:
d[s] = scanner_class
g.app.atAutoNames.add(s)
if extensions:
# Make entries for each extension.
d = g.app.writersDispatchDict
for ext in extensions:
aClass = d.get(ext)
if aClass and aClass != scanner_class:
g.trace('%s: duplicate %s class' % (sfn, ext),
aClass, scanner_class)
else:
d[ext] = scanner_class
elif sfn not in ('basewriter.py',):
g.warning('leo/plugins/writers/%s has no writer_dict' % sfn)
#@+node:ekr.20120219154958.10478: *5* LM.createGui
[docs] def createGui(self, pymacs):
lm = self
gui_option = lm.options.get('gui')
windowFlag = lm.options.get('windowFlag')
script = lm.options.get('script')
if g.app.gui:
if g.app.gui == g.app.nullGui:
g.app.gui = None # Enable g.app.createDefaultGui
g.app.createDefaultGui(__file__)
else:
pass
# This can happen when launching Leo from IPython.
# This can also happen when leoID does not exist.
elif gui_option is None:
if script and not windowFlag:
# Always use null gui for scripts.
g.app.createNullGuiWithScript(script)
else:
g.app.createDefaultGui(__file__)
else:
lm.createSpecialGui(gui_option, pymacs, script, windowFlag)
#@+node:ekr.20120219154958.10479: *5* LM.createSpecialGui
[docs] def createSpecialGui(self, gui, pymacs, script, windowFlag):
# lm = self
if pymacs:
g.app.createNullGuiWithScript(script=None)
elif script:
if windowFlag:
g.app.createDefaultGui()
g.app.gui.setScript(script=script)
sys.args = []
else:
g.app.createNullGuiWithScript(script=script)
else:
g.app.createDefaultGui()
#@+node:ekr.20120219154958.10480: *5* LM.adjustSysPath
[docs] def adjustSysPath(self):
'''Adjust sys.path to enable imports as usual with Leo.
This method is no longer needed:
1. g.importModule will import from the
'external' or 'extensions' folders as needed
without altering sys.path.
2. Plugins now do fully qualified imports.
'''
pass
#@+node:ekr.20120219154958.10482: *5* LM.getDefaultFile
[docs] def getDefaultFile(self):
# Get the name of the workbook.
fn = g.app.config.getString('default_leo_file')
fn = g.os_path_finalize(fn)
if not fn: return
if g.os_path_exists(fn):
return fn
elif g.os_path_isabs(fn):
# Create the file.
g.error('Using default leo file name:\n%s' % (fn))
return fn
else:
# It's too risky to open a default file if it is relative.
return None
#@+node:ekr.20120219154958.10484: *5* LM.initApp
[docs] def initApp(self, verbose):
self.createAllImporetersData()
# Can be done early. Uses only g.app.loadDir
assert g.app.loadManager
import leo.core.leoBackground as leoBackground
import leo.core.leoConfig as leoConfig
import leo.core.leoNodes as leoNodes
import leo.core.leoPlugins as leoPlugins
import leo.core.leoSessions as leoSessions
# Import leoIPython only if requested. The import is quite slow.
self.setStdStreams()
if g.app.useIpython:
import leo.core.leoIPython as leoIPython
# This launches the IPython Qt Console. It *is* required.
assert leoIPython # suppress pyflakes/flake8 warning.
# Make sure we call the new leoPlugins.init top-level function.
leoPlugins.init()
# Force the user to set g.app.leoID.
g.app.setLeoID(verbose=verbose)
# Create early classes *after* doing plugins.init()
g.app.idleTimeManager = IdleTimeManager()
g.app.backgroundProcessManager = leoBackground.BackgroundProcessManager()
g.app.externalFilesController = leoExternalFiles.ExternalFilesController()
g.app.recentFilesManager = RecentFilesManager()
g.app.config = leoConfig.GlobalConfigManager()
g.app.nodeIndices = leoNodes.NodeIndices(g.app.leoID)
g.app.sessionManager = leoSessions.SessionManager()
# Complete the plugins class last.
g.app.pluginsController.finishCreate()
#@+node:ekr.20120219154958.10486: *5* LM.scanOptions & helpers
[docs] def scanOptions(self, fileName, pymacs):
'''Handle all options, remove them from sys.argv and set lm.options.'''
lm = self
lm.old_argv = sys.argv[:]
parser = optparse.OptionParser(
usage="usage: launchLeo.py [options] file1, file2, ...")
# Automatically implements the --help option.
#
# Parse the options, and remove them from sys.argv.
self.addOptionsToParser(parser)
options, args = parser.parse_args()
sys.argv = [sys.argv[0]]
sys.argv.extend(args)
# Handle simple args...
self.doSimpleOptions(options)
# Compute the lm.files ivar.
lm.files = lm.computeFilesList(options, fileName)
script = None if pymacs else self.doScriptOption(options, parser)
d = {
'gui': lm.doGuiOption(options),
'load_type': lm.doLoadTypeOption(options),
'screenshot_fn': lm.doScreenShotOption(options),
# --screen-shot=fn
'script': script,
'select': options.select and options.select.strip('"'),
# --select=headline
'theme_path': options.theme,
# --theme=name
'version': options.version,
# --version: print the version and exit.
'windowFlag': script and options.script_window,
'windowSize': lm.doWindowSizeOption(options),
}
return d
#@+node:ekr.20180312150559.1: *6* LM.addOptionsToParser
#@@nobeautify
[docs] def addOptionsToParser(self, parser):
add = parser.add_option
def add_bool(option, help, dest=None):
add(option, action='store_true', dest=dest, help=help)
def add_other(option, help, dest=None, m=None):
add(option, dest=dest, help=help, metavar=m)
add_bool('--diff', 'use Leo as an external git diff')
add_bool('--fullscreen', 'start fullscreen')
add_bool('--ipython', 'enable ipython support')
add_bool('--fail-fast', 'stop unit tests after the first failure')
add_other('--gui', 'gui to use (qt/qttabs/console/null)')
add_bool('--listen-to-log', 'start log_listener.py on startup')
add_other('--load-type', '@<file> type for non-outlines', m='TYPE')
add_bool('--maximized', 'start maximized')
add_bool('--minimized', 'start minimized')
add_bool('--no-cache', 'disable reading of cached files')
add_bool('--no-plugins', 'disable all plugins')
add_bool('--no-splash', 'disable the splash screen')
add_other('--screen-shot', 'take a screen shot and then exit', m='PATH')
add_other('--script', 'execute a script and then exit', m="PATH")
add_bool('--script-window', 'execute script using default gui')
add_other('--select', 'headline or gnx of node to select', m='ID')
add_bool('--session-restore','restore session tabs at startup')
add_bool('--session-save', 'save session tabs on exit')
add_bool('--silent', 'disable all log messages')
add_other('--theme', 'use the named theme file', m='NAME')
add_other('--trace-binding', 'trace commands bound to a key', m='KEY')
add_bool('--trace-coloring', 'trace syntax coloring')
add_bool('--trace-events', 'trace non-key events')
add_bool('--trace-focus', 'trace changes of focus')
add_bool('--trace-gnx', 'trace gnx logic')
add_bool('--trace-ipython', 'trace ipython bridge')
add_bool('--trace-keys', 'trace key events')
add_bool('--trace-plugins', 'trace imports of plugins')
add_other('--trace-setting', 'trace where named setting is set', m="NAME")
add_bool('--trace-shutdown', 'trace shutdown logic')
add_bool('--trace-themes', 'trace theme init logic')
add_other('--window-size', 'initial window size (height x width)', m='SIZE')
# Multiple bool values.
add('-v', '--version', action='store_true',
help='print version number and exit')
#@+node:ekr.20120219154958.10483: *6* LM.computeFilesList
[docs] def computeFilesList(self, options, fileName):
'''Return the list of files on the command line.'''
lm = self
files = []
if fileName:
files.append(fileName)
for arg in sys.argv[1:]:
if arg and not arg.startswith('-'):
files.append(arg)
result = []
for z in files:
# Fix #245: wrong: result.extend(glob.glob(lm.completeFileName(z)))
aList = g.glob_glob(lm.completeFileName(z))
if aList:
result.extend(aList)
else:
result.append(z)
return [g.os_path_normslashes(z) for z in result]
#@+node:ekr.20180312150805.1: *6* LM.doGuiOption
[docs] def doGuiOption(self, options):
gui = options.gui
if gui:
gui = gui.lower()
if gui == 'qttabs':
g.app.qt_use_tabs = True
elif gui in ('console', 'curses', 'text', 'qt', 'null'):
# text: cursesGui.py, curses: cursesGui2.py.
g.app.qt_use_tabs = False
else:
print('scanOptions: unknown gui: %s. Using qt gui' % gui)
gui = 'qt'
g.app.qt_use_tabs = False
elif sys.platform == 'darwin':
gui = 'qt'
g.app.qt_use_tabs = False
else:
gui = 'qttabs'
g.app.qt_use_tabs = True
assert gui
g.app.guiArgName = gui
return gui
#@+node:ekr.20180312152329.1: *6* LM.doLoadTypeOption
[docs] def doLoadTypeOption(self, options):
s = options.load_type
s = s.lower() if s else 'edit'
return '@' + s
#@+node:ekr.20180312152609.1: *6* LM.doScreenShotOption
[docs] def doScreenShotOption(self, options):
# --screen-shot=fn
s = options.screen_shot
if s:
s = s.strip('"')
return s
#@+node:ekr.20180312153008.1: *6* LM.doScriptOption
[docs] def doScriptOption(self, options, parser):
# --script
script = options.script
if script:
fn = g.os_path_finalize_join(g.app.loadDir, script)
script, e = g.readFileIntoString(fn, kind='script:')
else:
script = None
return script
#@+node:ekr.20180312151544.1: *6* LM.doSimpleOptions
[docs] def doSimpleOptions(self, options):
'''These args just set g.app ivars.'''
# --fail-fast
g.app.failFast = options.fail_fast
# --fullscreen
g.app.start_fullscreen = options.fullscreen
# --git-diff
g.app.diff = options.diff
# --listen-to-log
g.app.listen_to_log_flag = options.listen_to_log
# --ipython
g.app.useIpython = options.ipython
# --maximized
g.app.start_maximized = options.maximized
# --minimized
g.app.start_minimized = options.minimized
# --no-cache
if options.no_cache:
g.enableDB = False
# --no-plugins
if options.no_plugins:
g.app.enablePlugins = False
# --no-splash: --minimized disables the splash screen
g.app.use_splash_screen = (
not options.no_splash and
not options.minimized)
# --session-restore & --session-save
g.app.restore_session = bool(options.session_restore)
g.app.save_session = bool(options.session_save)
# --silent
g.app.silentMode = options.silent
#
# Most --trace- options append items to g.app.debug.
table = (
('coloring', options.trace_coloring),
('events', options.trace_events), # New
('focus', options.trace_focus),
('gnx', options.trace_gnx), # New.
('keys', options.trace_keys), # New
('ipython', options.trace_ipython), # New
('plugins', options.trace_plugins),
('shutdown', options.trace_shutdown),
('themes', options.trace_themes),
)
for val, option in table:
if option:
g.app.debug.append(val)
#
# These are not bool options.
# --trace-binding
g.app.trace_binding = options.trace_binding
# g.app.config does not exist yet.
#
# --trace-setting=setting
g.app.trace_setting = options.trace_setting
# g.app.config does not exist yet.
#@+node:ekr.20180312154839.1: *6* LM.doWindowSizeOption
[docs] def doWindowSizeOption(self, options):
# --window-size
windowSize = options.window_size
if windowSize:
try:
h, w = windowSize.split('x')
windowSize = int(h), int(w)
except ValueError:
windowSize = None
print('scanOptions: bad --window-size:', windowSize)
return windowSize
#@+node:ekr.20160718072648.1: *5* LM.setStdStreams
[docs] def setStdStreams(self):
'''
Make sure that stdout and stderr exist.
This is an issue when running Leo with pythonw.exe.
'''
# pdb requires sys.stdin, which doesn't exist when using pythonw.exe.
# import pdb ; pdb.set_trace()
import sys
import leo.core.leoGlobals as g
# Define class LeoStdOut
#@+others
#@+node:ekr.20160718091844.1: *6* class LeoStdOut
class LeoStdOut:
'''A class to put stderr & stdout to Leo's log pane.'''
def __init__(self, kind):
self.kind = kind
g.es_print = self.write
g.pr = self.write
def flush(self, *args, **keys):
pass
#@+others
#@+node:ekr.20160718102306.1: *7* LeoStdOut.write
def write(self, *args, **keys):
'''Put all non-keyword args to the log pane, as in g.es.'''
#
# Tracing will lead to unbounded recursion unless
# sys.stderr has been redirected on the command line.
app = g.app
if not app or app.killed: return
if app.gui and app.gui.consoleOnly: return
log = app.log
# Compute the effective args.
d = {
'color': None,
'commas': False,
'newline': True,
'spaces': True,
'tabName': 'Log',
}
# Handle keywords for g.pr and g.es_print.
d = g.doKeywordArgs(keys, d)
color = d.get('color')
if color == 'suppress': return
elif log and color is None:
color = g.actualColor('black')
color = g.actualColor(color)
tabName = d.get('tabName') or 'Log'
newline = d.get('newline')
s = g.translateArgs(args, d)
if app.batchMode:
if log:
log.put(s)
elif log and app.logInited:
# from_redirect is the big difference between this and g.es.
log.put(s, color=color, tabName=tabName, from_redirect=True)
else:
app.logWaiting.append((s, color, newline),)
#@-others
#@-others
if not sys.stdout:
sys.stdout = sys.__stdout__ = LeoStdOut('stdout')
if not sys.stderr:
sys.stderr = sys.__stderr__ = LeoStdOut('stderr')
#@+node:ekr.20120219154958.10491: *4* LM.isValidPython & emergency (Tk) dialog class
[docs] def isValidPython(self):
if sys.platform == 'cli':
return True
minimum_python_version = '2.6'
message = """\
Leo requires Python %s or higher.
You may download Python from
http://python.org/download/
""" % minimum_python_version
try:
version = '.'.join([str(sys.version_info[i]) for i in (0, 1, 2)])
ok = g.CheckVersion(version, minimum_python_version)
if not ok:
print(message)
try:
# g.app.gui does not exist yet.
import Tkinter as Tk
#@+<< define emergency dialog class >>
#@+node:ekr.20120219154958.10492: *5* << define emergency dialog class >>
class EmergencyDialog(object):
"""A class that creates an Tkinter dialog with a single OK button."""
#@+others
#@+node:ekr.20120219154958.10493: *6* __init__ (emergencyDialog)
def __init__(self, title, message):
"""Constructor for the leoTkinterDialog class."""
self.answer = None # Value returned from run()
self.title = title
self.message = message
self.buttonsFrame = None # Frame to hold typical dialog buttons.
self.defaultButtonCommand = None
# Command to call when user closes the window
# by clicking the close box.
self.frame = None # The outermost frame.
self.root = None # Created in createTopFrame.
self.top = None # The toplevel Tk widget.
self.createTopFrame()
buttons = tuple({"text": "OK", "command": self.okButton, "default": True})
# Singleton tuple.
self.createButtons(buttons)
self.top.bind("<Key>", self.onKey)
#@+node:ekr.20120219154958.10494: *6* createButtons
def createButtons(self, buttons):
"""Create a row of buttons.
buttons is a list of dictionaries containing
the properties of each button."""
assert(self.frame)
self.buttonsFrame = f = Tk.Frame(self.top)
f.pack(side="top", padx=30)
# Buttons is a list of dictionaries, with an empty dictionary
# at the end if there is only one entry.
buttonList = []
for d in buttons:
text = d.get("text", "<missing button name>")
isDefault = d.get("default", False)
underline = d.get("underline", 0)
command = d.get("command", None)
bd = 4 if isDefault else 2
b = Tk.Button(f, width=6, text=text, bd=bd,
underline=underline, command=command)
b.pack(side="left", padx=5, pady=10)
buttonList.append(b)
if isDefault and command:
self.defaultButtonCommand = command
return buttonList
#@+node:ekr.20120219154958.10495: *6* createTopFrame
def createTopFrame(self):
"""Create the Tk.Toplevel widget for a leoTkinterDialog."""
self.root = Tk.Tk()
self.top = Tk.Toplevel(self.root)
self.top.title(self.title)
self.root.withdraw()
self.frame = Tk.Frame(self.top)
self.frame.pack(side="top", expand=1, fill="both")
label = Tk.Label(self.frame, text=message, bg='white')
label.pack(pady=10)
#@+node:ekr.20120219154958.10496: *6* okButton
def okButton(self):
"""Do default click action in ok button."""
self.top.destroy()
self.top = None
#@+node:ekr.20120219154958.10497: *6* onKey
def onKey(self, event):
"""Handle Key events in askOk dialogs."""
self.okButton()
return # (for Tk) "break"
#@+node:ekr.20120219154958.10498: *6* run
def run(self):
"""Run the modal emergency dialog."""
self.top.geometry("%dx%d%+d%+d" % (300, 200, 50, 50))
self.top.lift()
self.top.grab_set() # Make the dialog a modal dialog.
self.root.wait_window(self.top)
#@-others
#@-<< define emergency dialog class >>
d = EmergencyDialog(
title='Python Version Error',
message=message)
d.run()
except Exception:
pass
return ok
except Exception:
print("isValidPython: unexpected exception: g.CheckVersion")
traceback.print_exc()
return 0
#@+node:ekr.20120223062418.10393: *4* LM.loadLocalFile & helper
[docs] def loadLocalFile(self, fn, gui, old_c):
'''Completely read a file, creating the corresonding outline.
1. If fn is an existing .leo file (possibly zipped), read it twice:
the first time with a NullGui to discover settings,
the second time with the requested gui to create the outline.
2. If fn is an external file:
get settings from the leoSettings.leo and myLeoSetting.leo, then
create a "wrapper" outline continain an @file node for the external file.
3. If fn is empty:
get settings from the leoSettings.leo and myLeoSetting.leo or default settings,
or open an empty outline.
'''
lm = self
# Step 0: Return if the file is already open.
fn = g.os_path_finalize(fn)
if fn:
c = lm.findOpenFile(fn)
if c:
return c
# Step 1: get the previous settings.
# For .leo files (and zipped .leo files) this pre-reads the file in a null gui.
# Otherwise, get settings from leoSettings.leo, myLeoSettings.leo, or default settings.
previousSettings = lm.getPreviousSettings(fn)
# Step 2: open the outline in the requested gui.
# For .leo files (and zipped .leo file) this opens the file a second time.
c = lm.openFileByName(fn, gui, old_c, previousSettings)
return c
#@+node:ekr.20120223062418.10394: *5* LM.openFileByName & helpers
[docs] def openFileByName(self, fn, gui, old_c, previousSettings):
'''Read the local file whose full path is fn using the given gui.
fn may be a Leo file (including .leo or zipped file) or an external file.
This is not a pre-read: the previousSettings always exist and
the commander created here persists until the user closes the outline.
Reads the entire outline if fn exists and is a .leo file or zipped file.
Creates an empty outline if fn is a non-existent Leo file.
Creates an wrapper outline if fn is an external file, existing or not.
'''
lm = self
# Disable the log.
g.app.setLog(None)
g.app.lockLog()
# Create the a commander for the .leo file.
# Important. The settings don't matter for pre-reads!
# For second read, the settings for the file are *exactly* previousSettings.
c = g.app.newCommander(fileName=fn, gui=gui,
previousSettings=previousSettings)
assert c
# Open the file, if possible.
g.doHook('open0')
theFile = lm.openLeoOrZipFile(fn)
if isinstance(theFile, sqlite3.Connection):
# this commander is associated with sqlite db
c.sqlite_connection = theFile
# Enable the log.
g.app.unlockLog()
c.frame.log.enable(True)
# Phase 2: Create the outline.
g.doHook("open1", old_c=None, c=c, new_c=c, fileName=fn)
if theFile:
readAtFileNodesFlag = bool(previousSettings)
# The log is not set properly here.
ok = lm.readOpenedLeoFile(c, fn, readAtFileNodesFlag, theFile)
# Call c.fileCommands.openLeoFile to read the .leo file.
if not ok: return None
else:
# Create a wrapper .leo file if:
# a) fn is a .leo file that does not exist or
# b) fn is an external file, existing or not.
lm.initWrapperLeoFile(c, fn)
g.doHook("open2", old_c=None, c=c, new_c=c, fileName=fn)
# Phase 3: Complete the initialization.
g.app.writeWaitingLog(c)
c.setLog()
lm.createMenu(c, fn)
lm.finishOpen(c)
return c
#@+node:ekr.20120223062418.10405: *6* LM.createMenu
# Fix bug 844953: tell Unity which menu to use.
# c.enableMenuBar()
#@+node:ekr.20120223062418.10406: *6* LM.findOpenFile
[docs] def findOpenFile(self, fn):
# lm = self
def munge(name):
return g.os_path_normpath(name or '').lower()
for frame in g.app.windowList:
c = frame.c
if g.os_path_realpath(munge(fn)) == g.os_path_realpath(munge(c.mFileName)):
# don't frame.bringToFront(), it breaks --minimize
c.setLog()
# 2011/11/21: selecting the new tab ensures focus is set.
master = hasattr(frame.top, 'leo_master') and frame.top.leo_master
if master: # frame.top.leo_master is a TabbedTopLevel.
master.select(frame.c)
c.outerUpdate()
return c
return None
#@+node:ekr.20120223062418.10407: *6* LM.finishOpen
[docs] def finishOpen(self, c):
# lm = self
k = c.k
assert k
# New in Leo 4.6: provide an official way for very late initialization.
c.frame.tree.initAfterLoad()
c.initAfterLoad()
c.redraw()
# chapterController.finishCreate must be called after the first real redraw
# because it requires a valid value for c.rootPosition().
if c.chapterController: c.chapterController.finishCreate()
if k: k.setDefaultInputState()
c.initialFocusHelper()
if k: k.showStateAndMode()
c.frame.initCompleteHint()
c.outerUpdate()
# Honor focus requests.
# This fixes bug 181: Focus remains in previous file
# https://github.com/leo-editor/leo-editor/issues/181
#@+node:ekr.20120223062418.10408: *6* LM.initWrapperLeoFile
[docs] def initWrapperLeoFile(self, c, fn):
'''
Create an empty file if the external fn is empty.
Otherwise, create an @edit or @file node for the external file.
'''
# lm = self
# Use the config params to set the size and location of the window.
frame = c.frame
frame.setInitialWindowGeometry()
frame.deiconify()
frame.lift()
frame.resizePanesToRatio(frame.ratio, frame.secondary_ratio)
# Resize the _new_ frame.
if not g.os_path_exists(fn):
p = c.rootPosition()
# Create an empty @edit node unless fn is an .leo file.
p.h = g.shortFileName(fn) if fn.endswith('.leo') else '@edit %s' % fn
c.selectPosition(p)
elif c.looksLikeDerivedFile(fn):
# 2011/10/10: Create an @file node.
p = c.importCommands.importDerivedFiles(parent=c.rootPosition(),
paths=[fn], command=None) # Not undoable.
if p and p.hasBack():
p.back().doDelete()
p = c.rootPosition()
if not p: return None
else:
# Create an @<file> node.
p = c.rootPosition()
if p:
load_type = self.options['load_type']
p.setHeadString('%s %s' % (load_type,fn))
c.refreshFromDisk()
c.selectPosition(p)
# Fix critical bug 1184855: data loss with command line 'leo somefile.ext'
# Fix smallish bug 1226816 Command line "leo xxx.leo" creates file xxx.leo.leo.
c.mFileName = fn if fn.endswith('.leo') else '%s.leo' % (fn)
c.wrappedFileName = fn
c.frame.title = c.computeWindowTitle(c.mFileName)
c.frame.setTitle(c.frame.title)
# chapterController.finishCreate must be called after the first real redraw
# because it requires a valid value for c.rootPosition().
if c.config.getBool('use_chapters') and c.chapterController:
c.chapterController.finishCreate()
frame.c.setChanged(False)
# Mark the outline clean.
# This makes it easy to open non-Leo files for quick study.
return c
#@+node:ekr.20120223062418.10419: *6* LM.isLeoFile & LM.isZippedFile
[docs] def isLeoFile(self, fn):
if g.SQLITE:
return fn and (zipfile.is_zipfile(fn) or fn.endswith('.leo') or fn.endswith('.db'))
return fn and (zipfile.is_zipfile(fn) or fn.endswith('.leo'))
[docs] def isZippedFile(self, fn):
return fn and zipfile.is_zipfile(fn)
#@+node:ekr.20120224161905.10030: *6* LM.openLeoOrZipFile
[docs] def openLeoOrZipFile(self, fn):
lm = self
if g.SQLITE:
if fn.endswith('.db'):
return sqlite3.connect(fn)
zipped = lm.isZippedFile(fn)
if lm.isLeoFile(fn) and g.os_path_exists(fn):
if zipped:
theFile = lm.openZipFile(fn)
else:
theFile = lm.openLeoFile(fn)
else:
theFile = None
return theFile
#@+node:ekr.20120223062418.10416: *6* LM.openLeoFile
[docs] def openLeoFile(self, fn):
# lm = self
try:
theFile = open(fn, 'rb')
return theFile
except IOError:
# Do not use string + here: it will fail for non-ascii strings!
if not g.unitTesting:
g.error("can not open:", fn)
return None
#@+node:ekr.20120223062418.10410: *6* LM.openZipFile
[docs] def openZipFile(self, fn):
# lm = self
try:
theFile = zipfile.ZipFile(fn, 'r')
if not theFile: return None
# Read the file into an StringIO file.
aList = theFile.namelist()
name = aList and len(aList) == 1 and aList[0]
if not name: return None
s = theFile.read(name)
if g.isPython3: s = g.ue(s, 'utf-8')
return StringIO(s)
except IOError:
# Do not use string + here: it will fail for non-ascii strings!
if not g.unitTesting:
g.error("can not open:", fn)
return None
#@+node:ekr.20120223062418.10412: *6* LM.readOpenedLeoFile
[docs] def readOpenedLeoFile(self, c, fn, readAtFileNodesFlag, theFile):
# New in Leo 4.10: The open1 event does not allow an override of the init logic.
assert theFile
# lm = self
ok = c.fileCommands.openLeoFile(theFile, fn,
readAtFileNodesFlag=readAtFileNodesFlag)
# closes file.
if ok:
if not c.openDirectory:
theDir = c.os_path_finalize(g.os_path_dirname(fn))
c.openDirectory = c.frame.openDirectory = theDir
else:
g.app.closeLeoWindow(c.frame, finish_quit=self.more_cmdline_files is False)
return ok
#@+node:ekr.20160430063406.1: *3* LM.revertCommander
[docs] def revertCommander(self, c):
'''Revert c to the previously saved contents.'''
lm = self
fn = c.mFileName
# Re-read the file.
theFile = lm.openLeoOrZipFile(fn)
if theFile:
c.fileCommands.initIvars()
c.fileCommands.getLeoFile(theFile, fn, checkOpenFiles=False)
# Closes the file.
#@-others
#@+node:ekr.20120223062418.10420: ** class PreviousSettings
[docs]class PreviousSettings(object):
'''A class holding the settings and shortcuts dictionaries
that are computed in the first pass when loading local
files and passed to the second pass.'''
def __init__(self, settingsDict, shortcutsDict):
assert g.isTypedDict(settingsDict)
assert g.isTypedDictOfLists(shortcutsDict)
self.settingsDict = settingsDict
self.shortcutsDict = shortcutsDict
def __repr__(self):
return '<PreviousSettings\n%s\n%s\n>' % (
self.settingsDict, self.shortcutsDict)
__str__ = __repr__
#@+node:ekr.20120225072226.10283: ** class RecentFilesManager
[docs]class RecentFilesManager(object):
'''A class to manipulate leoRecentFiles.txt.'''
def __init__(self):
self.edit_headline = 'Recent files. Do not change this headline!'
# Headline used by
self.groupedMenus = []
# Set in rf.createRecentFilesMenuItems.
self.recentFiles = []
# List of g.Bunches describing .leoRecentFiles.txt files.
self.recentFilesMenuName = 'Recent Files'
# May be changed later.
self.recentFileMessageWritten = False
# To suppress all but the first message.
self.write_recent_files_as_needed = False
# Will be set later.
#@+others
#@+node:ekr.20041201080436: *3* rf.appendToRecentFiles
[docs] def appendToRecentFiles(self, files):
rf = self
files = [theFile.strip() for theFile in files]
def munge(name):
return g.os_path_normpath(name or '').lower()
for name in files:
# Remove all variants of name.
for name2 in rf.recentFiles[:]:
if munge(name) == munge(name2):
rf.recentFiles.remove(name2)
rf.recentFiles.append(name)
#@+node:ekr.20120225072226.10289: *3* rf.cleanRecentFiles
[docs] def cleanRecentFiles(self, c):
'''
Remove items from the recent files list that no longer exist.
This almost never does anything because Leo's startup logic removes
nonexistent files from the recent files list.
'''
result = [z for z in self.recentFiles if g.os_path_exists(z)]
if result != self.recentFiles:
for path in result:
self.updateRecentFiles(path)
self.writeRecentFilesFile(c, force=True)
#@+node:ekr.20180212141017.1: *3* rf.demangleRecentFiles
[docs] def demangleRecentFiles(self, c, data):
'''Rewrite recent files based on c.config.getData('path-demangle')'''
changes = []
replace = None
for line in data:
text = line.strip()
if text.startswith('REPLACE: '):
replace = text.split(None, 1)[1].strip()
if text.startswith('WITH:') and replace is not None:
with_ = text[5:].strip()
changes.append((replace, with_))
g.es('%s -> %s' % changes[-1])
orig = [z for z in self.recentFiles if z.startswith("/")]
self.recentFiles = []
for i in orig:
t = i
for change in changes:
t = t.replace(*change)
self.updateRecentFiles(t)
self.writeRecentFilesFile(c, force=True)
# Force the write message.
#@+node:ekr.20120225072226.10297: *3* rf.clearRecentFiles
[docs] def clearRecentFiles(self, c):
"""Clear the recent files list, then add the present file."""
rf = self; u = c.undoer; menu = c.frame.menu
bunch = u.beforeClearRecentFiles()
recentFilesMenu = menu.getMenu(self.recentFilesMenuName)
menu.deleteRecentFilesMenuItems(recentFilesMenu)
rf.recentFiles = [c.fileName()]
for frame in g.app.windowList:
rf.createRecentFilesMenuItems(frame.c)
u.afterClearRecentFiles(bunch)
# Write the file immediately.
rf.writeRecentFilesFile(c, force=True)
# Force the write message.
#@+node:ekr.20120225072226.10301: *3* rf.createRecentFilesMenuItems
#@+node:vitalije.20170703115609.1: *3* rf.editRecentFiles
[docs] def editRecentFiles(self, c):
'''
Dump recentFiles into new node appended as lastTopLevel, selects it and
request focus in body.
NOTE: command write-edited-recent-files assume that headline of this
node is not changed by user.
'''
rf = self
nl = '\n' if g.isPython3 else g.u('\n')
p1 = c.lastTopLevel().insertAfter()
p1.h = self.edit_headline
p1.b = nl.join(rf.recentFiles)
c.redraw()
c.selectPosition(p1)
c.redraw()
c.bodyWantsFocusNow()
g.es('edit list and run write-rff to save recentFiles')
#@+node:ekr.20120225072226.10286: *3* rf.getRecentFiles
[docs] def getRecentFiles(self):
# Fix #299: Leo loads a deleted file.
self.recentFiles = [z for z in self.recentFiles
if g.os_path_exists(z)]
return self.recentFiles
#@+node:ekr.20120225072226.10304: *3* rf.getRecentFilesTable
[docs] def getRecentFilesTable(self):
return (
"*clear-recent-files",
"*clean-recent-files",
"*demangle-recent-files",
"*sort-recent-files",
("-",None,None),
)
#@+node:ekr.20070224115832: *3* rf.readRecentFiles & helpers
[docs] def readRecentFiles(self, localConfigFile):
'''Read all .leoRecentFiles.txt files.'''
# The order of files in this list affects the order of the recent files list.
rf = self
seen = []
localConfigPath = g.os_path_dirname(localConfigFile)
for path in (
g.app.homeLeoDir,
g.app.globalConfigDir,
localConfigPath,
):
if path:
path = g.os_path_realpath(g.os_path_finalize(path))
if path and path not in seen:
ok = rf.readRecentFilesFile(path)
if ok: seen.append(path)
if not seen and rf.write_recent_files_as_needed:
rf.createRecentFiles()
#@+node:ekr.20061010121944: *4* rf.createRecentFiles
[docs] def createRecentFiles(self):
'''
Try to create .leoRecentFiles.txt, in the users home directory, or in
Leo's config directory if that fails.
'''
for theDir in (g.app.homeLeoDir, g.app.globalConfigDir):
if theDir:
fn = g.os_path_join(theDir, '.leoRecentFiles.txt')
try:
with open(fn, 'w'):
g.red('created', fn)
return
except IOError:
g.error('can not create', fn)
g.es_exception()
#@+node:ekr.20050424115658: *4* rf.readRecentFilesFile
[docs] def readRecentFilesFile(self, path):
fileName = g.os_path_join(path, '.leoRecentFiles.txt')
if not g.os_path_exists(fileName):
return False
try:
with io.open(fileName, encoding='utf-8', mode='r') as f:
try: # Fix #471.
lines = f.readlines()
except Exception:
lines = None
except IOError:
# The file exists, so FileNotFoundError is not possible.
g.trace('can not open', fileName)
return False
if lines and self.sanitize(lines[0]) == 'readonly':
lines = lines[1:]
if lines:
lines = [g.toUnicode(g.os_path_normpath(line)) for line in lines]
self.appendToRecentFiles(lines)
return True
#@+node:ekr.20120225072226.10285: *3* rf.sanitize
[docs] def sanitize(self, name):
'''Return a sanitized file name.'''
if name is None:
return None
name = name.lower()
for ch in ('-', '_', ' ', '\n'):
name = name.replace(ch, '')
return name or None
#@+node:ekr.20120215072959.12478: *3* rf.setRecentFiles
[docs] def setRecentFiles(self, files):
'''Update the recent files list.'''
rf = self
rf.appendToRecentFiles(files)
#@+node:ekr.20120225072226.10293: *3* rf.sortRecentFiles
[docs] def sortRecentFiles(self, c):
'''Sort the recent files list.'''
rf = self
def key(path):
# Sort only the base name. That's what will appear in the menu.
s = g.os_path_basename(path)
return s.lower() if sys.platform.lower().startswith('win') else s
aList = sorted(rf.recentFiles, key=key)
rf.recentFiles = []
for z in reversed(aList):
rf.updateRecentFiles(z)
rf.writeRecentFilesFile(c, force=True)
# Force the write message.
#@+node:ekr.20031218072017.2083: *3* rf.updateRecentFiles
[docs] def updateRecentFiles(self, fileName):
"""Create the RecentFiles menu. May be called with Null fileName."""
rf = self
if g.app.unitTesting: return
def munge(name):
return g.os_path_finalize(name or '').lower()
def munge2(name):
return g.os_path_finalize_join(g.app.loadDir, name or '')
# Update the recent files list in all windows.
if fileName:
for frame in g.app.windowList:
# Remove all versions of the file name.
for name in rf.recentFiles:
if (munge(fileName) == munge(name) or
munge2(fileName) == munge2(name)
):
rf.recentFiles.remove(name)
rf.recentFiles.insert(0, fileName)
# Recreate the Recent Files menu.
rf.createRecentFilesMenuItems(frame.c)
else:
for frame in g.app.windowList:
rf.createRecentFilesMenuItems(frame.c)
#@+node:vitalije.20170703115616.1: *3* rf.writeEditedRecentFiles
[docs] def writeEditedRecentFiles(self, c):
'''
Write content of "edit_headline" node as recentFiles and recreates
menues.
'''
rf = self; p = c.p
p = g.findNodeAnywhere(c, self.edit_headline)
if p:
files = [z for z in p.b.splitlines() if z and g.os_path_exists(z)]
rf.recentFiles = files
rf.writeRecentFilesFile(c, force=False)
rf.updateRecentFiles(None)
c.selectPosition(p)
c.deleteOutline()
else:
g.red('not found:', self.edit_headline)
#@+node:ekr.20050424114937.2: *3* rf.writeRecentFilesFile & helper
[docs] def writeRecentFilesFile(self, c, force=False):
'''
Write the appropriate .leoRecentFiles.txt file.
Write a message if force is True, or if it hasn't been written yet.
'''
tag = '.leoRecentFiles.txt'
rf = self
# tag:#661. Do nothing if in leoBride.
if g.app.unitTesting or g.app.inBridge:
return
localFileName = c.fileName()
if localFileName:
localPath, junk = g.os_path_split(localFileName)
else:
localPath = None
written = False
seen = []
for path in (localPath, g.app.globalConfigDir, g.app.homeLeoDir):
if path:
fileName = g.os_path_join(path, tag)
if g.os_path_exists(fileName) and fileName.lower() not in seen:
seen.append(fileName.lower())
ok = rf.writeRecentFilesFileHelper(fileName)
if force or not rf.recentFileMessageWritten:
if ok:
if not g.app.silentMode:
# Fix #459:
g.es_print('wrote recent file: %s' % fileName)
written = True
else:
g.error('failed to write recent file: %s' % (fileName))
# Bug fix: Leo 4.4.6: write *all* recent files.
if written:
rf.recentFileMessageWritten = True
else:
# Attempt to create .leoRecentFiles.txt in the user's home directory.
if g.app.homeLeoDir:
fileName = g.os_path_finalize_join(g.app.homeLeoDir, tag)
if not g.os_path_exists(fileName):
g.red('creating: %s' % (fileName))
rf.writeRecentFilesFileHelper(fileName)
#@+node:ekr.20050424131051: *4* rf.writeRecentFilesFileHelper
[docs] def writeRecentFilesFileHelper(self, fileName):
# Don't update the file if it begins with read-only.
#
# Part 1: Return False if the first line is "readonly".
# It's ok if the file doesn't exist.
if g.os_path_exists(fileName):
with io.open(fileName, encoding='utf-8', mode='r') as f:
try:
# Fix #471.
lines = f.readlines()
except Exception:
lines = None
if lines and self.sanitize(lines[0]) == 'readonly':
return False
# Part 2: write the files.
try:
with io.open(fileName, encoding='utf-8', mode='w') as f:
s = '\n'.join(self.recentFiles) if self.recentFiles else '\n'
f.write(g.toUnicode(s))
return True
except IOError:
g.error('error writing', fileName)
g.es_exception()
except Exception:
g.error('unexpected exception writing', fileName)
g.es_exception()
if g.unitTesting: raise
return False
#@-others
#@+node:ekr.20150514125218.1: ** Top-level-commands
#@+node:ekr.20150514125218.2: *3* ctrl-click-at-cursor
[docs]@g.command('ctrl-click-at-cursor')
def ctrlClickAtCursor(event):
'''Simulate a control-click at the cursor.'''
c = event.get('c')
if c:
g.openUrlOnClick(event)
#@+node:ekr.20180213045148.1: *3* demangle-recent-files
[docs]@g.command('demangle-recent-files')
def demangle_recent_files_command(event):
'''
Path demangling potentially alters the paths in the recent files list
according to find/replace patterns in the @data path-demangle setting.
For example:
REPLACE: .gnome-desktop
WITH: My Desktop
The default setting specifies no patterns.
'''
c = event and event.get('c')
if c:
data = c.config.getData('path-demangle')
if data:
g.app.recentFilesManager.demangleRecentFiles(c, data)
else:
g.es_print('No patterns in @data path-demangle')
#@+node:ekr.20150514125218.3: *3* enable/disable/toggle-idle-time-events
[docs]@g.command('disable-idle-time-events')
def disable_idle_time_events(event):
'''Disable default idle-time event handling.'''
g.app.idle_time_hooks_enabled = False
[docs]@g.command('enable-idle-time-events')
def enable_idle_time_events(event):
'''Enable default idle-time event handling.'''
g.app.idle_time_hooks_enabled = True
[docs]@g.command('toggle-idle-time-events')
def toggle_idle_time_events(event):
'''Toggle default idle-time event handling.'''
g.app.idle_time_hooks_enabled = not g.app.idle_time_hooks_enabled
#@+node:ekr.20150514125218.4: *3* join-leo-irc
[docs]@g.command('join-leo-irc')
def join_leo_irc(event=None):
'''Open the web page to Leo's irc channel on freenode.net.'''
import webbrowser
webbrowser.open("http://webchat.freenode.net/?channels=%23leo&uio=d4")
#@+node:ekr.20150514125218.5: *3* open-url
[docs]@g.command('open-url')
def openUrl(event=None):
'''
Open the url in the headline or body text of the selected node.
Use the headline if it contains a valid url.
Otherwise, look *only* at the first line of the body.
'''
c = event.get('c')
if c:
g.openUrl(c.p)
#@+node:ekr.20150514125218.6: *3* open-url-under-cursor
[docs]@g.command('open-url-under-cursor')
def openUrlUnderCursor(event=None):
'''Open the url under the cursor.'''
return g.openUrlOnClick(event)
#@-others
#@@language python
#@@tabwidth -4
#@@pagewidth 70
#@-leo