Coverage for C:\leo.repo\leo-editor\leo\core\leoBridge.py : 74%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#! /usr/bin/env python
2#@+leo-ver=5-thin
3#@+node:ekr.20070227091955.1: * @file leoBridge.py
4#@@first
5"""A module to allow full access to Leo commanders from outside Leo."""
6#@@language python
7#@@tabwidth -4
8#@+<< about the leoBridge module >>
9#@+node:ekr.20070227091955.2: ** << about the leoBridge module >>
10#@@language rest
11#@+at
12# A **host** program is a Python program separate from Leo. Host programs may
13# be created by Leo, but at the time they are run host programs must not be
14# part of Leo in any way. So if they are run from Leo, they must be run in a
15# separate process.
16#
17# The leoBridge module gives host programs access to all aspects of Leo,
18# including all of Leo's source code, the contents of any .leo file, all
19# configuration settings in .leo files, etc.
20#
21# Host programs will use the leoBridge module like this::
22#
23# from leo.core import leoBridge
24# bridge = leoBridge.controller(gui='nullGui',verbose=False)
25# if bridge.isOpen():
26# g = bridge.globals()
27# c = bridge.openLeoFile(path)
28#
29# Notes:
30#
31# - The leoBridge module imports no modules at the top level.
32#
33# - leoBridge.controller creates a singleton *bridge controller* that grants
34# access to Leo's objects, including fully initialized g and c objects. In
35# particular, the g.app and g.app.gui vars are fully initialized.
36#
37# - By default, leoBridge.controller creates a null gui so that no Leo
38# windows appear on the screen.
39#
40# - As shown above, the host program should gain access to Leo's leoGlobals
41# module using bridge.globals(). The host program should not import
42# leo.core.leoGlobals as leoGlobals directly.
43#
44# - bridge.openLeoFile(path) returns a completely standard Leo commander.
45# Host programs can use these commanders as described in Leo's scripting
46# chapter.
47#@-<< about the leoBridge module >>
48import os
49import traceback
50# This module must import *no* Leo modules at the outer level!
51gBridgeController = None # The singleton bridge controller.
52#@+others
53#@+node:ekr.20070227092442: ** controller
54def controller(
55 gui='nullGui',
56 loadPlugins=True,
57 readSettings=True,
58 silent=False,
59 tracePlugins=False,
60 useCaches=True,
61 verbose=False
62):
63 """Create an singleton instance of a bridge controller."""
64 global gBridgeController
65 if not gBridgeController:
66 gBridgeController = BridgeController(
67 gui,
68 loadPlugins,
69 readSettings,
70 silent,
71 tracePlugins,
72 useCaches,
73 verbose)
74 return gBridgeController
75#@+node:ekr.20070227092442.2: ** class BridgeController
76class BridgeController:
77 """Creates a way for host programs to access Leo."""
78 #@+others
79 #@+node:ekr.20070227092442.3: *3* bridge.ctor
80 def __init__(self,
81 guiName, loadPlugins, readSettings, silent, tracePlugins, useCaches, verbose,
82 vs_code_flag=False, # #2098.
83 ):
84 """Ctor for the BridgeController class."""
85 self.g = None
86 self.gui = None
87 self.guiName = guiName or 'nullGui'
88 self.loadPlugins = loadPlugins
89 self.readSettings = readSettings
90 self.silentMode = silent
91 self.tracePlugins = tracePlugins
92 self.useCaches = useCaches
93 self.verbose = verbose
94 self.vs_code_flag = vs_code_flag # #2098
95 self.mainLoop = False # True only if a non-null-gui mainloop is active.
96 self.initLeo()
97 #@+node:ekr.20070227092442.4: *3* bridge.globals
98 def globals(self):
99 """Return a fully initialized leoGlobals module."""
100 return self.isOpen() and self.g
101 #@+node:ekr.20070227093530: *3* bridge.initLeo & helpers
102 def initLeo(self):
103 """
104 Init the Leo app to which this class gives access.
105 This code is based on leo.run().
106 """
107 if not self.isValidPython():
108 return
109 #@+<< initLeo imports >>
110 #@+node:ekr.20070227093629.1: *4* << initLeo imports >> initLeo (leoBridge)
111 try:
112 # #1472: Simplify import of g
113 from leo.core import leoGlobals as g
114 self.g = g
115 except ImportError:
116 print("Error importing leoGlobals.py")
117 #
118 # Create the application object.
119 try:
120 g.in_bridge = self.vs_code_flag # #2098.
121 # Tell leoApp.createDefaultGui not to create a gui.
122 # This module will create the gui later.
123 g.in_vs_code = True # 2098.
124 from leo.core import leoApp
125 g.app = leoApp.LeoApp()
126 except ImportError:
127 print("Error importing leoApp.py")
128 g.app.leoID = None
129 if self.tracePlugins:
130 g.app.debug.append('plugins')
131 g.app.silentMode = self.silentMode
132 #
133 # Create the g.app.pluginsController here.
134 from leo.core import leoPlugins
135 leoPlugins.init() # Necessary. Sets g.app.pluginsController.
136 try:
137 from leo.core import leoNodes
138 except ImportError:
139 print("Error importing leoNodes.py")
140 traceback.print_exc()
141 try:
142 from leo.core import leoConfig
143 except ImportError:
144 print("Error importing leoConfig.py")
145 traceback.print_exc()
146 #@-<< initLeo imports >>
147 g.app.recentFilesManager = leoApp.RecentFilesManager()
148 g.app.loadManager = lm = leoApp.LoadManager()
149 lm.computeStandardDirectories()
150 if not g.app.setLeoID(useDialog=False, verbose=True):
151 raise ValueError("unable to set LeoID.")
152 lm.createAllImporterData() # #1965.
153 # Can be done early. Uses only g.app.loadDir & g.app.homeDir.
154 g.app.inBridge = True # Support for g.getScript.
155 g.app.nodeIndices = leoNodes.NodeIndices(g.app.leoID)
156 g.app.config = leoConfig.GlobalConfigManager()
157 if self.useCaches:
158 g.app.setGlobalDb() # #556.
159 else:
160 g.app.db = g.NullObject()
161 g.app.commander_cacher = g.NullObject()
162 g.app.global_cacher = g.NullObject()
163 if self.readSettings:
164 lm.readGlobalSettingsFiles()
165 # reads only standard settings files, using a null gui.
166 # uses lm.files[0] to compute the local directory
167 # that might contain myLeoSettings.leo.
168 else:
169 # Bug fix: 2012/11/26: create default global settings dicts.
170 settings_d, bindings_d = lm.createDefaultSettingsDicts()
171 lm.globalSettingsDict = settings_d
172 lm.globalBindingsDict = bindings_d
173 self.createGui() # Create the gui *before* loading plugins.
174 if self.verbose:
175 self.reportDirectories()
176 self.adjustSysPath()
177 # Kill all event handling if plugins not loaded.
178 if not self.loadPlugins:
180 def dummyDoHook(tag, *args, **keys):
181 pass
183 g.doHook = dummyDoHook
184 g.doHook("start1") # Load plugins.
185 g.app.computeSignon()
186 g.app.initing = False
187 g.doHook("start2", c=None, p=None, v=None, fileName=None)
188 #@+node:ekr.20070302061713: *4* bridge.adjustSysPath
189 def adjustSysPath(self):
190 """Adjust sys.path to enable imports as usual with Leo."""
191 import sys
192 g = self.g
193 leoDirs = (
194 'config', 'doc', 'extensions', 'modes', 'plugins', 'core', 'test') # 2008/7/30
195 for theDir in leoDirs:
196 path = g.os_path_finalize_join(g.app.loadDir, '..', theDir)
197 if path not in sys.path:
198 sys.path.append(path)
199 # #258: leoBridge does not work with @auto-md subtrees.
200 for theDir in ('importers', 'writers'):
201 path = g.os_path_finalize_join(g.app.loadDir, '..', 'plugins', theDir)
202 if path not in sys.path:
203 sys.path.append(path)
204 #@+node:ekr.20070227095743: *4* bridge.createGui
205 def createGui(self):
206 g = self.g
207 if self.guiName == 'nullGui':
208 g.app.gui = g.app.nullGui
209 g.app.log = g.app.gui.log = log = g.app.nullLog
210 log.isNull = False
211 log.enabled = True # Allow prints from NullLog.
212 log.logInited = True # Bug fix: 2012/10/17.
213 else:
214 assert False, f"leoBridge.py: unsupported gui: {self.guiName}"
215 #@+node:ekr.20070227093629.4: *4* bridge.isValidPython
216 def isValidPython(self):
217 import sys
218 if sys.platform == 'cli':
219 return True
220 message = """\
221 Leo requires Python 3.6 or higher.
222 You may download Python from http://python.org/download/
223 """
224 try:
225 # This will fail if True/False are not defined.
226 from leo.core import leoGlobals as g
227 # print('leoBridge:isValidPython:g',g)
228 # Set leoGlobals.g here, rather than in leoGlobals.py.
229 leoGlobals = g # Don't set g.g, it would pollute the autocompleter.
230 leoGlobals.g = g
231 except ImportError:
232 print("isValidPython: can not import leoGlobals")
233 return 0
234 except Exception:
235 print("isValidPytyhon: unexpected exception importing leoGlobals")
236 traceback.print_exc()
237 return 0
238 try:
239 version = '.'.join([str(sys.version_info[i]) for i in (0, 1, 2)])
240 ok = g.CheckVersion(version, '2.2.1')
241 if not ok:
242 print(message)
243 g.app.gui.runAskOkDialog(
244 None, "Python version error", message=message, text="Exit")
245 return ok
246 except Exception:
247 print("isValidPython: unexpected exception: g.CheckVersion")
248 traceback.print_exc()
249 return 0
250 #@+node:ekr.20070227093629.9: *4* bridge.reportDirectories
251 def reportDirectories(self):
252 if not self.silentMode:
253 g = self.g
254 for kind, theDir in (
255 ("global config", g.app.globalConfigDir),
256 ("home", g.app.homeDir),
257 ):
258 g.blue('', kind, 'directory', '', ':', theDir)
259 #@+node:ekr.20070227093918: *3* bridge.isOpen
260 def isOpen(self):
261 """Return True if the bridge is open."""
262 g = self.g
263 return bool(g and g.app and g.app.gui)
264 #@+node:ekr.20070227092442.5: *3* bridge.openLeoFile & helpers
265 def openLeoFile(self, fileName):
266 """Open a .leo file, or create a new Leo frame if no fileName is given."""
267 g = self.g
268 g.app.silentMode = self.silentMode
269 useLog = False
270 if self.isOpen():
271 if self.useCaches:
272 self.reopen_cachers()
273 else:
274 g.app.db = g.NullObject()
275 fileName = self.completeFileName(fileName)
276 c = self.createFrame(fileName)
277 # Leo 6.3: support leoInteg.
278 g.app.windowList.append(c.frame)
279 if not self.useCaches:
280 c.db = g.NullObject()
281 g.app.nodeIndices.compute_last_index(c)
282 # New in Leo 5.1. An alternate fix for bug #130.
283 # When using a bridge Leo might open a file, modify it,
284 # close it, reopen it and change it all within one second.
285 # In that case, this code must properly compute the next
286 # available gnx by scanning the entire outline.
287 if useLog:
288 g.app.gui.log = log = c.frame.log
289 log.isNull = False
290 log.enabled = True
291 return c
292 return None
293 #@+node:ekr.20070227093629.5: *4* bridge.completeFileName
294 def completeFileName(self, fileName):
295 g = self.g
296 if not (fileName and fileName.strip()):
297 return ''
298 fileName = g.os_path_finalize_join(os.getcwd(), fileName)
299 head, ext = g.os_path_splitext(fileName)
300 if not ext:
301 fileName = fileName + ".leo"
302 return fileName
303 #@+node:ekr.20070227093629.6: *4* bridge.createFrame
304 def createFrame(self, fileName):
305 """Create a commander and frame for the given file.
306 Create a new frame if the fileName is empty or non-exisent."""
307 g = self.g
308 if fileName.strip():
309 if g.os_path_exists(fileName):
310 # This takes a long time due to imports in c.__init__
311 c = g.openWithFileName(fileName)
312 if c:
313 return c
314 elif not self.silentMode:
315 print(f"file not found: {fileName}. creating new outline")
316 # Create a new frame. Unlike leo.run, this is not a startup window.
317 c = g.app.newCommander(fileName)
318 frame = c.frame
319 frame.createFirstTreeNode() # 2013/09/27: bug fix.
320 assert c.rootPosition()
321 frame.setInitialWindowGeometry()
322 frame.resizePanesToRatio(frame.ratio, frame.secondary_ratio)
323 # Call the 'new' hook for compatibility with plugins.
324 # 2011/11/07: Do this only if plugins have been loaded.
325 g.doHook("new", old_c=None, c=c, new_c=c)
326 return c
327 #@+node:vitalije.20190923081235.1: *4* reopen_cachers
328 def reopen_cachers(self):
329 from leo.core import leoCache
331 g = self.g
332 try:
333 g.app.db.get('dummy')
334 except Exception:
335 g.app.global_cacher = leoCache.GlobalCacher()
336 g.app.db = g.app.global_cacher.db
337 g.app.commander_cacher = leoCache.CommanderCacher()
338 g.app.commander_db = g.app.commander_cacher.db
339 #@-others
340#@-others
341#@-leo