#! /usr/bin/env python
#@+leo-ver=5-thin
#@+node:ekr.20070227091955.1: * @file leoBridge.py
#@@first
'''A module to allow full access to Leo commanders from outside Leo.'''
#@@language python
#@@tabwidth -4
#@+<< about the leoBridge module >>
#@+node:ekr.20070227091955.2: ** << about the leoBridge module >>
#@+at
#@@language rest
#@@wrap
#
# A **host** program is a Python program separate from Leo. Host programs may
# be created by Leo, but at the time they are run host programs must not be
# part of Leo in any way. So if they are run from Leo, they must be run in a
# separate process.
#
# The leoBridge module gives host programs access to all aspects of Leo,
# including all of Leo's source code, the contents of any .leo file, all
# configuration settings in .leo files, etc.
#
# Host programs will use the leoBridge module like this::
#
# import leo.core.leoBridge as leoBridge
# bridge = leoBridge.controller(gui='nullGui',verbose=False)
# if bridge.isOpen():
# g = bridge.globals()
# c = bridge.openLeoFile(path)
#
# Notes:
#
# - The leoBridge module imports no modules at the top level.
#
# - leoBridge.controller creates a singleton *bridge controller* that grants
# access to Leo's objects, including fully initialized g and c objects. In
# particular, the g.app and g.app.gui vars are fully initialized.
#
# - By default, leoBridge.controller creates a null gui so that no Leo
# windows appear on the screen.
#
# - As shown above, the host program should gain access to Leo's leoGlobals
# module using bridge.globals(). The host program should not import
# leo.core.leoGlobals as leoGlobals directly.
#
# - bridge.openLeoFile(path) returns a completely standard Leo commander.
# Host programs can use these commanders as described in Leo's scripting
# chapter.
#@-<< about the leoBridge module >>
gBridgeController = None # The singleton bridge controller.
# This module must import *no* modules at the outer level!
#@+others
#@+node:ekr.20070227092442: ** controller
[docs]def controller(
gui='nullGui',
loadPlugins=True,
readSettings=True,
silent=False,
tracePlugins=False,
verbose=False
):
'''Create an singleton instance of a bridge controller.'''
global gBridgeController
if not gBridgeController:
gBridgeController = BridgeController(
gui,
loadPlugins,
readSettings,
silent,
tracePlugins,
verbose)
return gBridgeController
#@+node:ekr.20070227092442.2: ** class BridgeController
[docs]class BridgeController(object):
'''Creates a way for host programs to access Leo.'''
#@+others
#@+node:ekr.20070227092442.3: *3* bridge.ctor
def __init__(self, guiName, loadPlugins, readSettings, silent, tracePlugins, verbose):
'''Ctor for the BridgeController class.'''
self.g = None
self.gui = None
self.guiName = guiName or 'nullGui'
self.loadPlugins = loadPlugins
self.readSettings = readSettings
self.silentMode = silent
self.tracePlugins = tracePlugins
self.verbose = verbose
self.mainLoop = False # True only if a non-null-gui mainloop is active.
self.initLeo()
#@+node:ekr.20070227092442.4: *3* bridge.globals
[docs] def globals(self):
'''Return a fully initialized leoGlobals module.'''
return self.isOpen() and self.g
#@+node:ekr.20070227093530: *3* bridge.initLeo & helpers
[docs] def initLeo(self):
'''
Init the Leo app to which this class gives access.
This code is based on leo.run().
'''
if not self.isValidPython():
return
#@+<< initLeo imports >>
#@+node:ekr.20070227093629.1: *4* << initLeo imports >> initLeo (leoBridge)
# Import leoGlobals, but do NOT set g.
try:
import leo.core.leoGlobals as leoGlobals
except ImportError:
print("Error importing leoGlobals.py")
# Create the application object.
try:
leoGlobals.in_bridge = True
# Tell leoApp.createDefaultGui not to create a gui.
# This module will create the gui later.
import leo.core.leoApp as leoApp
leoGlobals.app = leoApp.LeoApp()
except ImportError:
print("Error importing leoApp.py")
# NOW we can set g.
self.g = g = leoGlobals
assert(g.app)
g.app.leoID = None
if self.tracePlugins:
g.app.debug.append('plugins')
g.app.silentMode = self.silentMode
# Create the g.app.pluginsController here.
import leo.core.leoPlugins as leoPlugins
leoPlugins.init() # Necessary. Sets g.app.pluginsController.
try:
import leo.core.leoNodes as leoNodes
except ImportError:
print("Error importing leoNodes.py")
import traceback; traceback.print_exc()
try:
import leo.core.leoConfig as leoConfig
except ImportError:
print("Error importing leoConfig.py")
import traceback; traceback.print_exc()
# Set leoGlobals.g here, rather than in leoGlobals.
leoGlobals.g = leoGlobals
#@-<< initLeo imports >>
g.app.recentFilesManager = leoApp.RecentFilesManager()
g.app.loadManager = lm = leoApp.LoadManager()
g.app.loadManager.computeStandardDirectories()
if not g.app.setLeoID(useDialog=False, verbose=True):
raise ValueError("unable to set LeoID.")
g.app.inBridge = True # Added 2007/10/21: support for g.getScript.
g.app.nodeIndices = leoNodes.NodeIndices(g.app.leoID)
g.app.config = leoConfig.GlobalConfigManager()
g.app.setGlobalDb() # Fix #556.
if self.readSettings:
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.
else:
# Bug fix: 2012/11/26: create default global settings dicts.
settings_d, bindings_d = lm.createDefaultSettingsDicts()
lm.globalSettingsDict = settings_d
lm.globalBindingsDict = bindings_d
self.createGui() # Create the gui *before* loading plugins.
if self.verbose: self.reportDirectories()
self.adjustSysPath()
# Kill all event handling if plugins not loaded.
if not self.loadPlugins:
def dummyDoHook(tag, *args, **keys):
pass
g.doHook = dummyDoHook
g.doHook("start1") # Load plugins.
g.app.computeSignon()
g.app.initing = False
g.doHook("start2", c=None, p=None, v=None, fileName=None)
#@+node:ekr.20070302061713: *4* bridge.adjustSysPath
[docs] def adjustSysPath(self):
'''Adjust sys.path to enable imports as usual with Leo.'''
import sys
g = self.g
leoDirs = ('config', 'doc', 'extensions', 'modes', 'plugins', 'core', 'test') # 2008/7/30
for theDir in leoDirs:
path = g.os_path_finalize_join(g.app.loadDir, '..', theDir)
if path not in sys.path:
sys.path.append(path)
# Attempt a fix for bug #258: leoBridge does not work with @auto-md subtrees.
# https://github.com/leo-editor/leo-editor/issues/258
for theDir in ('importers', 'writers'):
path = g.os_path_finalize_join(g.app.loadDir, '..', 'plugins', theDir)
if path not in sys.path:
sys.path.append(path)
#@+node:ekr.20070227095743: *4* bridge.createGui
[docs] def createGui(self):
g = self.g
if self.guiName == 'nullGui':
g.app.gui = g.app.nullGui
g.app.log = g.app.gui.log = log = g.app.nullLog
log.isNull = False
log.enabled = True # Allow prints from NullLog.
log.logInited = True # Bug fix: 2012/10/17.
elif self.guiName == 'qt':
import leo.plugins.qt_gui as qt_gui
g.app.gui = qt_gui.LeoQtGui()
print('Qt gui created')
else:
assert False, 'leoBridge.py: unsupported gui: %s' % self.guiName
#@+node:ekr.20070227093629.4: *4* bridge.isValidPython
[docs] def isValidPython(self):
import sys
if sys.platform == 'cli':
return True
message = """\
Leo requires Python 2.2.1 or higher.
You may download Python from http://python.org/download/
"""
try:
# This will fail if True/False are not defined.
import leo.core.leoGlobals as g
# print('leoBridge:isValidPython:g',g)
# Set leoGlobals.g here, rather than in leoGlobals.py.
leoGlobals = g # Don't set g.g, it would pollute the autocompleter.
leoGlobals.g = g
except ImportError:
print("isValidPython: can not import leo.core.leoGlobals as leoGlobals")
return 0
except Exception:
print("isValidPytyhon: unexpected exception: import leo.core.leoGlobals as leoGlobals.py as g")
import traceback; traceback.print_exc()
return 0
try:
version = '.'.join([str(sys.version_info[i]) for i in (0, 1, 2)])
ok = g.CheckVersion(version, '2.2.1')
if not ok:
print(message)
g.app.gui.runAskOkDialog(None, "Python version error", message=message, text="Exit")
return ok
except Exception:
print("isValidPython: unexpected exception: g.CheckVersion")
import traceback; traceback.print_exc()
return 0
#@+node:ekr.20070227093629.9: *4* bridge.reportDirectories
[docs] def reportDirectories(self):
if not self.silentMode:
g = self.g
for kind, theDir in (
("global config", g.app.globalConfigDir),
("home", g.app.homeDir),
):
g.blue('', kind, 'directory', '', ':', theDir)
#@+node:ekr.20070227093918: *3* bridge.isOpen
[docs] def isOpen(self):
'''Return True if the bridge is open.'''
g = self.g
return bool(g and g.app and g.app.gui)
#@+node:ekr.20070227092442.5: *3* bridge.openLeoFile & helpers
[docs] def openLeoFile(self, fileName):
'''Open a .leo file, or create a new Leo frame if no fileName is given.'''
g = self.g
g.app.silentMode = self.silentMode
useLog = False
if self.isOpen():
fileName = self.completeFileName(fileName)
c = self.createFrame(fileName)
g.app.nodeIndices.compute_last_index(c)
# New in Leo 5.1. An alternate fix for bug #130.
# When using a bridge Leo might open a file, modify it,
# close it, reopen it and change it all within one second.
# In that case, this code must properly compute the next
# available gnx by scanning the entire outline.
if useLog:
g.app.gui.log = log = c.frame.log
log.isNull = False
log.enabled = True
return c
else:
return None
#@+node:ekr.20070227093629.5: *4* bridge.completeFileName
[docs] def completeFileName(self, fileName):
g = self.g
if not (fileName and fileName.strip()): return ''
import os
fileName = g.os_path_finalize_join(os.getcwd(), fileName)
head, ext = g.os_path_splitext(fileName)
if not ext: fileName = fileName + ".leo"
return fileName
#@+node:ekr.20070227093629.6: *4* bridge.createFrame
[docs] def createFrame(self, fileName):
'''Create a commander and frame for the given file.
Create a new frame if the fileName is empty or non-exisent.'''
g = self.g
if fileName.strip():
if g.os_path_exists(fileName):
# This takes a long time due to imports in c.__init__
c = g.openWithFileName(fileName)
if c: return c
elif not self.silentMode:
print('file not found: %s. creating new window' % (fileName))
# Create a new frame. Unlike leo.run, this is not a startup window.
c = g.app.newCommander(fileName)
frame = c.frame
frame.createFirstTreeNode() # 2013/09/27: bug fix.
assert c.rootPosition()
frame.setInitialWindowGeometry()
frame.resizePanesToRatio(frame.ratio, frame.secondary_ratio)
# Call the 'new' hook for compatibility with plugins.
# 2011/11/07: Do this only if plugins have been loaded.
g.doHook("new", old_c=None, c=c, new_c=c)
return c
#@-others
#@-others
#@-leo