Coverage for C:\Repos\leo-editor\leo\core\leoApp.py: 39%

1989 statements  

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

1# -*- coding: utf-8 -*- 

2#@+leo-ver=5-thin 

3#@+node:ekr.20031218072017.2608: * @file leoApp.py 

4#@@first 

5#@+<< imports >> 

6#@+node:ekr.20120219194520.10463: ** << imports >> (leoApp) 

7import argparse 

8import importlib 

9import io 

10import os 

11import sqlite3 

12import subprocess 

13import string 

14import sys 

15import textwrap 

16import time 

17import traceback 

18from typing import Any, Dict 

19import zipfile 

20import platform 

21from leo.core import leoGlobals as g 

22from leo.core import leoExternalFiles 

23from leo.core.leoQt import QCloseEvent 

24StringIO = io.StringIO 

25#@-<< imports >> 

26#@+others 

27#@+node:ekr.20150509193629.1: ** cmd (decorator) 

28def cmd(name): 

29 """Command decorator for the LeoApp class.""" 

30 return g.new_cmd_decorator(name, ['g', 'app']) 

31#@+node:ekr.20161026122804.1: ** class IdleTimeManager 

32class IdleTimeManager: 

33 """ 

34 A singleton class to manage idle-time handling. This class handles all 

35 details of running code at idle time, including running 'idle' hooks. 

36 

37 Any code can call g.app.idleTimeManager.add_callback(callback) to cause 

38 the callback to be called at idle time forever. 

39 """ 

40 

41 def __init__(self): 

42 """Ctor for IdleTimeManager class.""" 

43 self.callback_list = [] 

44 self.timer = None 

45 #@+others 

46 #@+node:ekr.20161026125611.1: *3* itm.add_callback 

47 def add_callback(self, callback): 

48 """Add a callback to be called at every idle time.""" 

49 self.callback_list.append(callback) 

50 #@+node:ekr.20161026124810.1: *3* itm.on_idle 

51 on_idle_count = 0 

52 

53 def on_idle(self, timer): 

54 """IdleTimeManager: Run all idle-time callbacks.""" 

55 if not g.app: 

56 return 

57 if g.app.killed: 

58 return 

59 if not g.app.pluginsController: 

60 g.trace('No g.app.pluginsController', g.callers()) 

61 timer.stop() 

62 return # For debugger. 

63 self.on_idle_count += 1 

64 # Handle the registered callbacks. 

65 for callback in self.callback_list: 

66 try: 

67 callback() 

68 except Exception: 

69 g.es_exception() 

70 g.es_print(f"removing callback: {callback}") 

71 self.callback_list.remove(callback) 

72 # Handle idle-time hooks. 

73 g.app.pluginsController.on_idle() 

74 #@+node:ekr.20161028034808.1: *3* itm.start 

75 def start(self): 

76 """Start the idle-time timer.""" 

77 self.timer = g.IdleTime( 

78 self.on_idle, 

79 delay=500, 

80 tag='IdleTimeManager.on_idle') 

81 if self.timer: 

82 self.timer.start() 

83 #@-others 

84#@+node:ekr.20120209051836.10241: ** class LeoApp 

85class LeoApp: 

86 """A class representing the Leo application itself. 

87 

88 Ivars of this class are Leo's global variables.""" 

89 #@+others 

90 #@+node:ekr.20150509193643.1: *3* app.Birth & startup 

91 #@+node:ekr.20031218072017.1416: *4* app.__init__ (helpers contain language dicts) 

92 def __init__(self): 

93 """ 

94 Ctor for LeoApp class. These ivars are Leo's global vars. 

95 

96 leoGlobals.py contains global switches to be set by hand. 

97 """ 

98 #@+<< LeoApp: command-line arguments >> 

99 #@+node:ekr.20161028035755.1: *5* << LeoApp: command-line arguments >> 

100 self.batchMode = False # True: run in batch mode. 

101 self.debug = [] # A list of switches to be enabled. 

102 self.diff = False # True: run Leo in diff mode. 

103 self.enablePlugins = True # True: run start1 hook to load plugins. --no-plugins 

104 self.failFast = False # True: Use the failfast option in unit tests. 

105 self.gui = None # The gui class. 

106 self.guiArgName = None # The gui name given in --gui option. 

107 self.ipython_inited = False # True if leoIpython.py imports succeeded. 

108 self.isTheme = False # True: load files as theme files (ignore myLeoSettings.leo). 

109 self.listen_to_log_flag = False # True: execute listen-to-log command. 

110 self.loaded_session = False # Set by startup logic to True if no files specified on the command line. 

111 self.silentMode = False # True: no signon. 

112 self.start_fullscreen = False # For qt_frame plugin. 

113 self.start_maximized = False # For qt_frame plugin. 

114 self.start_minimized = False # For qt_frame plugin. 

115 self.trace_binding = None # The name of a binding to trace, or None. 

116 self.trace_setting = None # The name of a setting to trace, or None. 

117 self.translateToUpperCase = False # Never set to True. 

118 self.useIpython = False # True: add support for IPython. 

119 self.use_splash_screen = True # True: put up a splash screen. 

120 #@-<< LeoApp: command-line arguments >> 

121 #@+<< LeoApp: Debugging & statistics >> 

122 #@+node:ekr.20161028035835.1: *5* << LeoApp: Debugging & statistics >> 

123 self.debug_dict = {} # For general use. 

124 self.disable_redraw = False # True: disable all redraws. 

125 self.disableSave = False # May be set by plugins. 

126 self.idle_timers = [] # A list of IdleTime instances, so they persist. 

127 self.log_listener = None # The process created by the 'listen-for-log' command. 

128 self.positions = 0 # The number of positions generated. 

129 self.scanErrors = 0 # The number of errors seen by g.scanError. 

130 self.structure_errors = 0 # Set by p.safeMoveToThreadNext. 

131 self.statsDict = {} # dict used by g.stat, g.clear_stats, g.print_stats. 

132 self.statsLockout = False # A lockout to prevent unbound recursion while gathering stats. 

133 self.validate_outline = False # True: enables c.validate_outline. (slow) 

134 #@-<< LeoApp: Debugging & statistics >> 

135 #@+<< LeoApp: error messages >> 

136 #@+node:ekr.20161028035902.1: *5* << LeoApp: error messages >> 

137 self.menuWarningsGiven = False # True: supress warnings in menu code. 

138 self.unicodeErrorGiven = True # True: suppres unicode tracebacks. 

139 #@-<< LeoApp: error messages >> 

140 #@+<< LeoApp: global directories >> 

141 #@+node:ekr.20161028035924.1: *5* << LeoApp: global directories >> 

142 self.extensionsDir = None # The leo/extensions directory 

143 self.globalConfigDir = None # leo/config directory 

144 self.globalOpenDir = None # The directory last used to open a file. 

145 self.homeDir = None # The user's home directory. 

146 self.homeLeoDir = None # The user's home/.leo directory. 

147 self.leoEditorDir = None # The leo-editor directory. 

148 self.loadDir = None # The leo/core directory. 

149 self.machineDir = None # The machine-specific directory. 

150 self.theme_directory = None # The directory from which the theme file was loaded, if any. 

151 #@-<< LeoApp: global directories >> 

152 #@+<< LeoApp: global data >> 

153 #@+node:ekr.20161028035956.1: *5* << LeoApp: global data >> 

154 self.atAutoNames = set() # The set of all @auto spellings. 

155 self.atFileNames = set() # The set of all built-in @<file> spellings. 

156 self.globalKillBuffer = [] # The global kill buffer. 

157 self.globalRegisters = {} # The global register list. 

158 self.leoID = None # The id part of gnx's. 

159 self.loadedThemes = [] # List of loaded theme.leo files. 

160 self.lossage = [] # List of last 100 keystrokes. 

161 self.paste_c = None # The commander that pasted the last outline. 

162 self.spellDict = None # The singleton PyEnchant spell dict. 

163 self.numberOfUntitledWindows = 0 # Number of opened untitled windows. 

164 self.windowList = [] # Global list of all frames. 

165 self.realMenuNameDict = {} # Translations of menu names. 

166 #@-<< LeoApp: global data >> 

167 #@+<< LeoApp: global controller/manager objects >> 

168 #@+node:ekr.20161028040028.1: *5* << LeoApp: global controller/manager objects >> 

169 # Singleton applications objects... 

170 self.backgroundProcessManager = None # A BackgroundProcessManager. 

171 self.commander_cacher = None # A leoCacher.CommanderCacher. 

172 self.commander_db = None # Managed by g.app.commander_cacher. 

173 self.config = None # g.app.config. 

174 self.db = None # A global db, managed by g.app.global_cacher. 

175 self.externalFilesController = None # An ExternalFilesController. 

176 self.global_cacher = None # A leoCacher.GlobalCacher. 

177 self.idleTimeManager = None # An IdleTimeManager. 

178 self.ipk = None # A python kernel. 

179 self.loadManager = None # A LoadManager. 

180 self.nodeIndices = None # A NodeIndices. 

181 self.pluginsController = None # A PluginsManager. 

182 self.sessionManager = None # A SessionManager. 

183 

184 # Global status vars for the Commands class... 

185 self.commandName = None # The name of the command being executed. 

186 self.commandInterruptFlag = False # True: command within a command. 

187 #@-<< LeoApp: global controller/manager objects >> 

188 #@+<< LeoApp: global reader/writer data >> 

189 #@+node:ekr.20170302075110.1: *5* << LeoApp: global reader/writer data >> 

190 # From leoAtFile.py. 

191 self.atAutoWritersDict = {} 

192 self.writersDispatchDict = {} 

193 # From leoImport.py 

194 # Keys are @auto names, values are scanner classes. 

195 self.atAutoDict = {} 

196 self.classDispatchDict = {} 

197 #@-<< LeoApp: global reader/writer data >> 

198 #@+<< LeoApp: global status vars >> 

199 #@+node:ekr.20161028040054.1: *5* << LeoApp: global status vars >> 

200 self.already_open_files = [] # A list of file names that *might* be open in another copy of Leo. 

201 self.dragging = False # True: dragging. 

202 self.inBridge = False # True: running from leoBridge module. 

203 self.inScript = False # True: executing a script. 

204 self.initing = True # True: we are initiing the app. 

205 self.initComplete = False # True: late bindings are not allowed. 

206 self.initStyleFlag = False # True: setQtStyle called. 

207 self.killed = False # True: we are about to destroy the root window. 

208 self.openingSettingsFile = False # True, opening a settings file. 

209 self.preReadFlag = False # True: we are pre-reading a settings file. 

210 self.quitting = False # True: quitting. Locks out some events. 

211 self.quit_after_load = False # True: quit immediately after loading. For unit a unit test. 

212 self.restarting = False # True: restarting all of Leo. #1240. 

213 self.reverting = False # True: executing the revert command. 

214 self.syntax_error_files = [] 

215 #@-<< LeoApp: global status vars >> 

216 #@+<< LeoApp: the global log >> 

217 #@+node:ekr.20161028040141.1: *5* << LeoApp: the global log >> 

218 self.log = None # The LeoFrame containing the present log. 

219 self.logInited = False # False: all log message go to logWaiting list. 

220 self.logIsLocked = False # True: no changes to log are allowed. 

221 self.logWaiting = [] # List of tuples (s, color, newline) waiting to go to a log. 

222 self.printWaiting = [] # Queue of messages to be sent to the printer. 

223 self.signon = '' 

224 self.signon1 = '' 

225 self.signon2 = '' 

226 #@-<< LeoApp: the global log >> 

227 #@+<< LeoApp: global types >> 

228 #@+node:ekr.20161028040204.1: *5* << LeoApp: global types >> 

229 from leo.core import leoFrame 

230 from leo.core import leoGui 

231 self.nullGui = leoGui.NullGui() 

232 self.nullLog = leoFrame.NullLog() 

233 #@-<< LeoApp: global types >> 

234 #@+<< LeoApp: plugins and event handlers >> 

235 #@+node:ekr.20161028040229.1: *5* << LeoApp: plugins and event handlers >> 

236 self.hookError = False # True: suppress further calls to hooks. 

237 self.hookFunction = None # Application wide hook function. 

238 self.idle_time_hooks_enabled = True # True: idle-time hooks are enabled. 

239 #@-<< LeoApp: plugins and event handlers >> 

240 #@+<< LeoApp: scripting ivars >> 

241 #@+node:ekr.20161028040303.1: *5* << LeoApp: scripting ivars >> 

242 self.scriptDict = {} # For use by scripts. Cleared before running each script. 

243 self.scriptResult = None # For use by leoPymacs. 

244 self.permanentScriptDict = {} # For use by scripts. Never cleared automatically. 

245 #@-<< LeoApp: scripting ivars >> 

246 #@+<< LeoApp: unit testing ivars >> 

247 #@+node:ekr.20161028040330.1: *5* << LeoApp: unit testing ivars >> 

248 self.suppressImportChecks = False # True: suppress importCommands.check 

249 #@-<< LeoApp: unit testing ivars >> 

250 # Define all global data. 

251 self.init_at_auto_names() 

252 self.init_at_file_names() 

253 self.define_global_constants() 

254 self.define_language_delims_dict() 

255 self.define_language_extension_dict() 

256 self.define_extension_dict() 

257 self.define_delegate_language_dict() 

258 #@+node:ekr.20141102043816.5: *5* app.define_delegate_language_dict 

259 def define_delegate_language_dict(self): 

260 self.delegate_language_dict = { 

261 # Keys are new language names. 

262 # Values are existing languages in leo/modes. 

263 "less": "css", 

264 "hbs": "html", 

265 "handlebars": "html", 

266 #"rust": "c", 

267 # "vue": "c", 

268 } 

269 #@+node:ekr.20120522160137.9911: *5* app.define_extension_dict 

270 #@@nobeautify 

271 

272 def define_extension_dict(self): 

273 

274 # Keys are extensions, values are languages 

275 self.extension_dict = { 

276 # "ada": "ada", 

277 "ada": "ada95", # modes/ada95.py exists. 

278 "ahk": "autohotkey", 

279 "aj": "aspect_j", 

280 "apdl": "apdl", 

281 "as": "actionscript", # jason 2003-07-03 

282 "asp": "asp", 

283 "awk": "awk", 

284 "b": "b", 

285 "bas": "rapidq", # fil 2004-march-11 

286 "bash": "shellscript", 

287 "bat": "batch", 

288 "bbj": "bbj", 

289 "bcel": "bcel", 

290 "bib": "bibtex", 

291 "c": "c", 

292 "c++": "cplusplus", 

293 "cbl": "cobol", # Only one extension is valid: .cob 

294 "cfg": "config", 

295 "cfm": "coldfusion", 

296 "clj": "clojure", # 2013/09/25: Fix bug 879338. 

297 "cljs": "clojure", 

298 "cljc": "clojure", 

299 "ch": "chill", # Other extensions, .c186,.c286 

300 "coffee": "coffeescript", 

301 "conf": "apacheconf", 

302 "cpp": "cplusplus", # 2020/08/12: was cpp. 

303 "css": "css", 

304 "d": "d", 

305 "dart": "dart", 

306 "e": "eiffel", 

307 "el": "elisp", 

308 "eml": "mail", 

309 "erl": "erlang", 

310 "ex": "elixir", 

311 "f": "fortran", 

312 "f90": "fortran90", 

313 "factor": "factor", 

314 "forth": "forth", 

315 "g": "antlr", 

316 "go": "go", 

317 "groovy": "groovy", 

318 "h": "c", # 2012/05/23. 

319 "handlebars": "html", # McNab. 

320 "hbs": "html", # McNab. 

321 "hs": "haskell", 

322 "html": "html", 

323 "hx": "haxe", 

324 "i": "swig", 

325 "i4gl": "i4gl", 

326 "icn": "icon", 

327 "idl": "idl", 

328 "inf": "inform", 

329 "info": "texinfo", 

330 "ini": "ini", 

331 "io": "io", 

332 "ipynb": "jupyter", 

333 "iss": "inno_setup", 

334 "java": "java", 

335 "jhtml": "jhtml", 

336 "jmk": "jmk", 

337 "js": "javascript", # For javascript import test. 

338 "jsp": "javaserverpage", 

339 "json": "json", 

340 # "jsp": "jsp", 

341 "ksh": "kshell", 

342 "kv": "kivy", # PeckJ 2014/05/05 

343 "latex": "latex", 

344 "less": "css", # McNab 

345 "lua": "lua", # ddm 13/02/06 

346 "ly": "lilypond", 

347 "m": "matlab", 

348 "mak": "makefile", 

349 "md": "md", # PeckJ 2013/02/07 

350 "ml": "ml", 

351 "mm": "objective_c", # Only one extension is valid: .m 

352 "mod": "modula3", 

353 "mpl": "maple", 

354 "mqsc": "mqsc", 

355 "nqc": "nqc", 

356 "nsi": "nsi", # EKR: 2010/10/27 

357 # "nsi": "nsis2", 

358 "nw": "noweb", 

359 "occ": "occam", 

360 "otl": "vimoutline", # TL 8/25/08 Vim's outline plugin 

361 "p": "pascal", 

362 # "p": "pop11", # Conflicts with pascal. 

363 "php": "php", 

364 "pike": "pike", 

365 "pl": "perl", 

366 "pl1": "pl1", 

367 "po": "gettext", 

368 "pod": "perlpod", 

369 "pov": "povray", 

370 "prg": "foxpro", 

371 "pro": "prolog", 

372 "ps": "postscript", 

373 "psp": "psp", 

374 "ptl": "ptl", 

375 "py": "python", 

376 "pyx": "cython", # Other extensions, .pyd,.pyi 

377 # "pyx": "pyrex", 

378 # "r": "r", # modes/r.py does not exist. 

379 "r": "rebol", # jason 2003-07-03 

380 "rb": "ruby", # thyrsus 2008-11-05 

381 "rest": "rst", 

382 "rex": "objectrexx", 

383 "rhtml": "rhtml", 

384 "rib": "rib", 

385 "rs": "rust", # EKR: 2019/08/11 

386 "sas": "sas", 

387 "scala": "scala", 

388 "scm": "scheme", 

389 "scpt": "applescript", 

390 "sgml": "sgml", 

391 "sh": "shell", # DS 4/1/04. modes/shell.py exists. 

392 "shtml": "shtml", 

393 "sm": "smalltalk", 

394 "splus": "splus", 

395 "sql": "plsql", # qt02537 2005-05-27 

396 "sqr": "sqr", 

397 "ss": "ssharp", 

398 "ssi": "shtml", 

399 "sty": "latex", 

400 "tcl": "tcl", # modes/tcl.py exists. 

401 # "tcl": "tcltk", 

402 "tex": "latex", 

403 # "tex": "tex", 

404 "tpl": "tpl", 

405 "ts": "typescript", 

406 "txt": "plain", 

407 # "txt": "text", 

408 # "txt": "unknown", # Set when @comment is seen. 

409 "uc": "uscript", 

410 "v": "verilog", 

411 "vbs": "vbscript", 

412 "vhd": "vhdl", 

413 "vhdl": "vhdl", 

414 "vim": "vim", 

415 "vtl": "velocity", 

416 "w": "cweb", 

417 "wiki": "moin", 

418 "xml": "xml", 

419 "xom": "omnimark", 

420 "xsl": "xsl", 

421 "yaml": "yaml", 

422 "vue": "javascript", 

423 "zpt": "zpt", 

424 } 

425 

426 # These aren't real languages, or have no delims... 

427 # cvs_commit, dsssl, embperl, freemarker, hex, jcl, 

428 # patch, phpsection, progress, props, pseudoplain, 

429 # relax_ng_compact, rtf, svn_commit. 

430 

431 # These have extensions which conflict with other languages. 

432 # assembly_6502: .asm or .a or .s 

433 # assembly_macro32: .asm or .a 

434 # assembly_mcs51: .asm or .a 

435 # assembly_parrot: .asm or .a 

436 # assembly_r2000: .asm or .a 

437 # assembly_x86: .asm or .a 

438 # squidconf: .conf 

439 # rpmspec: .rpm 

440 

441 # Extra language extensions, used to associate extensions with mode files. 

442 # Used by importCommands.languageForExtension. 

443 # Keys are extensions, values are corresponding mode file (without .py) 

444 # A value of 'none' is a signal to unit tests that no extension file exists. 

445 self.extra_extension_dict = { 

446 'pod' : 'perl', 

447 'unknown_language': 'none', 

448 'w' : 'c', 

449 } 

450 #@+node:ekr.20031218072017.1417: *5* app.define_global_constants 

451 def define_global_constants(self): 

452 # self.prolog_string = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" 

453 self.prolog_prefix_string = "<?xml version=\"1.0\" encoding=" 

454 self.prolog_postfix_string = "?>" 

455 self.prolog_namespace_string = 'xmlns:leo="http://edreamleo.org/namespaces/leo-python-editor/1.1"' 

456 #@+node:ekr.20120522160137.9909: *5* app.define_language_delims_dict 

457 #@@nobeautify 

458 

459 def define_language_delims_dict(self): 

460 

461 self.language_delims_dict = { 

462 # Internally, lower case is used for all language names. 

463 # Keys are languages, values are 1,2 or 3-tuples of delims. 

464 "actionscript" : "// /* */", # jason 2003-07-03 

465 "ada" : "--", 

466 "ada95" : "--", 

467 "ahk" : ";", 

468 "antlr" : "// /* */", 

469 "apacheconf" : "#", 

470 "apdl" : "!", 

471 "applescript" : "-- (* *)", 

472 "asp" : "<!-- -->", 

473 "aspect_j" : "// /* */", 

474 "assembly_6502" : ";", 

475 "assembly_macro32" : ";", 

476 "assembly_mcs51" : ";", 

477 "assembly_parrot" : "#", 

478 "assembly_r2000" : "#", 

479 "assembly_x86" : ";", 

480 "autohotkey" : "; /* */", # TL - AutoHotkey language 

481 "awk" : "#", 

482 "b" : "// /* */", 

483 "batch" : "REM_", # Use the REM hack. 

484 "bbj" : "/* */", 

485 "bcel" : "// /* */", 

486 "bibtex" : "%", 

487 "c" : "// /* */", # C, C++ or objective C. 

488 "chill" : "/* */", 

489 "clojure" : ";", # 2013/09/25: Fix bug 879338. 

490 "cobol" : "*", 

491 "coldfusion" : "<!-- -->", 

492 "coffeescript" : "#", # 2016/02/26. 

493 "config" : "#", # Leo 4.5.1 

494 "cplusplus" : "// /* */", 

495 "cpp" : "// /* */",# C++. 

496 "csharp" : "// /* */", # C# 

497 "css" : "/* */", # 4/1/04 

498 "cweb" : "@q@ @>", # Use the "cweb hack" 

499 "cython" : "#", 

500 "d" : "// /* */", 

501 "dart" : "// /* */", # Leo 5.0. 

502 "doxygen" : "#", 

503 "eiffel" : "--", 

504 "elisp" : ";", 

505 "erlang" : "%", 

506 "elixir" : "#", 

507 "factor" : "!_ ( )", # Use the rem hack. 

508 "forth" : "\\_ _(_ _)", # Use the "REM hack" 

509 "fortran" : "C", 

510 "fortran90" : "!", 

511 "foxpro" : "&&", 

512 "gettext" : "# ", 

513 "go" : "//", 

514 "groovy" : "// /* */", 

515 "handlebars" : "<!-- -->", # McNab: delegate to html. 

516 "haskell" : "--_ {-_ _-}", 

517 "haxe" : "// /* */", 

518 "hbs" : "<!-- -->", # McNab: delegate to html. 

519 "html" : "<!-- -->", 

520 "i4gl" : "-- { }", 

521 "icon" : "#", 

522 "idl" : "// /* */", 

523 "inform" : "!", 

524 "ini" : ";", 

525 "inno_setup" : ";", 

526 "interlis" : "/* */", 

527 "io" : "// */", 

528 "java" : "// /* */", 

529 "javascript" : "// /* */", # EKR: 2011/11/12: For javascript import test. 

530 "javaserverpage" : "<%-- --%>", # EKR: 2011/11/25 (See also, jsp) 

531 "jhtml" : "<!-- -->", 

532 "jmk" : "#", 

533 "json" : "#", # EKR: 2020/07/27: Json has no delims. This is a dummy entry. 

534 "jsp" : "<%-- --%>", 

535 "jupyter" : "<%-- --%>", # Default to markdown? 

536 "kivy" : "#", # PeckJ 2014/05/05 

537 "kshell" : "#", # Leo 4.5.1. 

538 "latex" : "%", 

539 "less" : "/* */", # NcNab: delegate to css. 

540 "lilypond" : "% %{ %}", 

541 "lisp" : ";", # EKR: 2010/09/29 

542 "lotos" : "(* *)", 

543 "lua" : "--", # ddm 13/02/06 

544 "mail" : ">", 

545 "makefile" : "#", 

546 "maple" : "//", 

547 "markdown" : "<!-- -->", # EKR, 2018/03/03: html comments. 

548 "matlab" : "%", # EKR: 2011/10/21 

549 "md" : "<!-- -->", # PeckJ: 2013/02/08 

550 "ml" : "(* *)", 

551 "modula3" : "(* *)", 

552 "moin" : "##", 

553 "mqsc" : "*", 

554 "netrexx" : "-- /* */", 

555 "noweb" : "%", # EKR: 2009-01-30. Use Latex for doc chunks. 

556 "nqc" : "// /* */", 

557 "nsi" : ";", # EKR: 2010/10/27 

558 "nsis2" : ";", 

559 "objective_c" : "// /* */", 

560 "objectrexx" : "-- /* */", 

561 "occam" : "--", 

562 "omnimark" : ";", 

563 "pandoc" : "<!-- -->", 

564 "pascal" : "// { }", 

565 "perl" : "#", 

566 "perlpod" : "# __=pod__ __=cut__", # 9/25/02: The perlpod hack. 

567 "php" : "// /* */", # 6/23/07: was "//", 

568 "pike" : "// /* */", 

569 "pl1" : "/* */", 

570 "plain" : "#", # We must pick something. 

571 "plsql" : "-- /* */", # SQL scripts qt02537 2005-05-27 

572 "pop11" : ";;; /* */", 

573 "postscript" : "%", 

574 "povray" : "// /* */", 

575 "powerdynamo" : "// <!-- -->", 

576 "prolog" : "% /* */", 

577 "psp" : "<!-- -->", 

578 "ptl" : "#", 

579 "pvwave" : ";", 

580 "pyrex" : "#", 

581 "python" : "#", 

582 "r" : "#", 

583 "rapidq" : "'", # fil 2004-march-11 

584 "rebol" : ";", # jason 2003-07-03 

585 "redcode" : ";", 

586 "rest" : ".._", 

587 "rhtml" : "<%# %>", 

588 "rib" : "#", 

589 "rpmspec" : "#", 

590 "rst" : ".._", 

591 "rust" : "// /* */", 

592 "ruby" : "#", # thyrsus 2008-11-05 

593 "rview" : "// /* */", 

594 "sas" : "* /* */", 

595 "scala" : "// /* */", 

596 "scheme" : "; #| |#", 

597 "sdl_pr" : "/* */", 

598 "sgml" : "<!-- -->", 

599 "shell" : "#", # shell scripts 

600 "shellscript" : "#", 

601 "shtml" : "<!-- -->", 

602 "smalltalk" : '" "', # Comments are enclosed in double quotes(!!) 

603 "smi_mib" : "--", 

604 "splus" : "#", 

605 "sqr" : "!", 

606 "squidconf" : "#", 

607 "ssharp" : "#", 

608 "swig" : "// /* */", 

609 "tcl" : "#", 

610 "tcltk" : "#", 

611 "tex" : "%", # Bug fix: 2008-1-30: Fixed Mark Edginton's bug. 

612 "text" : "#", # We must pick something. 

613 "texinfo" : "@c", 

614 "tpl" : "<!-- -->", 

615 "tsql" : "-- /* */", 

616 "typescript" : "// /* */", # For typescript import test. 

617 "unknown" : "#", # Set when @comment is seen. 

618 "unknown_language" : '#--unknown-language--', # For unknown extensions in @shadow files. 

619 "uscript" : "// /* */", 

620 "vbscript" : "'", 

621 "velocity" : "## #* *#", 

622 "verilog" : "// /* */", 

623 "vhdl" : "--", 

624 "vim" : "\"", 

625 "vimoutline" : "#", # TL 8/25/08 Vim's outline plugin 

626 "xml" : "<!-- -->", 

627 "xsl" : "<!-- -->", 

628 "xslt" : "<!-- -->", 

629 "yaml" : "#", 

630 "zpt" : "<!-- -->", 

631 

632 # These aren't real languages, or have no delims... 

633 # "cvs_commit" : "", 

634 # "dsssl" : "; <!-- -->", 

635 # "embperl" : "<!-- -->", # Internal colorizing state. 

636 # "freemarker" : "", 

637 # "hex" : "", 

638 # "jcl" : "", 

639 # "patch" : "", 

640 # "phpsection" : "<!-- -->", # Internal colorizing state. 

641 # "props" : "#", # Unknown language. 

642 # "pseudoplain" : "", 

643 # "relax_ng_compact" : "#", # An xml schema. 

644 # "rtf" : "", 

645 # "svn_commit" : "", 

646 } 

647 #@+node:ekr.20120522160137.9910: *5* app.define_language_extension_dict 

648 #@@nobeautify 

649 

650 def define_language_extension_dict(self): 

651 

652 # Used only by g.app.externalFilesController.get_ext. 

653 

654 # Keys are languages, values are extensions. 

655 self.language_extension_dict = { 

656 "actionscript" : "as", # jason 2003-07-03 

657 "ada" : "ada", 

658 "ada95" : "ada", 

659 "ahk" : "ahk", 

660 "antlr" : "g", 

661 "apacheconf" : "conf", 

662 "apdl" : "apdl", 

663 "applescript" : "scpt", 

664 "asp" : "asp", 

665 "aspect_j" : "aj", 

666 "autohotkey" : "ahk", # TL - AutoHotkey language 

667 "awk" : "awk", 

668 "b" : "b", 

669 "batch" : "bat", # Leo 4.5.1. 

670 "bbj" : "bbj", 

671 "bcel" : "bcel", 

672 "bibtex" : "bib", 

673 "c" : "c", 

674 "chill" : "ch", # Only one extension is valid: .c186, .c286 

675 "clojure" : "clj", # 2013/09/25: Fix bug 879338. 

676 "cobol" : "cbl", # Only one extension is valid: .cob 

677 "coldfusion" : "cfm", 

678 "coffeescript" : "coffee", 

679 "config" : "cfg", 

680 "cplusplus" : "c++", 

681 "cpp" : "cpp", 

682 "css" : "css", # 4/1/04 

683 "cweb" : "w", 

684 "cython" : "pyx", # Only one extension is valid at present: .pyi, .pyd. 

685 "d" : "d", 

686 "dart" : "dart", 

687 "eiffel" : "e", 

688 "elisp" : "el", 

689 "erlang" : "erl", 

690 "elixir" : "ex", 

691 "factor" : "factor", 

692 "forth" : "forth", 

693 "fortran" : "f", 

694 "fortran90" : "f90", 

695 "foxpro" : "prg", 

696 "gettext" : "po", 

697 "go" : "go", 

698 "groovy" : "groovy", 

699 "haskell" : "hs", 

700 "haxe" : "hx", 

701 "html" : "html", 

702 "i4gl" : "i4gl", 

703 "icon" : "icn", 

704 "idl" : "idl", 

705 "inform" : "inf", 

706 "ini" : "ini", 

707 "inno_setup" : "iss", 

708 "io" : "io", 

709 "java" : "java", 

710 "javascript" : "js", # EKR: 2011/11/12: For javascript import test. 

711 "javaserverpage": "jsp", # EKR: 2011/11/25 

712 "jhtml" : "jhtml", 

713 "jmk" : "jmk", 

714 "json" : "json", 

715 "jsp" : "jsp", 

716 "jupyter" : "ipynb", 

717 "kivy" : "kv", # PeckJ 2014/05/05 

718 "kshell" : "ksh", # Leo 4.5.1. 

719 "latex" : "tex", # 1/8/04 

720 "lilypond" : "ly", 

721 "lua" : "lua", # ddm 13/02/06 

722 "mail" : "eml", 

723 "makefile" : "mak", 

724 "maple" : "mpl", 

725 "matlab" : "m", 

726 "md" : "md", # PeckJ: 2013/02/07 

727 "ml" : "ml", 

728 "modula3" : "mod", 

729 "moin" : "wiki", 

730 "mqsc" : "mqsc", 

731 "noweb" : "nw", 

732 "nqc" : "nqc", 

733 "nsi" : "nsi", # EKR: 2010/10/27 

734 "nsis2" : "nsi", 

735 "objective_c" : "mm", # Only one extension is valid: .m 

736 "objectrexx" : "rex", 

737 "occam" : "occ", 

738 "omnimark" : "xom", 

739 "pascal" : "p", 

740 "perl" : "pl", 

741 "perlpod" : "pod", 

742 "php" : "php", 

743 "pike" : "pike", 

744 "pl1" : "pl1", 

745 "plain" : "txt", 

746 "plsql" : "sql", # qt02537 2005-05-27 

747 # "pop11" : "p", # Conflicts with pascall. 

748 "postscript" : "ps", 

749 "povray" : "pov", 

750 "prolog" : "pro", 

751 "psp" : "psp", 

752 "ptl" : "ptl", 

753 "pyrex" : "pyx", 

754 "python" : "py", 

755 "r" : "r", 

756 "rapidq" : "bas", # fil 2004-march-11 

757 "rebol" : "r", # jason 2003-07-03 

758 "rhtml" : "rhtml", 

759 "rib" : "rib", 

760 "rst" : "rest", 

761 "ruby" : "rb", # thyrsus 2008-11-05 

762 "rust" : "rs", # EKR: 2019/08/11 

763 "sas" : "sas", 

764 "scala" : "scala", 

765 "scheme" : "scm", 

766 "sgml" : "sgml", 

767 "shell" : "sh", # DS 4/1/04 

768 "shellscript" : "bash", 

769 "shtml" : "ssi", # Only one extension is valid: .shtml 

770 "smalltalk" : "sm", 

771 "splus" : "splus", 

772 "sqr" : "sqr", 

773 "ssharp" : "ss", 

774 "swig" : "i", 

775 "tcl" : "tcl", 

776 "tcltk" : "tcl", 

777 "tex" : "tex", 

778 "texinfo" : "info", 

779 "text" : "txt", 

780 "tpl" : "tpl", 

781 "tsql" : "sql", # A guess. 

782 "typescript" : "ts", 

783 "unknown" : "txt", # Set when @comment is seen. 

784 "uscript" : "uc", 

785 "vbscript" : "vbs", 

786 "velocity" : "vtl", 

787 "verilog" : "v", 

788 "vhdl" : "vhd", # Only one extension is valid: .vhdl 

789 "vim" : "vim", 

790 "vimoutline" : "otl", # TL 8/25/08 Vim's outline plugin 

791 "xml" : "xml", 

792 "xsl" : "xsl", 

793 "xslt" : "xsl", 

794 "yaml" : "yaml", 

795 "zpt" : "zpt", 

796 } 

797 

798 # These aren't real languages, or have no delims... 

799 # cvs_commit, dsssl, embperl, freemarker, hex, jcl, 

800 # patch, phpsection, progress, props, pseudoplain, 

801 # relax_ng_compact, rtf, svn_commit. 

802 

803 # These have extensions which conflict with other languages. 

804 # assembly_6502: .asm or .a or .s 

805 # assembly_macro32: .asm or .a 

806 # assembly_mcs51: .asm or .a 

807 # assembly_parrot: .asm or .a 

808 # assembly_r2000: .asm or .a 

809 # assembly_x86: .asm or .a 

810 # squidconf: .conf 

811 # rpmspec: .rpm 

812 #@+node:ekr.20140729162415.18086: *5* app.init_at_auto_names 

813 def init_at_auto_names(self): 

814 """Init the app.atAutoNames set.""" 

815 self.atAutoNames = set([ 

816 "@auto-rst", "@auto", 

817 ]) 

818 #@+node:ekr.20140729162415.18091: *5* app.init_at_file_names 

819 def init_at_file_names(self): 

820 """Init the app.atFileNames set.""" 

821 self.atFileNames = set([ 

822 "@asis", 

823 "@edit", 

824 "@file-asis", "@file-thin", "@file-nosent", "@file", 

825 "@clean", "@nosent", 

826 "@shadow", 

827 "@thin", 

828 ]) 

829 #@+node:ekr.20090717112235.6007: *4* app.computeSignon & printSignon 

830 def computeSignon(self): 

831 from leo.core import leoVersion 

832 app = self 

833 guiVersion = ', ' + app.gui.getFullVersion() if app.gui else '' 

834 leoVer = leoVersion.version 

835 n1, n2, n3, junk1, junk2 = sys.version_info 

836 if sys.platform.startswith('win'): 

837 sysVersion = 'Windows ' 

838 try: 

839 # peckj 20140416: determine true OS architecture 

840 # the following code should return the proper architecture 

841 # regardless of whether or not the python architecture matches 

842 # the OS architecture (i.e. python 32-bit on windows 64-bit will return 64-bit) 

843 v = platform.win32_ver() 

844 release, winbuild, sp, ptype = v 

845 true_platform = os.environ['PROCESSOR_ARCHITECTURE'] 

846 try: 

847 true_platform = os.environ['PROCESSOR_ARCHITEw6432'] 

848 except KeyError: 

849 pass 

850 sysVersion = f"Windows {release} {true_platform} (build {winbuild}) {sp}" 

851 except Exception: 

852 pass 

853 else: sysVersion = sys.platform 

854 branch, junk_commit = g.gitInfo() 

855 author, commit, date = g.getGitVersion() 

856 # Compute g.app.signon. 

857 signon = [f"Leo {leoVer}"] 

858 if branch: 

859 signon.append(f", {branch} branch") 

860 if commit: 

861 signon.append(', build ' + commit) 

862 if date: 

863 signon.append('\n' + date) 

864 app.signon = ''.join(signon) 

865 # Compute g.app.signon1. 

866 app.signon1 = f"Python {n1}.{n2}.{n3}{guiVersion}\n{sysVersion}" 

867 

868 def printSignon(self): 

869 """Print the signon to the log.""" 

870 app = self 

871 if app.silentMode: 

872 return 

873 if sys.stdout.encoding and sys.stdout.encoding.lower() != 'utf-8': 

874 print('Note: sys.stdout.encoding is not UTF-8') 

875 print(f"Encoding is: {sys.stdout.encoding!r}") 

876 print('See: https://stackoverflow.com/questions/14109024') 

877 print('') 

878 print(app.signon) 

879 print(app.signon1) 

880 #@+node:ekr.20100831090251.5838: *4* app.createXGui 

881 #@+node:ekr.20100831090251.5840: *5* app.createCursesGui 

882 def createCursesGui(self, fileName='', verbose=False): 

883 try: 

884 import curses 

885 assert curses 

886 except Exception: 

887 # g.es_exception() 

888 print('can not import curses.') 

889 if g.isWindows: 

890 print('Windows: pip install windows-curses') 

891 sys.exit() 

892 try: 

893 from leo.plugins import cursesGui2 

894 ok = cursesGui2.init() 

895 if ok: 

896 g.app.gui = cursesGui2.LeoCursesGui() 

897 except Exception: 

898 g.es_exception() 

899 print('can not create curses gui.') 

900 sys.exit() 

901 #@+node:ekr.20181031160401.1: *5* app.createBrowserGui 

902 def createBrowserGui(self, fileName='', verbose=False): 

903 app = self 

904 try: 

905 from flexx import flx 

906 assert flx 

907 except Exception: 

908 g.es_exception() 

909 print('can not import flexx') 

910 sys.exit(1) 

911 try: 

912 from leo.plugins import leoflexx 

913 assert leoflexx 

914 except Exception: 

915 g.es_exception() 

916 print('can not import leo.plugins.leoflexx') 

917 sys.exit(1) 

918 g.app.gui = leoflexx.LeoBrowserGui(gui_name=app.guiArgName) 

919 #@+node:ekr.20090619065122.8593: *5* app.createDefaultGui 

920 def createDefaultGui(self, fileName='', verbose=False): 

921 """A convenience routines for plugins to create the default gui class.""" 

922 app = self 

923 argName = app.guiArgName 

924 if g.in_bridge: 

925 return # The bridge will create the gui later. 

926 if app.gui: 

927 return # This method can be called twice if we had to get .leoID.txt. 

928 if argName == 'qt': 

929 app.createQtGui(fileName, verbose=verbose) 

930 elif argName == 'null': 

931 g.app.gui = g.app.nullGui 

932 elif argName.startswith('browser'): 

933 app.createBrowserGui() 

934 elif argName in ('console', 'curses'): 

935 app.createCursesGui() 

936 elif argName == 'text': 

937 app.createTextGui() 

938 if not app.gui: 

939 print('createDefaultGui: Leo requires Qt to be installed.') 

940 #@+node:ekr.20031218072017.1938: *5* app.createNullGuiWithScript 

941 def createNullGuiWithScript(self, script=None): 

942 app = self 

943 app.batchMode = True 

944 app.gui = g.app.nullGui 

945 app.gui.setScript(script) 

946 #@+node:ekr.20090202191501.1: *5* app.createQtGui 

947 def createQtGui(self, fileName='', verbose=False): 

948 # Do NOT omit fileName param: it is used in plugin code. 

949 """A convenience routines for plugins to create the Qt gui class.""" 

950 app = self 

951 try: 

952 from leo.core.leoQt import Qt 

953 assert Qt 

954 except Exception: 

955 # #1215: Raise an emergency dialog. 

956 message = 'Can not Import Qt' 

957 print(message) 

958 try: 

959 d = g.EmergencyDialog(title=message, message=message) 

960 d.run() 

961 except Exception: 

962 g.es_exception() 

963 sys.exit(1) 

964 try: 

965 from leo.plugins import qt_gui 

966 except Exception: 

967 g.es_exception() 

968 print('can not import leo.plugins.qt_gui') 

969 sys.exit(1) 

970 try: 

971 from leo.plugins.editpane.editpane import edit_pane_test_open, edit_pane_csv 

972 g.command('edit-pane-test-open')(edit_pane_test_open) 

973 g.command('edit-pane-csv')(edit_pane_csv) 

974 except ImportError: 

975 # g.es_exception() 

976 print('Failed to import editpane') 

977 # 

978 # Complete the initialization. 

979 qt_gui.init() 

980 if app.gui and fileName and verbose: 

981 print(f"Qt Gui created in {fileName}") 

982 #@+node:ekr.20170419093747.1: *5* app.createTextGui (was createCursesGui) 

983 def createTextGui(self, fileName='', verbose=False): 

984 app = self 

985 app.pluginsController.loadOnePlugin('leo.plugins.cursesGui', verbose=verbose) 

986 #@+node:ekr.20090126063121.3: *5* app.createWxGui 

987 def createWxGui(self, fileName='', verbose=False): 

988 # Do NOT omit fileName param: it is used in plugin code. 

989 """A convenience routines for plugins to create the wx gui class.""" 

990 app = self 

991 app.pluginsController.loadOnePlugin('leo.plugins.wxGui', verbose=verbose) 

992 if fileName and verbose: 

993 print(f"wxGui created in {fileName}") 

994 #@+node:ville.20090620122043.6275: *4* app.setGlobalDb 

995 def setGlobalDb(self): 

996 """ Create global pickleshare db 

997 

998 Usable by:: 

999 

1000 g.app.db['hello'] = [1,2,5] 

1001 

1002 """ 

1003 # Fixes bug 670108. 

1004 from leo.core import leoCache 

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

1006 g.app.db = g.app.global_cacher.db 

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

1008 g.app.commander_db = g.app.commander_cacher.db 

1009 #@+node:ekr.20031218072017.1978: *4* app.setLeoID & helpers 

1010 def setLeoID(self, useDialog=True, verbose=True): 

1011 """Get g.app.leoID from various sources.""" 

1012 self.leoID = None 

1013 assert self == g.app 

1014 verbose = verbose and not g.unitTesting and not self.silentMode 

1015 table = (self.setIDFromSys, self.setIDFromFile, self.setIDFromEnv,) 

1016 for func in table: 

1017 func(verbose) 

1018 if self.leoID: 

1019 return self.leoID 

1020 if useDialog: 

1021 self.setIdFromDialog() 

1022 if self.leoID: 

1023 self.setIDFile() 

1024 return self.leoID 

1025 #@+node:ekr.20191017061451.1: *5* app.cleanLeoID 

1026 def cleanLeoID(self, id_, tag): 

1027 """#1404: Make sure that the given Leo ID will not corrupt a .leo file.""" 

1028 old_id = id_ if isinstance(id_, str) else repr(id_) 

1029 try: 

1030 id_ = id_.replace('.', '').replace(',', '').replace('"', '').replace("'", '') 

1031 # Remove *all* whitespace: https://stackoverflow.com/questions/3739909 

1032 id_ = ''.join(id_.split()) 

1033 except Exception: 

1034 g.es_exception() 

1035 id_ = '' 

1036 if len(id_) < 3: 

1037 g.EmergencyDialog( 

1038 title=f"Invalid Leo ID: {tag}", 

1039 message=( 

1040 f"Invalid Leo ID: {old_id!r}\n\n" 

1041 "Your id should contain only letters and numbers\n" 

1042 "and must be at least 3 characters in length.")) 

1043 return id_ 

1044 #@+node:ekr.20031218072017.1979: *5* app.setIDFromSys 

1045 def setIDFromSys(self, verbose): 

1046 """ 

1047 Attempt to set g.app.leoID from sys.leoID. 

1048 

1049 This might be set by in Python's sitecustomize.py file. 

1050 """ 

1051 id_ = getattr(sys, "leoID", None) 

1052 if id_: 

1053 # Careful: periods in the id field of a gnx will corrupt the .leo file! 

1054 # cleanLeoID raises a warning dialog. 

1055 id_ = self.cleanLeoID(id_, 'sys.leoID') 

1056 if len(id_) > 2: 

1057 self.leoID = id_ 

1058 if verbose: 

1059 g.red("leoID=", self.leoID, spaces=False) 

1060 #@+node:ekr.20031218072017.1980: *5* app.setIDFromFile 

1061 def setIDFromFile(self, verbose): 

1062 """Attempt to set g.app.leoID from leoID.txt.""" 

1063 tag = ".leoID.txt" 

1064 for theDir in (self.homeLeoDir, self.globalConfigDir, self.loadDir): 

1065 if not theDir: 

1066 continue # Do not use the current directory! 

1067 fn = g.os_path_join(theDir, tag) 

1068 try: 

1069 with open(fn, 'r') as f: 

1070 s = f.readline().strip() 

1071 if not s: 

1072 continue 

1073 # #1404: Ensure valid ID. 

1074 # cleanLeoID raises a warning dialog. 

1075 id_ = self.cleanLeoID(s, tag) 

1076 if len(id_) > 2: 

1077 self.leoID = id_ 

1078 return 

1079 except IOError: 

1080 pass 

1081 except Exception: 

1082 g.error('unexpected exception in app.setLeoID') 

1083 g.es_exception() 

1084 #@+node:ekr.20060211140947.1: *5* app.setIDFromEnv 

1085 def setIDFromEnv(self, verbose): 

1086 """Set leoID from environment vars.""" 

1087 try: 

1088 id_ = os.getenv('USER') 

1089 if id_: 

1090 if verbose: 

1091 g.blue("setting leoID from os.getenv('USER'):", repr(id_)) 

1092 # Careful: periods in the gnx would corrupt the .leo file! 

1093 # cleanLeoID raises a warning dialog. 

1094 id_ = self.cleanLeoID(id_, "os.getenv('USER')") 

1095 if len(id_) > 2: 

1096 self.leoID = id_ 

1097 except Exception: 

1098 pass 

1099 #@+node:ekr.20031218072017.1981: *5* app.setIdFromDialog 

1100 def setIdFromDialog(self): 

1101 """Get leoID from a Tk dialog.""" 

1102 # 

1103 # Don't put up a splash screen: it would obscure the coming dialog. 

1104 self.use_splash_screen = False 

1105 # 

1106 # Get the id, making sure it is at least three characters long. 

1107 attempt = 0 

1108 id_ = None 

1109 while attempt < 2: 

1110 attempt += 1 

1111 dialog = g.TkIDDialog() 

1112 dialog.run() 

1113 # #1404: Make sure the id will not corrupt the .leo file. 

1114 # cleanLeoID raises a warning dialog. 

1115 id_ = self.cleanLeoID(dialog.val, "") 

1116 if id_ and len(id_) > 2: 

1117 break 

1118 # 

1119 # Put result in g.app.leoID. 

1120 # Note: For unit tests, leoTest2.py: create_app sets g.app.leoID. 

1121 if not id_: 

1122 print('Leo can not start without an id.') 

1123 print('Leo will now exit') 

1124 sys.exit(1) 

1125 self.leoID = id_ 

1126 g.blue('leoID=', repr(self.leoID), spaces=False) 

1127 #@+node:ekr.20031218072017.1982: *5* app.setIDFile 

1128 def setIDFile(self): 

1129 """Create leoID.txt.""" 

1130 tag = ".leoID.txt" 

1131 for theDir in (self.homeLeoDir, self.globalConfigDir, self.loadDir): 

1132 if theDir: 

1133 try: 

1134 fn = g.os_path_join(theDir, tag) 

1135 with open(fn, 'w') as f: 

1136 f.write(self.leoID) 

1137 if g.os_path_exists(fn): 

1138 g.error('', tag, 'created in', theDir) 

1139 return 

1140 except IOError: 

1141 pass 

1142 g.error('can not create', tag, 'in', theDir) 

1143 #@+node:ekr.20031218072017.1847: *4* app.setLog, lockLog, unlocklog 

1144 def setLog(self, log): 

1145 """set the frame to which log messages will go""" 

1146 if not self.logIsLocked: 

1147 self.log = log 

1148 

1149 def lockLog(self): 

1150 """Disable changes to the log""" 

1151 # print("app.lockLog:") 

1152 self.logIsLocked = True 

1153 

1154 def unlockLog(self): 

1155 """Enable changes to the log""" 

1156 # print("app.unlockLog:") 

1157 self.logIsLocked = False 

1158 #@+node:ekr.20031218072017.2619: *4* app.writeWaitingLog 

1159 def writeWaitingLog(self, c): 

1160 """Write all waiting lines to the log.""" 

1161 # 

1162 # Do not call g.es, g.es_print, g.pr or g.trace here! 

1163 app = self 

1164 if not c or not c.exists: 

1165 return 

1166 if g.unitTesting: 

1167 app.logWaiting = [] 

1168 g.app.setLog(None) # Prepare to requeue for other commanders. 

1169 return 

1170 # Write the signon to the log: similar to self.computeSignon(). 

1171 table = [ 

1172 ('Leo Log Window', 'red'), 

1173 (app.signon, None), 

1174 (app.signon1, None), 

1175 ] 

1176 table.reverse() 

1177 c.setLog() 

1178 app.logInited = True # Prevent recursive call. 

1179 if not app.silentMode: 

1180 # Write the signon. 

1181 for s, color in table: 

1182 if s: 

1183 app.logWaiting.insert(0, (s, color, True),) 

1184 # Write all the queued log entries. 

1185 for msg in app.logWaiting: 

1186 s, color, newline = msg[:3] 

1187 kwargs = {} if len(msg) < 4 else msg[3] 

1188 kwargs = { 

1189 k: v for k, v in kwargs.items() if k not in ('color', 'newline')} 

1190 g.es('', s, color=color, newline=newline, **kwargs) 

1191 if hasattr(c.frame.log, 'scrollToEnd'): 

1192 g.app.gui.runAtIdle(c.frame.log.scrollToEnd) 

1193 app.logWaiting = [] 

1194 # Essential when opening multiple files... 

1195 g.app.setLog(None) 

1196 #@+node:ekr.20180924093227.1: *3* app.c property 

1197 @property 

1198 def c(self): 

1199 return self.log and self.log.c 

1200 #@+node:ekr.20171127111053.1: *3* app.Closing 

1201 #@+node:ekr.20031218072017.2609: *4* app.closeLeoWindow 

1202 def closeLeoWindow(self, frame, new_c=None, finish_quit=True): 

1203 """ 

1204 Attempt to close a Leo window. 

1205 

1206 Return False if the user veto's the close. 

1207 

1208 finish_quit - usually True, close Leo when last file closes, but 

1209 False when closing an already-open-elsewhere file 

1210 during initial load, so UI remains for files 

1211 further along the command line. 

1212 """ 

1213 c = frame.c 

1214 if 'shutdown' in g.app.debug: 

1215 g.trace(f"changed: {c.changed} {c.shortFileName()}") 

1216 c.endEditing() # Commit any open edits. 

1217 if c.promptingForClose: 

1218 # There is already a dialog open asking what to do. 

1219 return False 

1220 # Make sure .leoRecentFiles.txt is written. 

1221 g.app.recentFilesManager.writeRecentFilesFile(c) 

1222 if c.changed: 

1223 c.promptingForClose = True 

1224 veto = frame.promptForSave() 

1225 c.promptingForClose = False 

1226 if veto: 

1227 return False 

1228 g.app.setLog(None) # no log until we reactive a window. 

1229 g.doHook("close-frame", c=c) 

1230 # 

1231 # Save the window state for *all* open files. 

1232 g.app.commander_cacher.commit() # store cache, but don't close it. 

1233 # This may remove frame from the window list. 

1234 if frame in g.app.windowList: 

1235 g.app.destroyWindow(frame) 

1236 g.app.windowList.remove(frame) 

1237 else: 

1238 # #69. 

1239 g.app.forgetOpenFile(fn=c.fileName()) 

1240 if g.app.windowList: 

1241 c2 = new_c or g.app.windowList[0].c 

1242 g.app.selectLeoWindow(c2) 

1243 elif finish_quit and not g.unitTesting: 

1244 g.app.finishQuit() 

1245 return True # The window has been closed. 

1246 #@+node:ekr.20031218072017.2612: *4* app.destroyAllOpenWithFiles 

1247 def destroyAllOpenWithFiles(self): 

1248 """Remove temp files created with the Open With command.""" 

1249 if 'shutdown' in g.app.debug: 

1250 g.pr('destroyAllOpenWithFiles') 

1251 if g.app.externalFilesController: 

1252 g.app.externalFilesController.shut_down() 

1253 g.app.externalFilesController = None 

1254 #@+node:ekr.20031218072017.2615: *4* app.destroyWindow 

1255 def destroyWindow(self, frame): 

1256 """Destroy all ivars in a Leo frame.""" 

1257 if 'shutdown' in g.app.debug: 

1258 g.pr(f"destroyWindow: {frame.c.shortFileName()}") 

1259 if g.app.externalFilesController: 

1260 g.app.externalFilesController.destroy_frame(frame) 

1261 if frame in g.app.windowList: 

1262 g.app.forgetOpenFile(frame.c.fileName()) 

1263 # force the window to go away now. 

1264 # Important: this also destroys all the objects of the commander. 

1265 frame.destroySelf() 

1266 #@+node:ekr.20031218072017.1732: *4* app.finishQuit 

1267 def finishQuit(self): 

1268 # forceShutdown may already have fired the "end1" hook. 

1269 assert self == g.app, repr(g.app) 

1270 trace = 'shutdown' in g.app.debug 

1271 if trace: 

1272 g.pr('finishQuit: killed:', g.app.killed) 

1273 if not g.app.killed: 

1274 g.doHook("end1") 

1275 if g.app.global_cacher: # #1766. 

1276 g.app.global_cacher.commit_and_close() 

1277 if g.app.commander_cacher: # #1766. 

1278 g.app.commander_cacher.commit() 

1279 g.app.commander_cacher.close() 

1280 if g.app.ipk: 

1281 g.app.ipk.cleanup_consoles() 

1282 g.app.destroyAllOpenWithFiles() 

1283 if hasattr(g.app, 'pyzo_close_handler'): 

1284 # pylint: disable=no-member 

1285 g.app.pyzo_close_handler() 

1286 # Disable all further hooks and events. 

1287 # Alas, "idle" events can still be called 

1288 # even after the following code. 

1289 g.app.killed = True 

1290 if g.app.gui: 

1291 g.app.gui.destroySelf() # Calls qtApp.quit() 

1292 #@+node:ekr.20031218072017.2616: *4* app.forceShutdown 

1293 def forceShutdown(self): 

1294 """ 

1295 Forces an immediate shutdown of Leo at any time. 

1296 

1297 In particular, may be called from plugins during startup. 

1298 """ 

1299 trace = 'shutdown' in g.app.debug 

1300 app = self 

1301 if trace: 

1302 g.pr('forceShutdown') 

1303 for c in app.commanders(): 

1304 app.forgetOpenFile(c.fileName()) 

1305 # Wait until everything is quiet before really quitting. 

1306 if trace: 

1307 g.pr('forceShutdown: before end1') 

1308 g.doHook("end1") 

1309 if trace: 

1310 g.pr('forceShutdown: after end1') 

1311 self.log = None # Disable writeWaitingLog 

1312 self.killed = True # Disable all further hooks. 

1313 for w in self.windowList[:]: 

1314 if trace: 

1315 g.pr(f"forceShutdown: {w}") 

1316 self.destroyWindow(w) 

1317 if trace: 

1318 g.pr('before finishQuit') 

1319 self.finishQuit() 

1320 #@+node:ekr.20031218072017.2617: *4* app.onQuit 

1321 @cmd('exit-leo') 

1322 @cmd('quit-leo') 

1323 def onQuit(self, event=None): 

1324 """Exit Leo, prompting to save unsaved outlines first.""" 

1325 if 'shutdown' in g.app.debug: 

1326 g.trace() 

1327 # #2433 - use the same method as clicking on the close box. 

1328 g.app.gui.close_event(QCloseEvent()) # type:ignore 

1329 #@+node:ville.20090602181814.6219: *3* app.commanders 

1330 def commanders(self): 

1331 """ Return list of currently active controllers """ 

1332 return [f.c for f in g.app.windowList] 

1333 #@+node:ekr.20120427064024.10068: *3* app.Detecting already-open files 

1334 #@+node:ekr.20120427064024.10064: *4* app.checkForOpenFile 

1335 def checkForOpenFile(self, c, fn): 

1336 """Warn if fn is already open and add fn to already_open_files list.""" 

1337 d, tag = g.app.db, 'open-leo-files' 

1338 if g.app.reverting: 

1339 # #302: revert to saved doesn't reset external file change monitoring 

1340 g.app.already_open_files = [] 

1341 if (d is None or 

1342 g.unitTesting or 

1343 g.app.batchMode or 

1344 g.app.reverting or 

1345 g.app.inBridge 

1346 ): 

1347 return 

1348 # #1519: check os.path.exists. 

1349 aList = g.app.db.get(tag) or [] # A list of normalized file names. 

1350 if any(os.path.exists(z) and os.path.samefile(z, fn) for z in aList): 

1351 # The file may be open in another copy of Leo, or not: 

1352 # another Leo may have been killed prematurely. 

1353 # Put the file on the global list. 

1354 # A dialog will warn the user such files later. 

1355 fn = os.path.normpath(fn) 

1356 if fn not in g.app.already_open_files: 

1357 g.es('may be open in another Leo:', color='red') 

1358 g.es(fn) 

1359 g.app.already_open_files.append(fn) 

1360 else: 

1361 g.app.rememberOpenFile(fn) 

1362 #@+node:ekr.20120427064024.10066: *4* app.forgetOpenFile 

1363 def forgetOpenFile(self, fn): 

1364 """ 

1365 Remove fn from g.app.db, so that is no longer considered open. 

1366 """ 

1367 trace = 'shutdown' in g.app.debug 

1368 d, tag = g.app.db, 'open-leo-files' 

1369 if not d or not fn: 

1370 return # #69. 

1371 aList = d.get(tag) or [] 

1372 fn = os.path.normpath(fn) 

1373 if fn in aList: 

1374 aList.remove(fn) 

1375 if trace: 

1376 g.pr(f"forgetOpenFile: {g.shortFileName(fn)}") 

1377 d[tag] = aList 

1378 

1379 #@+node:ekr.20120427064024.10065: *4* app.rememberOpenFile 

1380 def rememberOpenFile(self, fn): 

1381 

1382 # 

1383 # Do not call g.trace, etc. here. 

1384 d, tag = g.app.db, 'open-leo-files' 

1385 if d is None or g.unitTesting or g.app.batchMode or g.app.reverting: 

1386 pass 

1387 elif g.app.preReadFlag: 

1388 pass 

1389 else: 

1390 aList = d.get(tag) or [] 

1391 # It's proper to add duplicates to this list. 

1392 aList.append(os.path.normpath(fn)) 

1393 d[tag] = aList 

1394 #@+node:ekr.20150621062355.1: *4* app.runAlreadyOpenDialog 

1395 def runAlreadyOpenDialog(self, c): 

1396 """Warn about possibly already-open files.""" 

1397 if g.app.already_open_files: 

1398 aList = sorted(set(g.app.already_open_files)) 

1399 g.app.already_open_files = [] 

1400 g.app.gui.dismiss_splash_screen() 

1401 message = ( 

1402 'The following files may already be open\n' 

1403 'in another copy of Leo:\n\n' + 

1404 '\n'.join(aList)) 

1405 g.app.gui.runAskOkDialog(c, 

1406 title='Already Open Files', 

1407 message=message, 

1408 text="Ok") 

1409 #@+node:ekr.20171127111141.1: *3* app.Import utils 

1410 #@+node:ekr.20140727180847.17985: *4* app.scanner_for_at_auto 

1411 def scanner_for_at_auto(self, c, p, **kwargs): 

1412 """A factory returning a scanner function for p, an @auto node.""" 

1413 d = g.app.atAutoDict 

1414 for key in d: 

1415 # pylint: disable=cell-var-from-loop 

1416 func = d.get(key) 

1417 if func and g.match_word(p.h, 0, key): 

1418 return func 

1419 return None 

1420 #@+node:ekr.20140130172810.15471: *4* app.scanner_for_ext 

1421 def scanner_for_ext(self, c, ext, **kwargs): 

1422 """A factory returning a scanner function for the given file extension.""" 

1423 return g.app.classDispatchDict.get(ext) 

1424 #@+node:ekr.20170429152049.1: *3* app.listenToLog 

1425 @cmd('listen-to-log') 

1426 @cmd('log-listen') 

1427 def listenToLog(self, event=None): 

1428 """ 

1429 A socket listener, listening on localhost. See: 

1430 https://docs.python.org/2/howto/logging-cookbook.html#sending-and-receiving-logging-events-across-a-network 

1431 

1432 Start this listener first, then start the broadcaster. 

1433 

1434 leo/plugins/cursesGui2.py is a typical broadcaster. 

1435 """ 

1436 app = self 

1437 # Kill any previous listener. 

1438 if app.log_listener: 

1439 g.es_print('Killing previous listener') 

1440 try: 

1441 app.log_listener.kill() 

1442 except Exception: 

1443 g.es_exception() 

1444 app.log_listener = None 

1445 # Start a new listener. 

1446 g.es_print('Starting log_listener.py') 

1447 path = g.os_path_finalize_join(app.loadDir, 

1448 '..', 'external', 'log_listener.py') 

1449 app.log_listener = subprocess.Popen( 

1450 [sys.executable, path], 

1451 shell=False, 

1452 universal_newlines=True, 

1453 ) 

1454 #@+node:ekr.20171118024827.1: *3* app.makeAllBindings 

1455 def makeAllBindings(self): 

1456 """ 

1457 LeoApp.makeAllBindings: 

1458 

1459 Call c.k.makeAllBindings for all open commanders c. 

1460 """ 

1461 app = self 

1462 for c in app.commanders(): 

1463 c.k.makeAllBindings() 

1464 #@+node:ekr.20031218072017.2188: *3* app.newCommander 

1465 def newCommander(self, fileName, 

1466 gui=None, 

1467 parentFrame=None, 

1468 previousSettings=None, 

1469 relativeFileName=None, 

1470 ): 

1471 """Create a commander and its view frame for the Leo main window.""" 

1472 # Create the commander and its subcommanders. 

1473 # This takes about 3/4 sec when called by the leoBridge module. 

1474 # Timeit reports 0.0175 sec when using a nullGui. 

1475 from leo.core import leoCommands 

1476 c = leoCommands.Commands(fileName, 

1477 gui=gui, 

1478 parentFrame=parentFrame, 

1479 previousSettings=previousSettings, 

1480 relativeFileName=relativeFileName, 

1481 ) 

1482 return c 

1483 #@+node:ekr.20120304065838.15588: *3* app.selectLeoWindow 

1484 def selectLeoWindow(self, c): 

1485 frame = c.frame 

1486 frame.deiconify() 

1487 frame.lift() 

1488 c.setLog() 

1489 master = getattr(frame.top, 'leo_master', None) 

1490 if master: 

1491 # master is a TabbedTopLevel. 

1492 # Selecting the new tab ensures focus is set. 

1493 master.select(c) 

1494 if 1: 

1495 c.initialFocusHelper() 

1496 else: 

1497 c.bodyWantsFocus() 

1498 c.outerUpdate() 

1499 #@-others 

1500#@+node:ekr.20120209051836.10242: ** class LoadManager 

1501class LoadManager: 

1502 """A class to manage loading .leo files, including configuration files.""" 

1503 #@+others 

1504 #@+node:ekr.20120214060149.15851: *3* LM.ctor 

1505 def __init__(self): 

1506 

1507 # Global settings & shortcuts dicts... 

1508 # The are the defaults for computing settings and shortcuts for all loaded files. 

1509 

1510 # A g.TypedDict: the join of settings in leoSettings.leo & myLeoSettings.leo 

1511 self.globalSettingsDict = None 

1512 # A g.TypedDict: the join of shortcuts in leoSettings.leo & myLeoSettings.leo. 

1513 self.globalBindingsDict = None 

1514 

1515 # LoadManager ivars corresponding to user options... 

1516 

1517 self.files = [] # List of files to be loaded. 

1518 self.options = {} # Keys are option names; values are user options. 

1519 self.old_argv = [] # A copy of sys.argv for debugging. 

1520 

1521 # True when more files remain on the command line to be loaded. 

1522 # If the user is answering "No" to each file as Leo asks 

1523 # "file already open, open again". 

1524 # This must be False for a complete exit to be appropriate 

1525 # (finish_quit=True param for closeLeoWindow()) 

1526 self.more_cmdline_files = False 

1527 

1528 # Themes... 

1529 self.leo_settings_c = None 

1530 self.leo_settings_path = None 

1531 self.my_settings_c = None 

1532 self.my_settings_path = None 

1533 self.theme_c = None # #1374. 

1534 self.theme_path = None 

1535 #@+node:ekr.20120211121736.10812: *3* LM.Directory & file utils 

1536 #@+node:ekr.20120219154958.10481: *4* LM.completeFileName 

1537 def completeFileName(self, fileName): 

1538 fileName = g.toUnicode(fileName) 

1539 fileName = g.os_path_finalize(fileName) 

1540 # 2011/10/12: don't add .leo to *any* file. 

1541 return fileName 

1542 #@+node:ekr.20120209051836.10372: *4* LM.computeLeoSettingsPath 

1543 def computeLeoSettingsPath(self): 

1544 """Return the full path to leoSettings.leo.""" 

1545 # lm = self 

1546 join = g.os_path_finalize_join 

1547 settings_fn = 'leoSettings.leo' 

1548 table = ( 

1549 # First, leoSettings.leo in the home directories. 

1550 join(g.app.homeDir, settings_fn), 

1551 join(g.app.homeLeoDir, settings_fn), 

1552 # Last, leoSettings.leo in leo/config directory. 

1553 join(g.app.globalConfigDir, settings_fn) 

1554 ) 

1555 for path in table: 

1556 if g.os_path_exists(path): 

1557 break 

1558 else: 

1559 path = None 

1560 return path 

1561 #@+node:ekr.20120209051836.10373: *4* LM.computeMyLeoSettingsPath 

1562 def computeMyLeoSettingsPath(self): 

1563 """ 

1564 Return the full path to myLeoSettings.leo. 

1565 

1566 The "footnote": Get the local directory from lm.files[0] 

1567 """ 

1568 lm = self 

1569 join = g.os_path_finalize_join 

1570 settings_fn = 'myLeoSettings.leo' 

1571 # This seems pointless: we need a machine *directory*. 

1572 # For now, however, we'll keep the existing code as is. 

1573 machine_fn = lm.computeMachineName() + settings_fn 

1574 # First, compute the directory of the first loaded file. 

1575 # All entries in lm.files are full, absolute paths. 

1576 localDir = g.os_path_dirname(lm.files[0]) if lm.files else '' 

1577 table = ( 

1578 # First, myLeoSettings.leo in the local directory 

1579 join(localDir, settings_fn), 

1580 # Next, myLeoSettings.leo in the home directories. 

1581 join(g.app.homeDir, settings_fn), 

1582 join(g.app.homeLeoDir, settings_fn), 

1583 # Next, <machine-name>myLeoSettings.leo in the home directories. 

1584 join(g.app.homeDir, machine_fn), 

1585 join(g.app.homeLeoDir, machine_fn), 

1586 # Last, leoSettings.leo in leo/config directory. 

1587 join(g.app.globalConfigDir, settings_fn), 

1588 ) 

1589 for path in table: 

1590 if g.os_path_exists(path): 

1591 break 

1592 else: 

1593 path = None 

1594 return path 

1595 #@+node:ekr.20120209051836.10252: *4* LM.computeStandardDirectories & helpers 

1596 def computeStandardDirectories(self): 

1597 """ 

1598 Compute the locations of standard directories and 

1599 set the corresponding ivars. 

1600 """ 

1601 lm = self 

1602 join = os.path.join 

1603 g.app.loadDir = lm.computeLoadDir() 

1604 g.app.globalConfigDir = lm.computeGlobalConfigDir() 

1605 g.app.homeDir = lm.computeHomeDir() 

1606 g.app.homeLeoDir = lm.computeHomeLeoDir() 

1607 g.app.leoDir = lm.computeLeoDir() 

1608 # These use g.app.loadDir... 

1609 g.app.extensionsDir = join(g.app.loadDir, '..', 'extensions') 

1610 g.app.leoEditorDir = join(g.app.loadDir, '..', '..') 

1611 g.app.testDir = join(g.app.loadDir, '..', 'test') 

1612 #@+node:ekr.20120209051836.10253: *5* LM.computeGlobalConfigDir 

1613 def computeGlobalConfigDir(self): 

1614 leo_config_dir = getattr(sys, 'leo_config_directory', None) 

1615 if leo_config_dir: 

1616 theDir = leo_config_dir 

1617 else: 

1618 theDir = os.path.join(g.app.loadDir, "..", "config") 

1619 if theDir: 

1620 theDir = os.path.abspath(theDir) 

1621 if not theDir or not g.os_path_exists(theDir) or not g.os_path_isdir(theDir): 

1622 theDir = None 

1623 return theDir 

1624 #@+node:ekr.20120209051836.10254: *5* LM.computeHomeDir 

1625 def computeHomeDir(self): 

1626 """Returns the user's home directory.""" 

1627 # Windows searches the HOME, HOMEPATH and HOMEDRIVE 

1628 # environment vars, then gives up. 

1629 home = os.path.expanduser("~") 

1630 if home and len(home) > 1 and home[0] == '%' and home[-1] == '%': 

1631 # Get the indirect reference to the true home. 

1632 home = os.getenv(home[1:-1], default=None) 

1633 if home: 

1634 # Important: This returns the _working_ directory if home is None! 

1635 # This was the source of the 4.3 .leoID.txt problems. 

1636 home = g.os_path_finalize(home) 

1637 if (not g.os_path_exists(home) or not g.os_path_isdir(home)): 

1638 home = None 

1639 return home 

1640 #@+node:ekr.20120209051836.10260: *5* LM.computeHomeLeoDir 

1641 def computeHomeLeoDir(self): 

1642 # lm = self 

1643 homeLeoDir = g.os_path_finalize_join(g.app.homeDir, '.leo') 

1644 if g.os_path_exists(homeLeoDir): 

1645 return homeLeoDir 

1646 ok = g.makeAllNonExistentDirectories(homeLeoDir) 

1647 return homeLeoDir if ok else '' # #1450 

1648 #@+node:ekr.20120209051836.10255: *5* LM.computeLeoDir 

1649 def computeLeoDir(self): 

1650 # lm = self 

1651 loadDir = g.app.loadDir 

1652 # We don't want the result in sys.path 

1653 return g.os_path_dirname(loadDir) 

1654 #@+node:ekr.20120209051836.10256: *5* LM.computeLoadDir 

1655 def computeLoadDir(self): 

1656 """Returns the directory containing leo.py.""" 

1657 try: 

1658 # Fix a hangnail: on Windows the drive letter returned by 

1659 # __file__ is randomly upper or lower case! 

1660 # The made for an ugly recent files list. 

1661 path = g.__file__ # was leo.__file__ 

1662 if path: 

1663 # Possible fix for bug 735938: 

1664 # Do the following only if path exists. 

1665 #@+<< resolve symlinks >> 

1666 #@+node:ekr.20120209051836.10257: *6* << resolve symlinks >> 

1667 if path.endswith('pyc'): 

1668 srcfile = path[:-1] 

1669 if os.path.islink(srcfile): 

1670 path = os.path.realpath(srcfile) 

1671 #@-<< resolve symlinks >> 

1672 if sys.platform == 'win32': 

1673 if len(path) > 2 and path[1] == ':': 

1674 # Convert the drive name to upper case. 

1675 path = path[0].upper() + path[1:] 

1676 path = g.os_path_finalize(path) 

1677 loadDir = g.os_path_dirname(path) 

1678 else: loadDir = None 

1679 if ( 

1680 not loadDir or 

1681 not g.os_path_exists(loadDir) or 

1682 not g.os_path_isdir(loadDir) 

1683 ): 

1684 loadDir = os.getcwd() 

1685 # From Marc-Antoine Parent. 

1686 if loadDir.endswith("Contents/Resources"): 

1687 loadDir += "/leo/plugins" 

1688 else: 

1689 g.pr("Exception getting load directory") 

1690 loadDir = g.os_path_finalize(loadDir) 

1691 return loadDir 

1692 except Exception: 

1693 print("Exception getting load directory") 

1694 raise 

1695 #@+node:ekr.20120213164030.10697: *5* LM.computeMachineName 

1696 def computeMachineName(self): 

1697 """Return the name of the current machine, i.e, HOSTNAME.""" 

1698 # This is prepended to leoSettings.leo or myLeoSettings.leo 

1699 # to give the machine-specific setting name. 

1700 # How can this be worth doing?? 

1701 try: 

1702 name = os.getenv('HOSTNAME') 

1703 if not name: 

1704 name = os.getenv('COMPUTERNAME') 

1705 if not name: 

1706 import socket 

1707 name = socket.gethostname() 

1708 except Exception: 

1709 name = '' 

1710 return name 

1711 #@+node:ekr.20180318120148.1: *4* LM.computeThemeDirectories 

1712 def computeThemeDirectories(self): 

1713 """ 

1714 Return a list of *existing* directories that might contain theme .leo files. 

1715 """ 

1716 join = g.os_path_finalize_join 

1717 home = g.app.homeDir 

1718 leo = join(g.app.loadDir, '..') 

1719 table = [ 

1720 home, 

1721 join(home, 'themes'), 

1722 join(home, '.leo'), 

1723 join(home, '.leo', 'themes'), 

1724 join(leo, 'themes'), 

1725 ] 

1726 # Make sure home has normalized slashes. 

1727 return [g.os_path_normslashes(z) for z in table if g.os_path_exists(z)] 

1728 #@+node:ekr.20180318133620.1: *4* LM.computeThemeFilePath & helper 

1729 def computeThemeFilePath(self): 

1730 """ 

1731 Return the absolute path to the theme .leo file, resolved using the search order for themes. 

1732 

1733 1. Use the --theme command-line option if it exists. 

1734 

1735 2. Otherwise, preload the first .leo file. 

1736 Load the file given by @string theme-name setting. 

1737 

1738 3. Finally, look up the @string theme-name in the already-loaded, myLeoSettings.leo. 

1739 Load the file if setting exists. Otherwise return None. 

1740 """ 

1741 trace = 'themes' in g.app.db 

1742 lm = self 

1743 resolve = self.resolve_theme_path 

1744 # 

1745 # Step 1: Use the --theme command-line options if it exists 

1746 path = resolve(lm.options.get('theme_path'), tag='--theme') 

1747 if path: 

1748 # Caller (LM.readGlobalSettingsFiles) sets lm.theme_path 

1749 if trace: 

1750 g.trace('--theme:', path) 

1751 return path 

1752 # 

1753 # Step 2: look for the @string theme-name setting in the first loaded file. 

1754 path = lm.files and lm.files[0] 

1755 if path and g.os_path_exists(path): 

1756 # Tricky: we must call lm.computeLocalSettings *here*. 

1757 theme_c = lm.openSettingsFile(path) 

1758 if theme_c: 

1759 settings_d, junk_shortcuts_d = lm.computeLocalSettings( 

1760 c=theme_c, 

1761 settings_d=lm.globalSettingsDict, 

1762 bindings_d=lm.globalBindingsDict, 

1763 localFlag=False, 

1764 ) 

1765 setting = settings_d.get_string_setting('theme-name') 

1766 if setting: 

1767 tag = theme_c.shortFileName() 

1768 path = resolve(setting, tag=tag) 

1769 if path: 

1770 # Caller (LM.readGlobalSettingsFiles) sets lm.theme_path 

1771 if trace: 

1772 g.trace("First loaded file", theme_c.shortFileName(), path) 

1773 return path 

1774 # 

1775 # Step 3: use the @string theme-name setting in myLeoSettings.leo. 

1776 # Note: the setting should *never* appear in leoSettings.leo! 

1777 setting = lm.globalSettingsDict.get_string_setting('theme-name') 

1778 tag = 'myLeoSettings.leo' 

1779 path = resolve(setting, tag=tag) 

1780 if trace: 

1781 g.trace("myLeoSettings.leo", path) 

1782 return path 

1783 #@+node:ekr.20180321124503.1: *5* LM.resolve_theme_path 

1784 def resolve_theme_path(self, fn, tag): 

1785 """Search theme directories for the given .leo file.""" 

1786 if not fn or fn.lower().strip() == 'none': 

1787 return None 

1788 if not fn.endswith('.leo'): 

1789 fn += '.leo' 

1790 for directory in self.computeThemeDirectories(): 

1791 path = g.os_path_join(directory, fn) # Normalizes slashes, etc. 

1792 if g.os_path_exists(path): 

1793 return path 

1794 print(f"theme .leo file not found: {fn}") 

1795 return None 

1796 #@+node:ekr.20120211121736.10772: *4* LM.computeWorkbookFileName 

1797 def computeWorkbookFileName(self): 

1798 """ 

1799 Return full path to the workbook. 

1800 

1801 Return None if testing, or in batch mode, or if the containing 

1802 directory does not exist. 

1803 """ 

1804 # lm = self 

1805 # Never create a workbook during unit tests or in batch mode. 

1806 if g.unitTesting or g.app.batchMode: 

1807 return None 

1808 fn = g.app.config.getString(setting='default_leo_file') or '~/.leo/workbook.leo' 

1809 fn = g.os_path_finalize(fn) 

1810 directory = g.os_path_finalize(os.path.dirname(fn)) 

1811 # #1415. 

1812 return fn if os.path.exists(directory) else None 

1813 #@+node:ekr.20120219154958.10485: *4* LM.reportDirectories 

1814 def reportDirectories(self): 

1815 """Report directories.""" 

1816 # The cwd changes later, so it would be misleading to report it here. 

1817 for kind, theDir in ( 

1818 ('home', g.app.homeDir), 

1819 ('leo-editor', g.app.leoEditorDir), 

1820 ('load', g.app.loadDir), 

1821 ('config', g.app.globalConfigDir), 

1822 ): # g.blue calls g.es_print, and that's annoying. 

1823 g.es(f"{kind:>10}:", os.path.normpath(theDir), color='blue') 

1824 #@+node:ekr.20120215062153.10740: *3* LM.Settings 

1825 #@+node:ekr.20120130101219.10182: *4* LM.computeBindingLetter 

1826 def computeBindingLetter(self, c, path): 

1827 lm = self 

1828 if not path: 

1829 return 'D' 

1830 path = path.lower() 

1831 table = ( 

1832 ('M', 'myLeoSettings.leo'), 

1833 (' ', 'leoSettings.leo'), 

1834 ('F', c.shortFileName()), 

1835 ) 

1836 for letter, path2 in table: 

1837 if path2 and path.endswith(path2.lower()): 

1838 return letter 

1839 if lm.theme_path and path.endswith(lm.theme_path.lower()): 

1840 return 'T' 

1841 if path == 'register-command' or path.find('mode') > -1: 

1842 return '@' 

1843 return 'D' 

1844 #@+node:ekr.20120223062418.10421: *4* LM.computeLocalSettings 

1845 def computeLocalSettings(self, c, settings_d, bindings_d, localFlag): 

1846 """ 

1847 Merge the settings dicts from c's outline into *new copies of* 

1848 settings_d and bindings_d. 

1849 """ 

1850 lm = self 

1851 shortcuts_d2, settings_d2 = lm.createSettingsDicts(c, localFlag) 

1852 if not bindings_d: # #1766: unit tests. 

1853 settings_d, bindings_d = lm.createDefaultSettingsDicts() 

1854 if settings_d2: 

1855 if g.app.trace_setting: 

1856 key = g.app.config.munge(g.app.trace_setting) 

1857 val = settings_d2.d.get(key) 

1858 if val: 

1859 fn = g.shortFileName(val.path) 

1860 g.es_print( 

1861 f"--trace-setting: in {fn:20}: " 

1862 f"@{val.kind} {g.app.trace_setting}={val.val}") 

1863 settings_d = settings_d.copy() 

1864 settings_d.update(settings_d2) 

1865 if shortcuts_d2: 

1866 bindings_d = lm.mergeShortcutsDicts(c, bindings_d, shortcuts_d2, localFlag) 

1867 return settings_d, bindings_d 

1868 #@+node:ekr.20121126202114.3: *4* LM.createDefaultSettingsDicts 

1869 def createDefaultSettingsDicts(self): 

1870 """Create lm.globalSettingsDict & lm.globalBindingsDict.""" 

1871 settings_d = g.app.config.defaultsDict 

1872 assert isinstance(settings_d, g.TypedDict), settings_d 

1873 settings_d.setName('lm.globalSettingsDict') 

1874 bindings_d = g.TypedDict( # was TypedDictOfLists. 

1875 name='lm.globalBindingsDict', 

1876 keyType=type('s'), 

1877 valType=g.BindingInfo, 

1878 ) 

1879 return settings_d, bindings_d 

1880 #@+node:ekr.20120214165710.10726: *4* LM.createSettingsDicts 

1881 def createSettingsDicts(self, c, localFlag): 

1882 

1883 from leo.core import leoConfig 

1884 if c: 

1885 # returns the *raw* shortcutsDict, not a *merged* shortcuts dict. 

1886 parser = leoConfig.SettingsTreeParser(c, localFlag) 

1887 shortcutsDict, settingsDict = parser.traverse() 

1888 return shortcutsDict, settingsDict 

1889 return None, None 

1890 #@+node:ekr.20120223062418.10414: *4* LM.getPreviousSettings 

1891 def getPreviousSettings(self, fn): 

1892 """ 

1893 Return the settings in effect for fn. Typically, this involves 

1894 pre-reading fn. 

1895 """ 

1896 lm = self 

1897 settingsName = f"settings dict for {g.shortFileName(fn)}" 

1898 shortcutsName = f"shortcuts dict for {g.shortFileName(fn)}" 

1899 # A special case: settings in leoSettings.leo do *not* override 

1900 # the global settings, that is, settings in myLeoSettings.leo. 

1901 isLeoSettings = fn and g.shortFileName(fn).lower() == 'leosettings.leo' 

1902 exists = g.os_path_exists(fn) 

1903 if fn and exists and lm.isLeoFile(fn) and not isLeoSettings: 

1904 # Open the file usinging a null gui. 

1905 try: 

1906 g.app.preReadFlag = True 

1907 c = lm.openSettingsFile(fn) 

1908 finally: 

1909 g.app.preReadFlag = False 

1910 # Merge the settings from c into *copies* of the global dicts. 

1911 d1, d2 = lm.computeLocalSettings(c, 

1912 lm.globalSettingsDict, 

1913 lm.globalBindingsDict, 

1914 localFlag=True) # d1 and d2 are copies. 

1915 d1.setName(settingsName) 

1916 d2.setName(shortcutsName) 

1917 return PreviousSettings(d1, d2) 

1918 # 

1919 # The file does not exist, or is not valid. 

1920 # Get the settings from the globals settings dicts. 

1921 if lm.globalSettingsDict and lm.globalBindingsDict: # #1766. 

1922 d1 = lm.globalSettingsDict.copy(settingsName) 

1923 d2 = lm.globalBindingsDict.copy(shortcutsName) 

1924 else: 

1925 d1 = d2 = None 

1926 return PreviousSettings(d1, d2) 

1927 #@+node:ekr.20120214132927.10723: *4* LM.mergeShortcutsDicts & helpers 

1928 def mergeShortcutsDicts(self, c, old_d, new_d, localFlag): 

1929 """ 

1930 Create a new dict by overriding all shortcuts in old_d by shortcuts in new_d. 

1931 

1932 Both old_d and new_d remain unchanged. 

1933 """ 

1934 lm = self 

1935 if not old_d: 

1936 return new_d 

1937 if not new_d: 

1938 return old_d 

1939 bi_list = new_d.get(g.app.trace_setting) 

1940 if bi_list: 

1941 # This code executed only if g.app.trace_setting exists. 

1942 for bi in bi_list: 

1943 fn = bi.kind.split(' ')[-1] 

1944 stroke = c.k.prettyPrintKey(bi.stroke) 

1945 if bi.pane and bi.pane != 'all': 

1946 pane = f" in {bi.pane} panes" 

1947 else: 

1948 pane = '' 

1949 inverted_old_d = lm.invert(old_d) 

1950 inverted_new_d = lm.invert(new_d) 

1951 # #510 & #327: always honor --trace-binding here. 

1952 if g.app.trace_binding: 

1953 binding = g.app.trace_binding 

1954 # First, see if the binding is for a command. (Doesn't work for plugin commands). 

1955 if localFlag and binding in c.k.killedBindings: 

1956 g.es_print( 

1957 f"--trace-binding: {c.shortFileName()} " 

1958 f"sets {binding} to None") 

1959 elif localFlag and binding in c.commandsDict: 

1960 d = c.k.computeInverseBindingDict() 

1961 g.trace( 

1962 f"--trace-binding: {c.shortFileName():20} " 

1963 f"binds {binding} to {d.get(binding) or []}") 

1964 else: 

1965 binding = g.app.trace_binding 

1966 stroke = g.KeyStroke(binding) 

1967 bi_list = inverted_new_d.get(stroke) 

1968 if bi_list: 

1969 print('') 

1970 for bi in bi_list: 

1971 fn = bi.kind.split(' ')[-1] # bi.kind # 

1972 stroke2 = c.k.prettyPrintKey(stroke) 

1973 if bi.pane and bi.pane != 'all': 

1974 pane = f" in {bi.pane} panes" 

1975 else: 

1976 pane = '' 

1977 g.es_print( 

1978 f"--trace-binding: {fn:20} binds {stroke2} " 

1979 f"to {bi.commandName:>20}{pane}") 

1980 print('') 

1981 # Fix bug 951921: check for duplicate shortcuts only in the new file. 

1982 lm.checkForDuplicateShortcuts(c, inverted_new_d) 

1983 inverted_old_d.update(inverted_new_d) # Updates inverted_old_d in place. 

1984 result = lm.uninvert(inverted_old_d) 

1985 return result 

1986 #@+node:ekr.20120311070142.9904: *5* LM.checkForDuplicateShortcuts 

1987 def checkForDuplicateShortcuts(self, c, d): 

1988 """ 

1989 Check for duplicates in an "inverted" dictionary d 

1990 whose keys are strokes and whose values are lists of BindingInfo nodes. 

1991 

1992 Duplicates happen only if panes conflict. 

1993 """ 

1994 # Fix bug 951921: check for duplicate shortcuts only in the new file. 

1995 for ks in sorted(list(d.keys())): 

1996 duplicates, panes = [], ['all'] 

1997 aList = d.get(ks) # A list of bi objects. 

1998 aList2 = [z for z in aList if not z.pane.startswith('mode')] 

1999 if len(aList) > 1: 

2000 for bi in aList2: 

2001 if bi.pane in panes: 

2002 duplicates.append(bi) 

2003 else: 

2004 panes.append(bi.pane) 

2005 if duplicates: 

2006 bindings = list(set([z.stroke.s for z in duplicates])) 

2007 if len(bindings) == 1: 

2008 kind = 'duplicate, (not conflicting)' 

2009 else: 

2010 kind = 'conflicting' 

2011 g.es_print(f"{kind} key bindings in {c.shortFileName()}") 

2012 for bi in aList2: 

2013 g.es_print(f"{bi.pane:6} {bi.stroke.s} {bi.commandName}") 

2014 #@+node:ekr.20120214132927.10724: *5* LM.invert 

2015 def invert(self, d): 

2016 """ 

2017 Invert a shortcut dict whose keys are command names, 

2018 returning a dict whose keys are strokes. 

2019 """ 

2020 result = g.TypedDict( # was TypedDictOfLists. 

2021 name=f"inverted {d.name()}", 

2022 keyType=g.KeyStroke, 

2023 valType=g.BindingInfo, 

2024 ) 

2025 for commandName in d.keys(): 

2026 for bi in d.get(commandName, []): 

2027 stroke = bi.stroke # This is canonicalized. 

2028 bi.commandName = commandName # Add info. 

2029 assert stroke 

2030 result.add_to_list(stroke, bi) 

2031 return result 

2032 #@+node:ekr.20120214132927.10725: *5* LM.uninvert 

2033 def uninvert(self, d): 

2034 """ 

2035 Uninvert an inverted shortcut dict whose keys are strokes, 

2036 returning a dict whose keys are command names. 

2037 """ 

2038 assert d.keyType == g.KeyStroke, d.keyType 

2039 result = g.TypedDict( # was TypedDictOfLists. 

2040 name=f"uninverted {d.name()}", 

2041 keyType=type('commandName'), 

2042 valType=g.BindingInfo, 

2043 ) 

2044 for stroke in d.keys(): 

2045 for bi in d.get(stroke, []): 

2046 commandName = bi.commandName 

2047 assert commandName 

2048 result.add_to_list(commandName, bi) 

2049 return result 

2050 #@+node:ekr.20120222103014.10312: *4* LM.openSettingsFile 

2051 def openSettingsFile(self, fn): 

2052 """ 

2053 Open a settings file with a null gui. Return the commander. 

2054 

2055 The caller must init the c.config object. 

2056 """ 

2057 lm = self 

2058 if not fn: 

2059 return None 

2060 theFile = lm.openAnyLeoFile(fn) 

2061 if not theFile: 

2062 return None # Fix #843. 

2063 if not any([g.unitTesting, g.app.silentMode, g.app.batchMode]): 

2064 # This occurs early in startup, so use the following. 

2065 s = f"reading settings in {os.path.normpath(fn)}" 

2066 if 'startup' in g.app.debug: 

2067 print(s) 

2068 g.es(s, color='blue') 

2069 # A useful trace. 

2070 # g.trace('%20s' % g.shortFileName(fn), g.callers(3)) 

2071 # Changing g.app.gui here is a major hack. It is necessary. 

2072 oldGui = g.app.gui 

2073 g.app.gui = g.app.nullGui 

2074 c = g.app.newCommander(fn) 

2075 frame = c.frame 

2076 frame.log.enable(False) 

2077 g.app.lockLog() 

2078 g.app.openingSettingsFile = True 

2079 try: 

2080 ok = c.fileCommands.openLeoFile(theFile, fn, 

2081 readAtFileNodesFlag=False, silent=True) # closes theFile. 

2082 finally: 

2083 g.app.openingSettingsFile = False 

2084 g.app.unlockLog() 

2085 c.openDirectory = frame.openDirectory = g.os_path_dirname(fn) 

2086 g.app.gui = oldGui 

2087 return c if ok else None 

2088 #@+node:ekr.20120213081706.10382: *4* LM.readGlobalSettingsFiles 

2089 def readGlobalSettingsFiles(self): 

2090 """ 

2091 Read leoSettings.leo and myLeoSettings.leo using a null gui. 

2092 

2093 New in Leo 6.1: this sets ivars for the ActiveSettingsOutline class. 

2094 """ 

2095 trace = 'themes' in g.app.debug 

2096 lm = self 

2097 # Open the standard settings files with a nullGui. 

2098 # Important: their commanders do not exist outside this method! 

2099 old_commanders = g.app.commanders() 

2100 lm.leo_settings_path = lm.computeLeoSettingsPath() 

2101 lm.my_settings_path = lm.computeMyLeoSettingsPath() 

2102 lm.leo_settings_c = lm.openSettingsFile(self.leo_settings_path) 

2103 lm.my_settings_c = lm.openSettingsFile(self.my_settings_path) 

2104 commanders = [lm.leo_settings_c, lm.my_settings_c] 

2105 commanders = [z for z in commanders if z] 

2106 settings_d, bindings_d = lm.createDefaultSettingsDicts() 

2107 for c in commanders: 

2108 # Merge the settings dicts from c's outline into 

2109 # *new copies of* settings_d and bindings_d. 

2110 settings_d, bindings_d = lm.computeLocalSettings( 

2111 c, settings_d, bindings_d, localFlag=False) 

2112 # Adjust the name. 

2113 bindings_d.setName('lm.globalBindingsDict') 

2114 lm.globalSettingsDict = settings_d 

2115 lm.globalBindingsDict = bindings_d 

2116 # Add settings from --theme or @string theme-name files. 

2117 # This must be done *after* reading myLeoSettigns.leo. 

2118 lm.theme_path = lm.computeThemeFilePath() 

2119 if lm.theme_path: 

2120 lm.theme_c = lm.openSettingsFile(lm.theme_path) 

2121 if lm.theme_c: 

2122 # Merge theme_c's settings into globalSettingsDict. 

2123 settings_d, junk_shortcuts_d = lm.computeLocalSettings( 

2124 lm.theme_c, settings_d, bindings_d, localFlag=False) 

2125 lm.globalSettingsDict = settings_d 

2126 # Set global var used by the StyleSheetManager. 

2127 g.app.theme_directory = g.os_path_dirname(lm.theme_path) 

2128 if trace: 

2129 g.trace('g.app.theme_directory', g.app.theme_directory) 

2130 # Clear the cache entries for the commanders. 

2131 # This allows this method to be called outside the startup logic. 

2132 for c in commanders: 

2133 if c not in old_commanders: 

2134 g.app.forgetOpenFile(c.fileName()) 

2135 #@+node:ekr.20120214165710.10838: *4* LM.traceSettingsDict 

2136 def traceSettingsDict(self, d, verbose=False): 

2137 if verbose: 

2138 print(d) 

2139 for key in sorted(list(d.keys())): 

2140 gs = d.get(key) 

2141 print(f"{key:35} {g.shortFileName(gs.path):17} {gs.val}") 

2142 if d: 

2143 print('') 

2144 else: 

2145 # print(d) 

2146 print(f"{d.name} {len(d.d.keys())}") 

2147 #@+node:ekr.20120214165710.10822: *4* LM.traceShortcutsDict 

2148 def traceShortcutsDict(self, d, verbose=False): 

2149 if verbose: 

2150 print(d) 

2151 for key in sorted(list(d.keys())): 

2152 val = d.get(key) 

2153 # print('%20s %s' % (key,val.dump())) 

2154 print(f"{key:35} {[z.stroke for z in val]}") 

2155 if d: 

2156 print('') 

2157 else: 

2158 print(d) 

2159 #@+node:ekr.20120219154958.10452: *3* LM.load & helpers 

2160 def load(self, fileName=None, pymacs=None): 

2161 """This is Leo's main startup method.""" 

2162 lm = self 

2163 # 

2164 # Phase 1: before loading plugins. 

2165 # Scan options, set directories and read settings. 

2166 t1 = time.process_time() 

2167 print('') # Give some separation for the coming traces. 

2168 if not lm.isValidPython(): 

2169 return 

2170 lm.doPrePluginsInit(fileName, pymacs) # sets lm.options and lm.files 

2171 g.app.computeSignon() 

2172 g.app.printSignon() 

2173 if lm.options.get('version'): 

2174 return 

2175 if not g.app.gui: 

2176 return 

2177 # Disable redraw until all files are loaded. 

2178 g.app.disable_redraw = True 

2179 # 

2180 # Phase 2: load plugins: the gui has already been set. 

2181 t2 = time.process_time() 

2182 g.doHook("start1") 

2183 t3 = time.process_time() 

2184 if g.app.killed: 

2185 return 

2186 g.app.idleTimeManager.start() 

2187 # 

2188 # Phase 3: after loading plugins. Create one or more frames. 

2189 t3 = time.process_time() 

2190 if lm.options.get('script') and not self.files: 

2191 ok = True 

2192 else: 

2193 ok = lm.doPostPluginsInit() 

2194 # Fix #579: Key bindings don't take for commands defined in plugins 

2195 g.app.makeAllBindings() 

2196 if ok and g.app.diff: 

2197 lm.doDiff() 

2198 if not ok: 

2199 return 

2200 g.es('') # Clears horizontal scrolling in the log pane. 

2201 if g.app.listen_to_log_flag: 

2202 g.app.listenToLog() 

2203 if 'startup' in g.app.debug: 

2204 t4 = time.process_time() 

2205 print('') 

2206 g.es_print(f"settings:{t2 - t1:5.2f} sec") 

2207 g.es_print(f" plugins:{t3 - t2:5.2f} sec") 

2208 g.es_print(f" files:{t4 - t3:5.2f} sec") 

2209 g.es_print(f" total:{t4 - t1:5.2f} sec") 

2210 print('') 

2211 # -- quit 

2212 if g.app.quit_after_load: 

2213 if 'shutdown' in g.app.debug or 'startup' in g.app.debug: 

2214 print('--quit') 

2215 g.app.forceShutdown() 

2216 return 

2217 # #1128: support for restart-leo. 

2218 if not g.app.start_minimized: 

2219 try: # Careful: we may be unit testing. 

2220 g.app.log.c.frame.bringToFront() 

2221 except Exception: 

2222 pass 

2223 g.app.gui.runMainLoop() 

2224 # For scripts, the gui is a nullGui. 

2225 # and the gui.setScript has already been called. 

2226 #@+node:ekr.20150225133846.7: *4* LM.doDiff 

2227 def doDiff(self): 

2228 """Support --diff option after loading Leo.""" 

2229 if len(self.old_argv[2:]) == 2: 

2230 pass # c.editFileCommands.compareAnyTwoFiles gives a message. 

2231 else: 

2232 # This is an unusual situation. 

2233 g.es('--diff mode. sys.argv[2:]...', color='red') 

2234 for z in self.old_argv[2:]: 

2235 g.es(g.shortFileName(z) if z else repr(z), color='blue') 

2236 commanders = g.app.commanders() 

2237 if len(commanders) == 2: 

2238 c = commanders[0] 

2239 c.editFileCommands.compareAnyTwoFiles(event=None) 

2240 #@+node:ekr.20120219154958.10487: *4* LM.doPostPluginsInit & helpers 

2241 def doPostPluginsInit(self): 

2242 """Create a Leo window for each file in the lm.files list.""" 

2243 # Clear g.app.initing _before_ creating commanders. 

2244 lm = self 

2245 g.app.initing = False # "idle" hooks may now call g.app.forceShutdown. 

2246 # Create the main frame.Show it and all queued messages. 

2247 c = c1 = fn = None 

2248 if lm.files: 

2249 try: # #1403. 

2250 for n, fn in enumerate(lm.files): 

2251 lm.more_cmdline_files = n < len(lm.files) - 1 

2252 # Returns None if the file is open in another instance of Leo. 

2253 c = lm.loadLocalFile(fn, gui=g.app.gui, old_c=None) 

2254 if c and not c1: # #1416: 

2255 c1 = c 

2256 except Exception: 

2257 g.es_print(f"Unexpected exception reading {fn!r}") 

2258 g.es_exception() 

2259 c = None 

2260 # Load (and save later) a session *only* if the command line contains no files. 

2261 g.app.loaded_session = not lm.files 

2262 if g.app.sessionManager and g.app.loaded_session: 

2263 try: # #1403. 

2264 aList = g.app.sessionManager.load_snapshot() 

2265 if aList: 

2266 g.app.sessionManager.load_session(c1, aList) 

2267 # #659. 

2268 if g.app.windowList: 

2269 c = c1 = g.app.windowList[0].c 

2270 else: 

2271 c = c1 = None 

2272 except Exception: 

2273 g.es_print('Can not load session') 

2274 g.es_exception() 

2275 

2276 # Enable redraws. 

2277 g.app.disable_redraw = False 

2278 if not c1: 

2279 try: # #1403. 

2280 c1 = lm.openEmptyWorkBook() # Calls LM.loadLocalFile. 

2281 except Exception: 

2282 g.es_print('Can not create empty workbook') 

2283 g.es_exception() 

2284 c = c1 

2285 if not c: 

2286 # Leo is out of options: Force an immediate exit. 

2287 return False 

2288 # #199. 

2289 g.app.runAlreadyOpenDialog(c1) 

2290 # 

2291 # Final inits... 

2292 # For qt gui, select the first-loaded tab. 

2293 if hasattr(g.app.gui, 'frameFactory'): 

2294 factory = g.app.gui.frameFactory 

2295 if factory and hasattr(factory, 'setTabForCommander'): 

2296 factory.setTabForCommander(c) 

2297 g.app.logInited = True 

2298 g.app.initComplete = True 

2299 c.setLog() 

2300 c.redraw() 

2301 g.doHook("start2", c=c, p=c.p, fileName=c.fileName()) 

2302 c.initialFocusHelper() 

2303 screenshot_fn = lm.options.get('screenshot_fn') 

2304 if screenshot_fn: 

2305 lm.make_screen_shot(screenshot_fn) 

2306 return False # Force an immediate exit. 

2307 return True 

2308 #@+node:ekr.20120219154958.10489: *5* LM.make_screen_shot 

2309 def make_screen_shot(self, fn): 

2310 """Create a screenshot of the present Leo outline and save it to path.""" 

2311 if g.app.gui.guiName() == 'qt': 

2312 m = g.loadOnePlugin('screenshots') 

2313 m.make_screen_shot(fn) 

2314 #@+node:ekr.20131028155339.17098: *5* LM.openEmptyWorkBook 

2315 def openEmptyWorkBook(self): 

2316 """Save CheatSheet.leo as the workbook. Return the new commander.""" 

2317 fn = self.computeWorkbookFileName() 

2318 if not fn: 

2319 return None # #1415 

2320 # Open CheatSheet.leo. 

2321 fn2 = g.os_path_finalize_join(g.app.loadDir, '..', 'doc', 'CheatSheet.leo') 

2322 if not g.os_path_exists(fn2): 

2323 return None 

2324 c = self.loadLocalFile(fn2, gui=g.app.gui, old_c=None) 

2325 # Save as the workbook name. 

2326 c.mFileName = fn 

2327 c.frame.title = title = c.computeWindowTitle(fn) 

2328 c.frame.setTitle(title) 

2329 c.openDirectory = c.frame.openDirectory = g.os_path_dirname(fn) 

2330 if hasattr(c.frame, 'top'): 

2331 c.frame.top.leo_master.setTabName(c, fn) 

2332 c.fileCommands.saveAs(fn) 

2333 g.app.recentFilesManager.updateRecentFiles(fn) 

2334 g.chdir(fn) 

2335 # Finish. 

2336 g.app.already_open_files = [] 

2337 c.target_language = 'rest' 

2338 c.clearChanged() 

2339 c.redraw(c.rootPosition()) # # 1380: Select the root. 

2340 return c 

2341 #@+node:ekr.20120219154958.10477: *4* LM.doPrePluginsInit & helpers 

2342 def doPrePluginsInit(self, fileName, pymacs): 

2343 """ Scan options, set directories and read settings.""" 

2344 lm = self 

2345 lm.computeStandardDirectories() 

2346 # Scan the options as early as possible. 

2347 lm.options = options = lm.scanOptions(fileName, pymacs) # also sets lm.files. 

2348 if options.get('version'): 

2349 return 

2350 script = options.get('script') 

2351 verbose = script is None 

2352 # Init the app. 

2353 lm.initApp(verbose) 

2354 g.app.setGlobalDb() 

2355 if verbose: 

2356 lm.reportDirectories() 

2357 # Read settings *after* setting g.app.config and *before* opening plugins. 

2358 # This means if-gui has effect only in per-file settings. 

2359 if g.app.quit_after_load: 

2360 localConfigFile = None 

2361 else: 

2362 # Read only standard settings files, using a null gui. 

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

2364 # that might contain myLeoSettings.leo. 

2365 lm.readGlobalSettingsFiles() 

2366 # Read the recent files file. 

2367 localConfigFile = lm.files[0] if lm.files else None 

2368 g.app.recentFilesManager.readRecentFiles(localConfigFile) 

2369 # Create the gui after reading options and settings. 

2370 lm.createGui(pymacs) 

2371 # We can't print the signon until we know the gui. 

2372 g.app.computeSignon() # Set app.signon/signon1 for commanders. 

2373 #@+node:ekr.20170302093006.1: *5* LM.createAllImporterData & helpers 

2374 def createAllImporterData(self): 

2375 """ 

2376 New in Leo 5.5: 

2377 

2378 Create global data structures describing importers and writers. 

2379 """ 

2380 assert g.app.loadDir # This is the only data required. 

2381 self.createWritersData() # Was an AtFile method. 

2382 self.createImporterData() # Was a LeoImportCommands method. 

2383 #@+node:ekr.20140724064952.18037: *6* LM.createImporterData & helper 

2384 def createImporterData(self): 

2385 """Create the data structures describing importer plugins.""" 

2386 # Allow plugins to be defined in ~/.leo/plugins. 

2387 plugins1 = g.os_path_finalize_join(g.app.homeDir, '.leo', 'plugins') 

2388 plugins2 = g.os_path_finalize_join(g.app.loadDir, '..', 'plugins') 

2389 for kind, plugins in (('home', plugins1), ('leo', plugins2)): 

2390 pattern = g.os_path_finalize_join( 

2391 g.app.loadDir, '..', 'plugins', 'importers', '*.py') 

2392 for fn in g.glob_glob(pattern): 

2393 sfn = g.shortFileName(fn) 

2394 if sfn != '__init__.py': 

2395 try: 

2396 module_name = sfn[:-3] 

2397 # Important: use importlib to give imported modules 

2398 # their fully qualified names. 

2399 m = importlib.import_module( 

2400 f"leo.plugins.importers.{module_name}") 

2401 self.parse_importer_dict(sfn, m) 

2402 # print('createImporterData', m.__name__) 

2403 except Exception: 

2404 g.warning(f"can not import leo.plugins.importers.{module_name}") 

2405 #@+node:ekr.20140723140445.18076: *7* LM.parse_importer_dict 

2406 def parse_importer_dict(self, sfn, m): 

2407 """ 

2408 Set entries in g.app.classDispatchDict, g.app.atAutoDict and 

2409 g.app.atAutoNames using entries in m.importer_dict. 

2410 """ 

2411 importer_d = getattr(m, 'importer_dict', None) 

2412 if importer_d: 

2413 at_auto = importer_d.get('@auto', []) 

2414 scanner_func = importer_d.get('func', None) 

2415 # scanner_name = scanner_class.__name__ 

2416 extensions = importer_d.get('extensions', []) 

2417 if at_auto: 

2418 # Make entries for each @auto type. 

2419 d = g.app.atAutoDict 

2420 for s in at_auto: 

2421 d[s] = scanner_func 

2422 g.app.atAutoDict[s] = scanner_func 

2423 g.app.atAutoNames.add(s) 

2424 if extensions: 

2425 # Make entries for each extension. 

2426 d = g.app.classDispatchDict 

2427 for ext in extensions: 

2428 d[ext] = scanner_func #importer_d.get('func')#scanner_class 

2429 elif sfn not in ( 

2430 # These are base classes, not real plugins. 

2431 'basescanner.py', 

2432 'linescanner.py', 

2433 ): 

2434 g.warning(f"leo/plugins/importers/{sfn} has no importer_dict") 

2435 #@+node:ekr.20140728040812.17990: *6* LM.createWritersData & helper 

2436 def createWritersData(self): 

2437 """Create the data structures describing writer plugins.""" 

2438 # Do *not* remove this trace. 

2439 trace = False and 'createWritersData' not in g.app.debug_dict 

2440 if trace: 

2441 # Suppress multiple traces. 

2442 g.app.debug_dict['createWritersData'] = True 

2443 g.app.writersDispatchDict = {} 

2444 g.app.atAutoWritersDict = {} 

2445 plugins1 = g.os_path_finalize_join(g.app.homeDir, '.leo', 'plugins') 

2446 plugins2 = g.os_path_finalize_join(g.app.loadDir, '..', 'plugins') 

2447 for kind, plugins in (('home', plugins1), ('leo', plugins2)): 

2448 pattern = g.os_path_finalize_join(g.app.loadDir, 

2449 '..', 'plugins', 'writers', '*.py') 

2450 for fn in g.glob_glob(pattern): 

2451 sfn = g.shortFileName(fn) 

2452 if sfn != '__init__.py': 

2453 try: 

2454 # Important: use importlib to give imported modules their fully qualified names. 

2455 m = importlib.import_module(f"leo.plugins.writers.{sfn[:-3]}") 

2456 self.parse_writer_dict(sfn, m) 

2457 except Exception: 

2458 g.es_exception() 

2459 g.warning(f"can not import leo.plugins.writers.{sfn}") 

2460 if trace: 

2461 g.trace('LM.writersDispatchDict') 

2462 g.printDict(g.app.writersDispatchDict) 

2463 g.trace('LM.atAutoWritersDict') 

2464 g.printDict(g.app.atAutoWritersDict) 

2465 #@+node:ekr.20140728040812.17991: *7* LM.parse_writer_dict 

2466 def parse_writer_dict(self, sfn, m): 

2467 """ 

2468 Set entries in g.app.writersDispatchDict and g.app.atAutoWritersDict 

2469 using entries in m.writers_dict. 

2470 """ 

2471 writer_d = getattr(m, 'writer_dict', None) 

2472 if writer_d: 

2473 at_auto = writer_d.get('@auto', []) 

2474 scanner_class = writer_d.get('class', None) 

2475 extensions = writer_d.get('extensions', []) 

2476 if at_auto: 

2477 # Make entries for each @auto type. 

2478 d = g.app.atAutoWritersDict 

2479 for s in at_auto: 

2480 aClass = d.get(s) 

2481 if aClass and aClass != scanner_class: 

2482 g.trace( 

2483 f"{sfn}: duplicate {s} class {aClass.__name__} " 

2484 f"in {m.__file__}:") 

2485 else: 

2486 d[s] = scanner_class 

2487 g.app.atAutoNames.add(s) 

2488 if extensions: 

2489 # Make entries for each extension. 

2490 d = g.app.writersDispatchDict 

2491 for ext in extensions: 

2492 aClass = d.get(ext) 

2493 if aClass and aClass != scanner_class: 

2494 g.trace(f"{sfn}: duplicate {ext} class", aClass, scanner_class) 

2495 else: 

2496 d[ext] = scanner_class 

2497 elif sfn not in ('basewriter.py',): 

2498 g.warning(f"leo/plugins/writers/{sfn} has no writer_dict") 

2499 #@+node:ekr.20120219154958.10478: *5* LM.createGui 

2500 def createGui(self, pymacs): 

2501 lm = self 

2502 gui_option = lm.options.get('gui') 

2503 windowFlag = lm.options.get('windowFlag') 

2504 script = lm.options.get('script') 

2505 if g.app.gui: 

2506 if g.app.gui == g.app.nullGui: 

2507 g.app.gui = None # Enable g.app.createDefaultGui 

2508 g.app.createDefaultGui(__file__) 

2509 else: 

2510 pass 

2511 # This can happen when launching Leo from IPython. 

2512 # This can also happen when leoID does not exist. 

2513 elif gui_option is None: 

2514 if script and not windowFlag: 

2515 # Always use null gui for scripts. 

2516 g.app.createNullGuiWithScript(script) 

2517 else: 

2518 g.app.createDefaultGui(__file__) 

2519 else: 

2520 lm.createSpecialGui(gui_option, pymacs, script, windowFlag) 

2521 #@+node:ekr.20120219154958.10479: *5* LM.createSpecialGui 

2522 def createSpecialGui(self, gui, pymacs, script, windowFlag): 

2523 # lm = self 

2524 if pymacs: 

2525 g.app.createNullGuiWithScript(script=None) 

2526 elif script: 

2527 if windowFlag: 

2528 g.app.createDefaultGui() 

2529 g.app.gui.setScript(script=script) 

2530 sys.argv = [] # 2021/06/24: corrected by mypy. 

2531 else: 

2532 g.app.createNullGuiWithScript(script=script) 

2533 else: 

2534 g.app.createDefaultGui() 

2535 #@+node:ekr.20120219154958.10482: *5* LM.getDefaultFile 

2536 def getDefaultFile(self): 

2537 # Get the name of the workbook. 

2538 fn = g.app.config.getString('default-leo-file') 

2539 fn = g.os_path_finalize(fn) 

2540 if not fn: 

2541 return None 

2542 if g.os_path_exists(fn): 

2543 return fn 

2544 if g.os_path_isabs(fn): 

2545 # Create the file. 

2546 g.error(f"Using default leo file name:\n{fn}") 

2547 return fn 

2548 # It's too risky to open a default file if it is relative. 

2549 return None 

2550 #@+node:ekr.20120219154958.10484: *5* LM.initApp 

2551 def initApp(self, verbose): 

2552 

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

2554 self.createAllImporterData() 

2555 assert g.app.loadManager 

2556 from leo.core import leoBackground 

2557 from leo.core import leoConfig 

2558 from leo.core import leoNodes 

2559 from leo.core import leoPlugins 

2560 from leo.core import leoSessions 

2561 # Import leoIPython only if requested. The import is quite slow. 

2562 self.setStdStreams() 

2563 if g.app.useIpython: 

2564 # This launches the IPython Qt Console. It *is* required. 

2565 from leo.core import leoIPython 

2566 assert leoIPython # suppress pyflakes/flake8 warning. 

2567 # Make sure we call the new leoPlugins.init top-level function. 

2568 leoPlugins.init() 

2569 # Force the user to set g.app.leoID. 

2570 g.app.setLeoID(verbose=verbose) 

2571 # Create early classes *after* doing plugins.init() 

2572 g.app.idleTimeManager = IdleTimeManager() 

2573 g.app.backgroundProcessManager = leoBackground.BackgroundProcessManager() 

2574 g.app.externalFilesController = leoExternalFiles.ExternalFilesController() 

2575 g.app.recentFilesManager = RecentFilesManager() 

2576 g.app.config = leoConfig.GlobalConfigManager() 

2577 g.app.nodeIndices = leoNodes.NodeIndices(g.app.leoID) 

2578 g.app.sessionManager = leoSessions.SessionManager() 

2579 # Complete the plugins class last. 

2580 g.app.pluginsController.finishCreate() 

2581 #@+node:ekr.20210927034148.1: *5* LM.scanOptions & helpers 

2582 def scanOptions(self, fileName, pymacs): 

2583 """Handle all options, remove them from sys.argv and set lm.options.""" 

2584 lm = self 

2585 table = ( 

2586 '--dock', 

2587 '--global-docks', # #1643. use --use-docks instead. 

2588 '--init-docks', 

2589 '--no-cache', 

2590 '--no-dock', # #1171 and #1514: use --use-docks instead. 

2591 '--session-restore', 

2592 '--session-save', 

2593 '--use-docks', 

2594 ) 

2595 trace_m = textwrap.dedent("""\ 

2596 abbrev, beauty, cache, coloring, drawing, events, focus, git, gnx 

2597 importers, ipython, keys, layouts, plugins, save, select, sections, 

2598 shutdown, size, speed, startup, themes, undo, verbose, zoom 

2599 """) 

2600 for bad_option in table: 

2601 if bad_option in sys.argv: 

2602 sys.argv.remove(bad_option) 

2603 print(f"Ignoring the unused/deprecated {bad_option} option") 

2604 lm.old_argv = sys.argv[:] 

2605 # Automatically implements the --help option. 

2606 description = "usage: launchLeo.py [options] file1, file2, ..." 

2607 parser = argparse.ArgumentParser( 

2608 description=description, 

2609 formatter_class=argparse.RawTextHelpFormatter) 

2610 # 

2611 # Parse the options, and remove them from sys.argv. 

2612 self.addOptionsToParser(parser, trace_m) 

2613 args = parser.parse_args() 

2614 # Handle simple args... 

2615 self.doSimpleOptions(args, trace_m) 

2616 # Compute the lm.files ivar. 

2617 lm.files = lm.computeFilesList(fileName) 

2618 # Compute the script. Used twice below. 

2619 script = None if pymacs else self.doScriptOption(args, parser) 

2620 # Return the dictionary of options. 

2621 return { 

2622 'gui': lm.doGuiOption(args), 

2623 'load_type': lm.doLoadTypeOption(args), 

2624 'screenshot_fn': lm.doScreenShotOption(args), # --screen-shot=fn 

2625 'script': script, 

2626 'select': args.select and args.select.strip('"'), # --select=headline 

2627 'theme_path': args.theme, # --theme=name 

2628 'version': args.version, # --version: print the version and exit. 

2629 'windowFlag': script and args.script_window, 

2630 'windowSize': lm.doWindowSizeOption(args), 

2631 'windowSpot': lm.doWindowSpotOption(args), 

2632 } 

2633 #@+node:ekr.20210927034148.2: *6* LM.addOptionsToParser 

2634 #@@nobeautify 

2635 

2636 def addOptionsToParser(self, parser, trace_m): 

2637 """Init the argsparse parser.""" 

2638 add = parser.add_argument 

2639 add('PATHS', nargs='*', metavar='FILES', 

2640 help='list of files') 

2641 add('--diff', dest='diff', action='store_true', 

2642 help='use Leo as an external git diff') 

2643 add('--fail-fast', dest='fail_fast', action='store_true', 

2644 help='stop unit tests after the first failure') 

2645 add('--fullscreen', dest='fullscreen', action='store_true', 

2646 help='start fullscreen') 

2647 add('--ipython', dest='ipython', action='store_true', 

2648 help='enable ipython support') 

2649 add('--gui', dest='gui', metavar='GUI', 

2650 help='gui to use (qt/console/null)') 

2651 add('--listen-to-log', dest='listen_to_log', action='store_true', 

2652 help='start log_listener.py on startup') 

2653 add('--load-type', dest='load_type', metavar='TYPE', 

2654 help='@<file> type for non-outlines') 

2655 add('--maximized', dest='maximized', action='store_true', 

2656 help='start maximized') 

2657 add('--minimized', dest='minimized', action='store_true', 

2658 help='start minimized') 

2659 add('--no-plugins', dest='no_plugins', action='store_true', 

2660 help='disable all plugins') 

2661 add('--no-splash', dest='no_splash', action='store_true', 

2662 help='disable the splash screen') 

2663 add('--quit', dest='quit', action='store_true', 

2664 help='quit immediately after loading') 

2665 add('--screen-shot', dest='screen_shot', metavar='PATH', 

2666 help='take a screen shot and then exit') 

2667 add('--script', dest='script', metavar="PATH", 

2668 help='execute a script and then exit') 

2669 add('--script-window', dest='script_window', action='store_true', 

2670 help='execute script using default gui') 

2671 add('--select', dest='select', metavar='ID', 

2672 help='headline or gnx of node to select') 

2673 add('--silent', dest='silent', action='store_true', 

2674 help='disable all log messages') 

2675 add('--theme', dest='theme', metavar='NAME', 

2676 help='use the named theme file') 

2677 add('--trace', dest='trace', metavar='TRACE-KEY', 

2678 help=f"add one or more strings to g.app.debug. One or more of...\n{trace_m}") 

2679 add('--trace-binding', dest='trace_binding', metavar='KEY', 

2680 help='trace commands bound to a key') 

2681 add('--trace-setting', dest='trace_setting', metavar="NAME", 

2682 help='trace where named setting is set') 

2683 add('--window-size', dest='window_size', metavar='SIZE', 

2684 help='initial window size (height x width)') 

2685 add('--window-spot', dest='window_spot', metavar='SPOT', 

2686 help='initial window position (top x left)') 

2687 add('-v', '--version', dest='version', action='store_true', 

2688 help='print version number and exit') 

2689 #@+node:ekr.20210927034148.3: *6* LM.computeFilesList 

2690 def computeFilesList(self, fileName): 

2691 """Return the list of files on the command line.""" 

2692 lm = self 

2693 files = [] 

2694 if fileName: 

2695 files.append(fileName) 

2696 for arg in sys.argv[1:]: 

2697 if arg and not arg.startswith('-'): 

2698 files.append(arg) 

2699 result = [] 

2700 for z in files: 

2701 # Fix #245: wrong: result.extend(glob.glob(lm.completeFileName(z))) 

2702 aList = g.glob_glob(lm.completeFileName(z)) 

2703 if aList: 

2704 result.extend(aList) 

2705 else: 

2706 result.append(z) 

2707 return [g.os_path_normslashes(z) for z in result] 

2708 #@+node:ekr.20210927034148.4: *6* LM.doGuiOption 

2709 def doGuiOption(self, args): 

2710 gui = args.gui 

2711 if gui: 

2712 gui = gui.lower() 

2713 if gui in ('qt', 'qttabs'): 

2714 gui = 'qt' # Allow qttabs gui. 

2715 elif gui.startswith('browser'): 

2716 pass 

2717 elif gui in ('console', 'curses', 'text', 'null'): 

2718 pass 

2719 else: 

2720 print(f"scanOptions: unknown gui: {gui}. Using qt gui") 

2721 gui = 'qt' 

2722 else: 

2723 gui = 'qt' 

2724 assert gui 

2725 g.app.guiArgName = gui 

2726 return gui 

2727 #@+node:ekr.20210927034148.5: *6* LM.doLoadTypeOption 

2728 def doLoadTypeOption(self, args): 

2729 

2730 s = args.load_type 

2731 s = s.lower() if s else 'edit' 

2732 return '@' + s 

2733 #@+node:ekr.20210927034148.6: *6* LM.doScreenShotOption 

2734 def doScreenShotOption(self, args): 

2735 

2736 # --screen-shot=fn 

2737 s = args.screen_shot 

2738 if s: 

2739 s = s.strip('"') 

2740 return s 

2741 #@+node:ekr.20210927034148.7: *6* LM.doScriptOption 

2742 def doScriptOption(self, args, parser): 

2743 

2744 # --script 

2745 script = args.script 

2746 if script: 

2747 # #1090: use cwd, not g.app.loadDir, to find scripts. 

2748 fn = g.os_path_finalize_join(os.getcwd(), script) 

2749 script, e = g.readFileIntoString(fn, kind='script:', verbose=False) 

2750 if not script: 

2751 print(f"script not found: {fn}") 

2752 sys.exit(1) 

2753 else: 

2754 script = None 

2755 return script 

2756 #@+node:ekr.20210927034148.8: *6* LM.doSimpleOptions 

2757 def doSimpleOptions(self, args, trace_m): 

2758 """These args just set g.app ivars.""" 

2759 # --fail-fast 

2760 g.app.failFast = args.fail_fast 

2761 # --fullscreen 

2762 g.app.start_fullscreen = args.fullscreen 

2763 # --git-diff 

2764 g.app.diff = args.diff 

2765 # --listen-to-log 

2766 g.app.listen_to_log_flag = args.listen_to_log 

2767 # --ipython 

2768 g.app.useIpython = args.ipython 

2769 # --maximized 

2770 g.app.start_maximized = args.maximized 

2771 # --minimized 

2772 g.app.start_minimized = args.minimized 

2773 # --no-plugins 

2774 if args.no_plugins: 

2775 g.app.enablePlugins = False 

2776 # --no-splash: --minimized disables the splash screen 

2777 g.app.use_splash_screen = not args.no_splash and not args.minimized 

2778 # -- quit 

2779 g.app.quit_after_load = args.quit 

2780 # --silent 

2781 g.app.silentMode = args.silent 

2782 # --trace=... 

2783 valid = trace_m.replace(' ', '').replace('\n', '').split(',') 

2784 if args.trace: 

2785 ok = True 

2786 values = args.trace.lstrip('(').lstrip('[').rstrip(')').rstrip(']') 

2787 for val in values.split(','): 

2788 if val in valid: 

2789 g.app.debug.append(val) 

2790 else: 

2791 g.es_print(f"unknown --trace value: {val}") 

2792 ok = False 

2793 if not ok: 

2794 g.es_print('Valid --trace values are...') 

2795 for line in trace_m.split('\n'): 

2796 print(' ', line.rstrip()) 

2797 # 

2798 # These are not bool args. 

2799 # --trace-binding 

2800 g.app.trace_binding = args.trace_binding # g.app.config does not exist yet. 

2801 # 

2802 # --trace-setting=setting 

2803 g.app.trace_setting = args.trace_setting # g.app.config does not exist yet. 

2804 #@+node:ekr.20210927034148.9: *6* LM.doWindowSpotOption 

2805 def doWindowSpotOption(self, args): 

2806 

2807 # --window-spot 

2808 spot = args.window_spot 

2809 if spot: 

2810 try: 

2811 top, left = spot.split('x') 

2812 spot = int(top), int(left) 

2813 except ValueError: 

2814 print('scanOptions: bad --window-spot:', spot) 

2815 spot = None 

2816 

2817 return spot 

2818 #@+node:ekr.20210927034148.10: *6* LM.doWindowSizeOption 

2819 def doWindowSizeOption(self, args): 

2820 

2821 # --window-size 

2822 windowSize = args.window_size 

2823 if windowSize: 

2824 try: 

2825 h, w = windowSize.split('x') 

2826 windowSize = int(h), int(w) 

2827 except ValueError: 

2828 windowSize = None 

2829 print('scanOptions: bad --window-size:', windowSize) 

2830 return windowSize 

2831 #@+node:ekr.20160718072648.1: *5* LM.setStdStreams 

2832 def setStdStreams(self): 

2833 """ 

2834 Make sure that stdout and stderr exist. 

2835 This is an issue when running Leo with pythonw.exe. 

2836 """ 

2837 # Define class LeoStdOut 

2838 #@+others 

2839 #@+node:ekr.20160718091844.1: *6* class LeoStdOut 

2840 class LeoStdOut: 

2841 """A class to put stderr & stdout to Leo's log pane.""" 

2842 

2843 def __init__(self, kind): 

2844 self.kind = kind 

2845 g.es_print = self.write 

2846 g.pr = self.write 

2847 

2848 def flush(self, *args, **keys): 

2849 pass 

2850 #@+others 

2851 #@+node:ekr.20160718102306.1: *7* LeoStdOut.write 

2852 def write(self, *args, **keys): 

2853 """Put all non-keyword args to the log pane, as in g.es.""" 

2854 # 

2855 # Tracing will lead to unbounded recursion unless 

2856 # sys.stderr has been redirected on the command line. 

2857 app = g.app 

2858 if not app or app.killed: 

2859 return 

2860 if app.gui and app.gui.consoleOnly: 

2861 return 

2862 log = app.log 

2863 # Compute the effective args. 

2864 d = { 

2865 'color': None, 

2866 'commas': False, 

2867 'newline': True, 

2868 'spaces': True, 

2869 'tabName': 'Log', 

2870 } 

2871 # Handle keywords for g.pr and g.es_print. 

2872 d = g.doKeywordArgs(keys, d) 

2873 color: Any = d.get('color') 

2874 if color == 'suppress': 

2875 return 

2876 if log and color is None: 

2877 color = g.actualColor('black') 

2878 color = g.actualColor(color) 

2879 tabName = d.get('tabName') or 'Log' 

2880 newline = d.get('newline') 

2881 s = g.translateArgs(args, d) 

2882 if app.batchMode: 

2883 if log: 

2884 log.put(s) 

2885 elif log and app.logInited: 

2886 # from_redirect is the big difference between this and g.es. 

2887 log.put(s, color=color, tabName=tabName, from_redirect=True) 

2888 else: 

2889 app.logWaiting.append((s, color, newline),) 

2890 #@-others 

2891 #@-others 

2892 if not sys.stdout: 

2893 sys.stdout = sys.__stdout__ = LeoStdOut('stdout') # type:ignore 

2894 if not sys.stderr: 

2895 sys.stderr = sys.__stderr__ = LeoStdOut('stderr') # type:ignore 

2896 #@+node:ekr.20120219154958.10491: *4* LM.isValidPython 

2897 def isValidPython(self): 

2898 if sys.platform == 'cli': 

2899 return True 

2900 message = ( 

2901 f"Leo requires Python {g.minimum_python_version} or higher" 

2902 f"You may download Python from http://python.org/download/") 

2903 try: 

2904 version = '.'.join([str(sys.version_info[i]) for i in (0, 1, 2)]) 

2905 ok = g.CheckVersion(version, g.minimum_python_version) 

2906 if not ok: 

2907 print(message) 

2908 try: 

2909 # g.app.gui does not exist yet. 

2910 d = g.EmergencyDialog( 

2911 title='Python Version Error', 

2912 message=message) 

2913 d.run() 

2914 except Exception: 

2915 g.es_exception() 

2916 return ok 

2917 except Exception: 

2918 print("isValidPython: unexpected exception: g.CheckVersion") 

2919 traceback.print_exc() 

2920 return 0 

2921 #@+node:ekr.20120223062418.10393: *4* LM.loadLocalFile & helpers 

2922 def loadLocalFile(self, fn, gui, old_c): 

2923 """Completely read a file, creating the corresonding outline. 

2924 

2925 1. If fn is an existing .leo, .db or .leojs file, read it twice: 

2926 the first time with a NullGui to discover settings, 

2927 the second time with the requested gui to create the outline. 

2928 

2929 2. If fn is an external file: 

2930 get settings from the leoSettings.leo and myLeoSetting.leo, then 

2931 create a "wrapper" outline continain an @file node for the external file. 

2932 

2933 3. If fn is empty: 

2934 get settings from the leoSettings.leo and myLeoSetting.leo or default settings, 

2935 or open an empty outline. 

2936 """ 

2937 lm = self 

2938 # #2489: If fn is empty, open an empty, untitled .leo file. 

2939 if not fn: 

2940 return lm.openEmptyLeoFile(gui, old_c) 

2941 # Return the commander if the file is an already open outline. 

2942 fn = g.os_path_finalize(fn) # #2489. 

2943 c = lm.findOpenFile(fn) 

2944 if c: 

2945 return c 

2946 # Open the file, creating a wrapper .leo file if necessary. 

2947 previousSettings = lm.getPreviousSettings(fn) 

2948 c = lm.openFileByName(fn, gui, old_c, previousSettings) 

2949 return c 

2950 #@+node:ekr.20220318033804.1: *5* LM.openEmptyLeoFile 

2951 def openEmptyLeoFile(self, gui, old_c): 

2952 """Open an empty, untitled, new Leo file.""" 

2953 lm = self 

2954 # Disable the log. 

2955 g.app.setLog(None) 

2956 g.app.lockLog() 

2957 # Create the commander for the .leo file. 

2958 c = g.app.newCommander( 

2959 fileName=None, 

2960 gui=gui, 

2961 previousSettings=lm.getPreviousSettings(None), 

2962 ) 

2963 g.doHook('open0') 

2964 # Enable the log. 

2965 g.app.unlockLog() 

2966 c.frame.log.enable(True) 

2967 g.doHook("open1", old_c=old_c, c=c, new_c=c, fileName=None) 

2968 # Init the frame. 

2969 c.frame.setInitialWindowGeometry() 

2970 c.frame.deiconify() 

2971 c.frame.lift() 

2972 c.frame.splitVerticalFlag, r1, r2 = c.frame.initialRatios() 

2973 c.frame.resizePanesToRatio(r1, r2) 

2974 c.mFileName = None 

2975 c.wrappedFileName = None 

2976 # Fix a buglet: Don't call c.computeWindowTitle here. 

2977 c.frame.setTitle(c.frame.title) 

2978 # Late inits. Order matters. 

2979 if c.config.getBool('use-chapters') and c.chapterController: 

2980 c.chapterController.finishCreate() 

2981 c.clearChanged() 

2982 g.doHook("open2", old_c=old_c, c=c, new_c=c, fileName=None) 

2983 g.doHook("new", old_c=old_c, c=c, new_c=c) 

2984 g.app.writeWaitingLog(c) 

2985 c.setLog() 

2986 lm.createMenu(c) 

2987 lm.finishOpen(c) 

2988 return c 

2989 #@+node:ekr.20120223062418.10394: *5* LM.openFileByName & helpers 

2990 def openFileByName(self, fn, gui, old_c, previousSettings): 

2991 """ 

2992 Create an outline (Commander) for either: 

2993 - a Leo file (including .leo or zipped file), 

2994 - an external file. 

2995 

2996 Note: The settings don't matter for pre-reads! 

2997 For second read, the settings for the file are *exactly* previousSettings. 

2998 """ 

2999 lm = self 

3000 # Disable the log. 

3001 g.app.setLog(None) 

3002 g.app.lockLog() 

3003 # Create the a commander for the .leo file. 

3004 c = g.app.newCommander( 

3005 fileName=fn, 

3006 gui=gui, 

3007 previousSettings=previousSettings, 

3008 ) 

3009 g.doHook('open0') 

3010 # Open the file before the open1 hook. 

3011 theFile = lm.openAnyLeoFile(fn) 

3012 if isinstance(theFile, sqlite3.Connection): 

3013 # This commander is associated with sqlite db. 

3014 c.sqlite_connection = theFile 

3015 # Enable the log and do the open1 hook. 

3016 g.app.unlockLog() 

3017 c.frame.log.enable(True) 

3018 # Create the outline. 

3019 g.doHook("open1", old_c=None, c=c, new_c=c, fileName=fn) 

3020 if theFile: 

3021 # Some kind of existing Leo file. 

3022 readAtFileNodesFlag = bool(previousSettings) 

3023 # Read the Leo file. 

3024 ok = lm.readOpenedLeoFile(c, fn, readAtFileNodesFlag, theFile) 

3025 if not ok: 

3026 return None 

3027 else: 

3028 # Not a any kind of Leo file. Create a wrapper .leo file. 

3029 c = lm.initWrapperLeoFile(c, fn) # #2489 

3030 g.doHook("new", old_c=old_c, c=c, new_c=c) # #2489. 

3031 g.doHook("open2", old_c=old_c, c=c, new_c=c, fileName=fn) 

3032 # Complete the inits. 

3033 g.app.writeWaitingLog(c) 

3034 c.setLog() 

3035 lm.createMenu(c, fn) 

3036 lm.finishOpen(c) 

3037 return c 

3038 #@+node:ekr.20120223062418.10405: *6* LM.createMenu 

3039 def createMenu(self, c, fn=None): 

3040 # lm = self 

3041 # Create the menu as late as possible so it can use user commands. 

3042 if not g.doHook("menu1", c=c, p=c.p, v=c.p): 

3043 c.frame.menu.createMenuBar(c.frame) 

3044 g.app.recentFilesManager.updateRecentFiles(fn) 

3045 g.doHook("menu2", c=c, p=c.p, v=c.p) 

3046 g.doHook("after-create-leo-frame", c=c) 

3047 g.doHook("after-create-leo-frame2", c=c) 

3048 # Fix bug 844953: tell Unity which menu to use. 

3049 # c.enableMenuBar() 

3050 #@+node:ekr.20120223062418.10406: *6* LM.findOpenFile 

3051 def findOpenFile(self, fn): 

3052 

3053 def munge(name): 

3054 return g.os_path_normpath(name or '').lower() 

3055 

3056 for frame in g.app.windowList: 

3057 c = frame.c 

3058 if g.os_path_realpath(munge(fn)) == g.os_path_realpath(munge(c.mFileName)): 

3059 # Don't call frame.bringToFront(), it breaks --minimize 

3060 c.setLog() 

3061 # Selecting the new tab ensures focus is set. 

3062 master = getattr(frame.top, 'leo_master', None) 

3063 if master: # master is a TabbedTopLevel. 

3064 master.select(frame.c) 

3065 c.outerUpdate() 

3066 return c 

3067 return None 

3068 #@+node:ekr.20120223062418.10407: *6* LM.finishOpen 

3069 def finishOpen(self, c): 

3070 # lm = self 

3071 k = c.k 

3072 assert k 

3073 # New in Leo 4.6: provide an official way for very late initialization. 

3074 c.frame.tree.initAfterLoad() 

3075 c.initAfterLoad() 

3076 # chapterController.finishCreate must be called after the first real redraw 

3077 # because it requires a valid value for c.rootPosition(). 

3078 if c.chapterController: 

3079 c.chapterController.finishCreate() 

3080 if k: 

3081 k.setDefaultInputState() 

3082 c.initialFocusHelper() 

3083 if k: 

3084 k.showStateAndMode() 

3085 c.frame.initCompleteHint() 

3086 c.outerUpdate() # #181: Honor focus requests. 

3087 #@+node:ekr.20120223062418.10408: *6* LM.initWrapperLeoFile 

3088 def initWrapperLeoFile(self, c, fn): 

3089 """ 

3090 Create an empty file if the external fn is empty. 

3091 

3092 Otherwise, create an @edit or @file node for the external file. 

3093 """ 

3094 # lm = self 

3095 # Use the config params to set the size and location of the window. 

3096 frame = c.frame 

3097 frame.setInitialWindowGeometry() 

3098 frame.deiconify() 

3099 frame.lift() 

3100 # #1570: Resize the _new_ frame. 

3101 frame.splitVerticalFlag, r1, r2 = frame.initialRatios() 

3102 frame.resizePanesToRatio(r1, r2) 

3103 if not g.os_path_exists(fn): 

3104 p = c.rootPosition() 

3105 # Create an empty @edit node unless fn is an .leo file. 

3106 # Fix #1070: Use "newHeadline", not fn. 

3107 p.h = "newHeadline" if fn.endswith('.leo') else f"@edit {fn}" 

3108 c.selectPosition(p) 

3109 elif c.looksLikeDerivedFile(fn): 

3110 # 2011/10/10: Create an @file node. 

3111 p = c.importCommands.importDerivedFiles(parent=c.rootPosition(), 

3112 paths=[fn], command=None) # Not undoable. 

3113 if p and p.hasBack(): 

3114 p.back().doDelete() 

3115 p = c.rootPosition() 

3116 if not p: 

3117 return None 

3118 else: 

3119 # Create an @<file> node. 

3120 p = c.rootPosition() 

3121 if p: 

3122 # The 'load_type' key may not exist when run from the bridge. 

3123 load_type = self.options.get('load_type', '@edit') # #2489. 

3124 p.setHeadString(f"{load_type} {fn}") 

3125 c.refreshFromDisk() 

3126 c.selectPosition(p) 

3127 # Fix critical bug 1184855: data loss with command line 'leo somefile.ext' 

3128 # Fix smallish bug 1226816 Command line "leo xxx.leo" creates file xxx.leo.leo. 

3129 c.mFileName = fn if fn.endswith('.leo') else f"{fn}.leo" 

3130 c.wrappedFileName = fn 

3131 c.frame.title = c.computeWindowTitle(c.mFileName) 

3132 c.frame.setTitle(c.frame.title) 

3133 if c.config.getBool('use-chapters') and c.chapterController: 

3134 c.chapterController.finishCreate() 

3135 frame.c.clearChanged() 

3136 return c 

3137 #@+node:ekr.20120223062418.10419: *6* LM.isLeoFile & LM.isZippedFile 

3138 def isLeoFile(self, fn): 

3139 """ 

3140 Return True if fn is any kind of Leo file, 

3141 including a zipped file or .leo, .db, or .leojs file. 

3142 """ 

3143 if not fn: 

3144 return False 

3145 return zipfile.is_zipfile(fn) or fn.endswith(('.leo', 'db', '.leojs')) 

3146 

3147 def isZippedFile(self, fn): 

3148 """Return True if fn is a zipped file.""" 

3149 return fn and zipfile.is_zipfile(fn) 

3150 #@+node:ekr.20120224161905.10030: *6* LM.openAnyLeoFile 

3151 def openAnyLeoFile(self, fn): 

3152 """Open a .leo, .leojs or .db file.""" 

3153 lm = self 

3154 if fn.endswith('.db'): 

3155 return sqlite3.connect(fn) 

3156 if lm.isLeoFile(fn) and g.os_path_exists(fn): 

3157 if lm.isZippedFile(fn): 

3158 theFile = lm.openZipFile(fn) 

3159 else: 

3160 theFile = lm.openLeoFile(fn) 

3161 else: 

3162 theFile = None 

3163 return theFile 

3164 #@+node:ekr.20120223062418.10416: *6* LM.openLeoFile 

3165 def openLeoFile(self, fn): 

3166 """Open the file for reading.""" 

3167 try: 

3168 theFile = open(fn, 'rb') 

3169 return theFile 

3170 except IOError: 

3171 # Do not use string + here: it will fail for non-ascii strings! 

3172 if not g.unitTesting: 

3173 g.error("can not open:", fn) 

3174 return None 

3175 #@+node:ekr.20120223062418.10410: *6* LM.openZipFile 

3176 def openZipFile(self, fn): 

3177 """ 

3178 Open a zipped file for reading. 

3179 Return a StringIO file if successful. 

3180 """ 

3181 try: 

3182 theFile = zipfile.ZipFile(fn, 'r') 

3183 if not theFile: 

3184 return None 

3185 # Read the file into an StringIO file. 

3186 aList = theFile.namelist() 

3187 name = aList and len(aList) == 1 and aList[0] 

3188 if not name: 

3189 return None 

3190 s = theFile.read(name) 

3191 s2 = g.toUnicode(s, 'utf-8') 

3192 return StringIO(s2) 

3193 except IOError: 

3194 # Do not use string + here: it will fail for non-ascii strings! 

3195 if not g.unitTesting: 

3196 g.error("can not open:", fn) 

3197 return None 

3198 #@+node:ekr.20120223062418.10412: *6* LM.readOpenedLeoFile 

3199 def readOpenedLeoFile(self, c, fn, readAtFileNodesFlag, theFile): 

3200 """ 

3201 Call c.fileCommands.openLeoFile to open some kind of Leo file. 

3202 

3203 the_file: An open file, which is a StringIO file for zipped files. 

3204 

3205 Note: g.app.log is not inited here. 

3206 """ 

3207 # New in Leo 4.10: The open1 event does not allow an override of the init logic. 

3208 assert theFile 

3209 # Read and close the file. 

3210 ok = c.fileCommands.openLeoFile( 

3211 theFile, fn, readAtFileNodesFlag=readAtFileNodesFlag) 

3212 if ok: 

3213 if not c.openDirectory: 

3214 theDir = g.os_path_finalize(g.os_path_dirname(fn)) # 1341 

3215 c.openDirectory = c.frame.openDirectory = theDir 

3216 else: 

3217 # #970: Never close Leo here. 

3218 g.app.closeLeoWindow(c.frame, finish_quit=False) 

3219 return ok 

3220 #@+node:ekr.20160430063406.1: *3* LM.revertCommander 

3221 def revertCommander(self, c): 

3222 """Revert c to the previously saved contents.""" 

3223 lm = self 

3224 fn = c.mFileName 

3225 # Re-read the file. 

3226 theFile = lm.openAnyLeoFile(fn) 

3227 if theFile: 

3228 c.fileCommands.initIvars() 

3229 # Closes the file. 

3230 c.fileCommands.getLeoFile(theFile, fn, checkOpenFiles=False) 

3231 #@-others 

3232#@+node:ekr.20120223062418.10420: ** class PreviousSettings 

3233class PreviousSettings: 

3234 """ 

3235 A class holding the settings and shortcuts dictionaries 

3236 that are computed in the first pass when loading local 

3237 files and passed to the second pass. 

3238 """ 

3239 

3240 def __init__(self, settingsDict, shortcutsDict): 

3241 if not shortcutsDict or not settingsDict: # #1766: unit tests. 

3242 lm = g.app.loadManager 

3243 settingsDict, shortcutsDict = lm.createDefaultSettingsDicts() 

3244 self.settingsDict = settingsDict 

3245 self.shortcutsDict = shortcutsDict 

3246 

3247 def __repr__(self): 

3248 return ( 

3249 f"<PreviousSettings\n" 

3250 f"{self.settingsDict}\n" 

3251 f"{self.shortcutsDict}\n>") 

3252 

3253 __str__ = __repr__ 

3254#@+node:ekr.20120225072226.10283: ** class RecentFilesManager 

3255class RecentFilesManager: 

3256 """A class to manipulate leoRecentFiles.txt.""" 

3257 

3258 def __init__(self): 

3259 

3260 self.edit_headline = 'Recent files. Do not change this headline!' 

3261 self.groupedMenus = [] # Set in rf.createRecentFilesMenuItems. 

3262 self.recentFiles = [] # List of g.Bunches describing .leoRecentFiles.txt files. 

3263 self.recentFilesMenuName = 'Recent Files' # May be changed later. 

3264 self.recentFileMessageWritten = False # To suppress all but the first message. 

3265 self.write_recent_files_as_needed = False # Will be set later. 

3266 

3267 #@+others 

3268 #@+node:ekr.20041201080436: *3* rf.appendToRecentFiles 

3269 def appendToRecentFiles(self, files): 

3270 rf = self 

3271 files = [theFile.strip() for theFile in files] 

3272 

3273 def munge(name): 

3274 return g.os_path_normpath(name or '').lower() 

3275 

3276 for name in files: 

3277 # Remove all variants of name. 

3278 for name2 in rf.recentFiles[:]: 

3279 if munge(name) == munge(name2): 

3280 rf.recentFiles.remove(name2) 

3281 rf.recentFiles.append(name) 

3282 #@+node:ekr.20120225072226.10289: *3* rf.cleanRecentFiles 

3283 def cleanRecentFiles(self, c): 

3284 """ 

3285 Remove items from the recent files list that no longer exist. 

3286 

3287 This almost never does anything because Leo's startup logic removes 

3288 nonexistent files from the recent files list. 

3289 """ 

3290 result = [z for z in self.recentFiles if g.os_path_exists(z)] 

3291 if result != self.recentFiles: 

3292 for path in result: 

3293 self.updateRecentFiles(path) 

3294 self.writeRecentFilesFile(c) 

3295 #@+node:ekr.20180212141017.1: *3* rf.demangleRecentFiles 

3296 def demangleRecentFiles(self, c, data): 

3297 """Rewrite recent files based on c.config.getData('path-demangle')""" 

3298 changes = [] 

3299 replace = None 

3300 for line in data: 

3301 text = line.strip() 

3302 if text.startswith('REPLACE: '): 

3303 replace = text.split(None, 1)[1].strip() 

3304 if text.startswith('WITH:') and replace is not None: 

3305 with_ = text[5:].strip() 

3306 changes.append((replace, with_)) 

3307 g.es(f"{replace} -> {with_}") 

3308 orig = [z for z in self.recentFiles if z.startswith("/")] 

3309 self.recentFiles = [] 

3310 for i in orig: 

3311 t = i 

3312 for change in changes: 

3313 t = t.replace(*change) 

3314 self.updateRecentFiles(t) 

3315 self.writeRecentFilesFile(c) 

3316 #@+node:ekr.20120225072226.10297: *3* rf.clearRecentFiles 

3317 def clearRecentFiles(self, c): 

3318 """Clear the recent files list, then add the present file.""" 

3319 rf = self 

3320 menu, u = c.frame.menu, c.undoer 

3321 bunch = u.beforeClearRecentFiles() 

3322 recentFilesMenu = menu.getMenu(self.recentFilesMenuName) 

3323 menu.deleteRecentFilesMenuItems(recentFilesMenu) 

3324 rf.recentFiles = [c.fileName()] 

3325 for frame in g.app.windowList: 

3326 rf.createRecentFilesMenuItems(frame.c) 

3327 u.afterClearRecentFiles(bunch) 

3328 # Write the file immediately. 

3329 rf.writeRecentFilesFile(c) 

3330 #@+node:ekr.20120225072226.10301: *3* rf.createRecentFilesMenuItems 

3331 def createRecentFilesMenuItems(self, c): 

3332 rf = self 

3333 menu = c.frame.menu 

3334 recentFilesMenu = menu.getMenu(self.recentFilesMenuName) 

3335 if not recentFilesMenu: 

3336 return 

3337 # Delete all previous entries. 

3338 menu.deleteRecentFilesMenuItems(recentFilesMenu) 

3339 # Create the permanent (static) menu entries. 

3340 table = rf.getRecentFilesTable() 

3341 menu.createMenuEntries(recentFilesMenu, table) 

3342 # Create all the other entries (a maximum of 36). 

3343 accel_ch = string.digits + string.ascii_uppercase # Not a unicode problem. 

3344 i = 0 

3345 n = len(accel_ch) 

3346 # see if we're grouping when files occur in more than one place 

3347 rf_group = c.config.getBool("recent-files-group") 

3348 rf_always = c.config.getBool("recent-files-group-always") 

3349 groupedEntries = rf_group or rf_always 

3350 if groupedEntries: # if so, make dict of groups 

3351 dirCount: Dict[str, Any] = {} 

3352 for fileName in rf.getRecentFiles()[:n]: 

3353 dirName, baseName = g.os_path_split(fileName) 

3354 if baseName not in dirCount: 

3355 dirCount[baseName] = {'dirs': [], 'entry': None} 

3356 dirCount[baseName]['dirs'].append(dirName) 

3357 for name in rf.getRecentFiles()[:n]: 

3358 # pylint: disable=cell-var-from-loop 

3359 if name.strip() == "": 

3360 continue # happens with empty list/new file 

3361 

3362 def recentFilesCallback(event=None, c=c, name=name): 

3363 c.openRecentFile(fn=name) 

3364 

3365 if groupedEntries: 

3366 dirName, baseName = g.os_path_split(name) 

3367 entry = dirCount[baseName] 

3368 if len(entry['dirs']) > 1 or rf_always: # sub menus 

3369 if entry['entry'] is None: 

3370 entry['entry'] = menu.createNewMenu(baseName, "Recent Files...") 

3371 # acts as a flag for the need to create the menu 

3372 c.add_command(menu.getMenu(baseName), label=dirName, 

3373 command=recentFilesCallback, underline=0) 

3374 else: # single occurence, no submenu 

3375 c.add_command(recentFilesMenu, label=baseName, 

3376 command=recentFilesCallback, underline=0) 

3377 else: # original behavior 

3378 label = f"{accel_ch[i]} {g.computeWindowTitle(name)}" 

3379 c.add_command(recentFilesMenu, label=label, 

3380 command=recentFilesCallback, underline=0) 

3381 i += 1 

3382 if groupedEntries: # store so we can delete them later 

3383 rf.groupedMenus = [z for z in dirCount 

3384 if dirCount[z]['entry'] is not None] 

3385 #@+node:vitalije.20170703115609.1: *3* rf.editRecentFiles 

3386 def editRecentFiles(self, c): 

3387 """ 

3388 Dump recentFiles into new node appended as lastTopLevel, selects it and 

3389 request focus in body. 

3390 

3391 NOTE: command write-edited-recent-files assume that headline of this 

3392 node is not changed by user. 

3393 """ 

3394 rf = self 

3395 p1 = c.lastTopLevel().insertAfter() 

3396 p1.h = self.edit_headline 

3397 p1.b = '\n'.join(rf.recentFiles) 

3398 c.redraw() 

3399 c.selectPosition(p1) 

3400 c.redraw() 

3401 c.bodyWantsFocusNow() 

3402 g.es('edit list and run write-rff to save recentFiles') 

3403 #@+node:ekr.20120225072226.10286: *3* rf.getRecentFiles 

3404 def getRecentFiles(self): 

3405 # Fix #299: Leo loads a deleted file. 

3406 self.recentFiles = [z for z in self.recentFiles 

3407 if g.os_path_exists(z)] 

3408 return self.recentFiles 

3409 #@+node:ekr.20120225072226.10304: *3* rf.getRecentFilesTable 

3410 def getRecentFilesTable(self): 

3411 return ( 

3412 "*clear-recent-files", 

3413 "*clean-recent-files", 

3414 "*demangle-recent-files", 

3415 "*sort-recent-files", 

3416 ("-", None, None), 

3417 ) 

3418 #@+node:ekr.20070224115832: *3* rf.readRecentFiles & helpers 

3419 def readRecentFiles(self, localConfigFile): 

3420 """Read all .leoRecentFiles.txt files.""" 

3421 # The order of files in this list affects the order of the recent files list. 

3422 rf = self 

3423 seen = [] 

3424 localConfigPath = g.os_path_dirname(localConfigFile) 

3425 for path in (g.app.homeLeoDir, g.app.globalConfigDir, localConfigPath): 

3426 if path: 

3427 path = g.os_path_realpath(g.os_path_finalize(path)) 

3428 if path and path not in seen: 

3429 ok = rf.readRecentFilesFile(path) 

3430 if ok: 

3431 seen.append(path) 

3432 if not seen and rf.write_recent_files_as_needed: 

3433 rf.createRecentFiles() 

3434 #@+node:ekr.20061010121944: *4* rf.createRecentFiles 

3435 def createRecentFiles(self): 

3436 """ 

3437 Try to create .leoRecentFiles.txt, in the users home directory, or in 

3438 Leo's config directory if that fails. 

3439 """ 

3440 for theDir in (g.app.homeLeoDir, g.app.globalConfigDir): 

3441 if theDir: 

3442 fn = g.os_path_join(theDir, '.leoRecentFiles.txt') 

3443 try: 

3444 with open(fn, 'w'): 

3445 g.red('created', fn) 

3446 return 

3447 except IOError: 

3448 g.error('can not create', fn) 

3449 g.es_exception() 

3450 #@+node:ekr.20050424115658: *4* rf.readRecentFilesFile 

3451 def readRecentFilesFile(self, path): 

3452 

3453 fileName = g.os_path_join(path, '.leoRecentFiles.txt') 

3454 if not g.os_path_exists(fileName): 

3455 return False 

3456 try: 

3457 with io.open(fileName, encoding='utf-8', mode='r') as f: 

3458 try: # Fix #471. 

3459 lines = f.readlines() 

3460 except Exception: 

3461 lines = None 

3462 except IOError: 

3463 # The file exists, so FileNotFoundError is not possible. 

3464 g.trace('can not open', fileName) 

3465 return False 

3466 if lines and self.sanitize(lines[0]) == 'readonly': 

3467 lines = lines[1:] 

3468 if lines: 

3469 lines = [g.toUnicode(g.os_path_normpath(line)) for line in lines] 

3470 self.appendToRecentFiles(lines) 

3471 return True 

3472 #@+node:ekr.20120225072226.10285: *3* rf.sanitize 

3473 def sanitize(self, name): 

3474 """Return a sanitized file name.""" 

3475 if name is None: 

3476 return None 

3477 name = name.lower() 

3478 for ch in ('-', '_', ' ', '\n'): 

3479 name = name.replace(ch, '') 

3480 return name or None 

3481 #@+node:ekr.20120215072959.12478: *3* rf.setRecentFiles 

3482 def setRecentFiles(self, files): 

3483 """Update the recent files list.""" 

3484 rf = self 

3485 rf.appendToRecentFiles(files) 

3486 #@+node:ekr.20120225072226.10293: *3* rf.sortRecentFiles 

3487 def sortRecentFiles(self, c): 

3488 """Sort the recent files list.""" 

3489 rf = self 

3490 

3491 def key(path): 

3492 # Sort only the base name. That's what will appear in the menu. 

3493 s = g.os_path_basename(path) 

3494 return s.lower() if sys.platform.lower().startswith('win') else s 

3495 

3496 aList = sorted(rf.recentFiles, key=key) 

3497 rf.recentFiles = [] 

3498 for z in reversed(aList): 

3499 rf.updateRecentFiles(z) 

3500 rf.writeRecentFilesFile(c) 

3501 #@+node:ekr.20031218072017.2083: *3* rf.updateRecentFiles 

3502 def updateRecentFiles(self, fileName): 

3503 """Create the RecentFiles menu. May be called with Null fileName.""" 

3504 rf = self 

3505 if g.unitTesting: 

3506 return 

3507 

3508 def munge(name): 

3509 return g.os_path_finalize(name or '').lower() 

3510 

3511 def munge2(name): 

3512 return g.os_path_finalize_join(g.app.loadDir, name or '') 

3513 

3514 # Update the recent files list in all windows. 

3515 

3516 if fileName: 

3517 for frame in g.app.windowList: 

3518 # Remove all versions of the file name. 

3519 for name in rf.recentFiles: 

3520 if ( 

3521 munge(fileName) == munge(name) or 

3522 munge2(fileName) == munge2(name) 

3523 ): 

3524 rf.recentFiles.remove(name) 

3525 rf.recentFiles.insert(0, fileName) 

3526 # Recreate the Recent Files menu. 

3527 rf.createRecentFilesMenuItems(frame.c) 

3528 else: 

3529 for frame in g.app.windowList: 

3530 rf.createRecentFilesMenuItems(frame.c) 

3531 #@+node:vitalije.20170703115616.1: *3* rf.writeEditedRecentFiles 

3532 def writeEditedRecentFiles(self, c): 

3533 """ 

3534 Write content of "edit_headline" node as recentFiles and recreates 

3535 menues. 

3536 """ 

3537 p, rf = c.p, self 

3538 p = g.findNodeAnywhere(c, self.edit_headline) 

3539 if p: 

3540 files = [z for z in p.b.splitlines() if z and g.os_path_exists(z)] 

3541 rf.recentFiles = files 

3542 rf.writeRecentFilesFile(c) 

3543 rf.updateRecentFiles(None) 

3544 c.selectPosition(p) 

3545 c.deleteOutline() 

3546 else: 

3547 g.red('not found:', self.edit_headline) 

3548 #@+node:ekr.20050424114937.2: *3* rf.writeRecentFilesFile & helper 

3549 def writeRecentFilesFile(self, c): 

3550 """Write the appropriate .leoRecentFiles.txt file.""" 

3551 tag = '.leoRecentFiles.txt' 

3552 rf = self 

3553 # tag:#661. Do nothing if in leoBride. 

3554 if g.unitTesting or g.app.inBridge: 

3555 return 

3556 localFileName = c.fileName() 

3557 if localFileName: 

3558 localPath, junk = g.os_path_split(localFileName) 

3559 else: 

3560 localPath = None 

3561 written = False 

3562 seen = [] 

3563 for path in (localPath, g.app.globalConfigDir, g.app.homeLeoDir): 

3564 if path: 

3565 fileName = g.os_path_join(path, tag) 

3566 if g.os_path_exists(fileName) and fileName.lower() not in seen: 

3567 seen.append(fileName.lower()) 

3568 ok = rf.writeRecentFilesFileHelper(fileName) 

3569 if ok: 

3570 written = True 

3571 if not rf.recentFileMessageWritten and not g.unitTesting and not g.app.silentMode: # #459: 

3572 if ok: 

3573 g.es_print(f"wrote recent file: {fileName}") 

3574 else: 

3575 g.error(f"failed to write recent file: {fileName}") 

3576 if written: 

3577 rf.recentFileMessageWritten = True 

3578 else: 

3579 # Attempt to create .leoRecentFiles.txt in the user's home directory. 

3580 if g.app.homeLeoDir: 

3581 fileName = g.os_path_finalize_join(g.app.homeLeoDir, tag) 

3582 if not g.os_path_exists(fileName): 

3583 g.red(f"creating: {fileName}") 

3584 rf.writeRecentFilesFileHelper(fileName) 

3585 #@+node:ekr.20050424131051: *4* rf.writeRecentFilesFileHelper 

3586 def writeRecentFilesFileHelper(self, fileName): 

3587 # Don't update the file if it begins with read-only. 

3588 # 

3589 # Part 1: Return False if the first line is "readonly". 

3590 # It's ok if the file doesn't exist. 

3591 if g.os_path_exists(fileName): 

3592 with io.open(fileName, encoding='utf-8', mode='r') as f: 

3593 try: 

3594 # Fix #471. 

3595 lines = f.readlines() 

3596 except Exception: 

3597 lines = None 

3598 if lines and self.sanitize(lines[0]) == 'readonly': 

3599 return False 

3600 # Part 2: write the files. 

3601 try: 

3602 with io.open(fileName, encoding='utf-8', mode='w') as f: 

3603 s = '\n'.join(self.recentFiles) if self.recentFiles else '\n' 

3604 f.write(g.toUnicode(s)) 

3605 return True 

3606 except IOError: 

3607 g.error('error writing', fileName) 

3608 g.es_exception() 

3609 except Exception: 

3610 g.error('unexpected exception writing', fileName) 

3611 g.es_exception() 

3612 if g.unitTesting: 

3613 raise 

3614 return False 

3615 #@-others 

3616#@+node:ekr.20150514125218.1: ** Top-level-commands 

3617#@+node:ekr.20150514125218.2: *3* ctrl-click-at-cursor 

3618@g.command('ctrl-click-at-cursor') 

3619def ctrlClickAtCursor(event): 

3620 """Simulate a control-click at the cursor.""" 

3621 c = event.get('c') 

3622 if c: 

3623 g.openUrlOnClick(event) 

3624#@+node:ekr.20180213045148.1: *3* demangle-recent-files 

3625@g.command('demangle-recent-files') 

3626def demangle_recent_files_command(event): 

3627 """ 

3628 Path demangling potentially alters the paths in the recent files list 

3629 according to find/replace patterns in the @data path-demangle setting. 

3630 For example: 

3631 

3632 REPLACE: .gnome-desktop 

3633 WITH: My Desktop 

3634 

3635 The default setting specifies no patterns. 

3636 """ 

3637 c = event and event.get('c') 

3638 if c: 

3639 data = c.config.getData('path-demangle') 

3640 if data: 

3641 g.app.recentFilesManager.demangleRecentFiles(c, data) 

3642 else: 

3643 g.es_print('No patterns in @data path-demangle') 

3644#@+node:ekr.20150514125218.3: *3* enable/disable/toggle-idle-time-events 

3645@g.command('disable-idle-time-events') 

3646def disable_idle_time_events(event): 

3647 """Disable default idle-time event handling.""" 

3648 g.app.idle_time_hooks_enabled = False 

3649 

3650@g.command('enable-idle-time-events') 

3651def enable_idle_time_events(event): 

3652 """Enable default idle-time event handling.""" 

3653 g.app.idle_time_hooks_enabled = True 

3654 

3655@g.command('toggle-idle-time-events') 

3656def toggle_idle_time_events(event): 

3657 """Toggle default idle-time event handling.""" 

3658 g.app.idle_time_hooks_enabled = not g.app.idle_time_hooks_enabled 

3659#@+node:ekr.20150514125218.4: *3* join-leo-irc 

3660@g.command('join-leo-irc') 

3661def join_leo_irc(event=None): 

3662 """Open the web page to Leo's irc channel on freenode.net.""" 

3663 import webbrowser 

3664 webbrowser.open("http://webchat.freenode.net/?channels=%23leo&uio=d4") 

3665#@+node:ekr.20150514125218.5: *3* open-url 

3666@g.command('open-url') 

3667def openUrl(event=None): 

3668 """ 

3669 Open the url in the headline or body text of the selected node. 

3670 

3671 Use the headline if it contains a valid url. 

3672 Otherwise, look *only* at the first line of the body. 

3673 """ 

3674 c = event.get('c') 

3675 if c: 

3676 g.openUrl(c.p) 

3677#@+node:ekr.20150514125218.6: *3* open-url-under-cursor 

3678@g.command('open-url-under-cursor') 

3679def openUrlUnderCursor(event=None): 

3680 """Open the url under the cursor.""" 

3681 return g.openUrlOnClick(event) 

3682#@-others 

3683#@@language python 

3684#@@tabwidth -4 

3685#@@pagewidth 70 

3686#@-leo