Coverage for C:\Repos\leo-editor\leo\core\leoBridge.py: 73%

180 statements  

« prev     ^ index     » next       coverage.py v6.4, created at 2022-05-24 10:21 -0500

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 # Tell leoApp.createDefaultGui not to create a gui. 

121 # This module will create the gui later. 

122 g.in_bridge = self.vs_code_flag # #2098. 

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 # #2519: Call sys.exit if leoID does not exist. 

151 g.app.setLeoID(useDialog=False, verbose=True) 

152 # Can be done early. Uses only g.app.loadDir & g.app.homeDir. 

153 lm.createAllImporterData() # #1965. 

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 # reads only standard settings files, using a null gui. 

165 # uses lm.files[0] to compute the local directory 

166 # that might contain myLeoSettings.leo. 

167 lm.readGlobalSettingsFiles() 

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: 

179 

180 def dummyDoHook(tag, *args, **keys): 

181 pass 

182 

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 not self.isOpen(): 

271 return None 

272 if self.useCaches: 

273 self.reopen_cachers() 

274 else: 

275 g.app.db = g.NullObject() 

276 fileName = self.completeFileName(fileName) 

277 c = g.openWithFileName(fileName) # #2489. 

278 # Leo 6.3: support leoInteg. 

279 g.app.windowList.append(c.frame) 

280 if not self.useCaches: 

281 c.db = g.NullObject() 

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 g.app.nodeIndices.compute_last_index(c) 

288 if useLog: 

289 g.app.gui.log = log = c.frame.log 

290 log.isNull = False 

291 log.enabled = True 

292 return c 

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:vitalije.20190923081235.1: *4* reopen_cachers 

304 def reopen_cachers(self): 

305 from leo.core import leoCache 

306 

307 g = self.g 

308 try: 

309 g.app.db.get('dummy') 

310 except Exception: 

311 g.app.global_cacher = leoCache.GlobalCacher() 

312 g.app.db = g.app.global_cacher.db 

313 g.app.commander_cacher = leoCache.CommanderCacher() 

314 g.app.commander_db = g.app.commander_cacher.db 

315 #@-others 

316#@-others 

317#@-leo