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

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

2000 statements  

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 self.atAutoDict = {} 

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

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 g.app.recentFilesManager.writeRecentFilesFile(c) 

1221 # Make sure .leoRecentFiles.txt is written. 

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() 

1233 # store cache, but don't close it. 

1234 # This may remove frame from the window list. 

1235 if frame in g.app.windowList: 

1236 g.app.destroyWindow(frame) 

1237 g.app.windowList.remove(frame) 

1238 else: 

1239 # #69. 

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

1241 if g.app.windowList: 

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

1243 g.app.selectLeoWindow(c2) 

1244 elif finish_quit and not g.unitTesting: 

1245 g.app.finishQuit() 

1246 return True # The window has been closed. 

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

1248 def destroyAllOpenWithFiles(self): 

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

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

1251 g.pr('destroyAllOpenWithFiles') 

1252 if g.app.externalFilesController: 

1253 g.app.externalFilesController.shut_down() 

1254 g.app.externalFilesController = None 

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

1256 def destroyWindow(self, frame): 

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

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

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

1260 if g.app.externalFilesController: 

1261 g.app.externalFilesController.destroy_frame(frame) 

1262 if frame in g.app.windowList: 

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

1264 # force the window to go away now. 

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

1266 frame.destroySelf() 

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

1268 def finishQuit(self): 

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

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

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

1272 if trace: 

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

1274 if not g.app.killed: 

1275 g.doHook("end1") 

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

1277 g.app.global_cacher.commit_and_close() 

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

1279 g.app.commander_cacher.commit() 

1280 g.app.commander_cacher.close() 

1281 if g.app.ipk: 

1282 g.app.ipk.cleanup_consoles() 

1283 g.app.destroyAllOpenWithFiles() 

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

1285 # pylint: disable=no-member 

1286 g.app.pyzo_close_handler() 

1287 # Disable all further hooks and events. 

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

1289 # even after the following code. 

1290 g.app.killed = True 

1291 if g.app.gui: 

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

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

1294 def forceShutdown(self): 

1295 """ 

1296 Forces an immediate shutdown of Leo at any time. 

1297 

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

1299 """ 

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

1301 app = self 

1302 if trace: 

1303 g.pr('forceShutdown') 

1304 for c in app.commanders(): 

1305 app.forgetOpenFile(c.fileName()) 

1306 # Wait until everything is quiet before really quitting. 

1307 if trace: 

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

1309 g.doHook("end1") 

1310 if trace: 

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

1312 self.log = None # Disable writeWaitingLog 

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

1314 for w in self.windowList[:]: 

1315 if trace: 

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

1317 self.destroyWindow(w) 

1318 if trace: 

1319 g.pr('before finishQuit') 

1320 self.finishQuit() 

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

1322 @cmd('exit-leo') 

1323 @cmd('quit-leo') 

1324 def onQuit(self, event=None): 

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

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

1327 g.trace() 

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

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

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

1331 def commanders(self): 

1332 """ Return list of currently active controllers """ 

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

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

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

1336 def checkForOpenFile(self, c, fn): 

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

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

1339 if g.app.reverting: 

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

1341 g.app.already_open_files = [] 

1342 if (d is None or 

1343 g.unitTesting or 

1344 g.app.batchMode or 

1345 g.app.reverting or 

1346 g.app.inBridge 

1347 ): 

1348 return 

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

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

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

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

1353 # another Leo may have been killed prematurely. 

1354 # Put the file on the global list. 

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

1356 fn = os.path.normpath(fn) 

1357 if fn not in g.app.already_open_files: 

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

1359 g.es(fn) 

1360 g.app.already_open_files.append(fn) 

1361 else: 

1362 g.app.rememberOpenFile(fn) 

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

1364 def forgetOpenFile(self, fn): 

1365 """ 

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

1367 """ 

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

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

1370 if not d or not fn: 

1371 return # #69. 

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

1373 fn = os.path.normpath(fn) 

1374 if fn in aList: 

1375 aList.remove(fn) 

1376 if trace: 

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

1378 d[tag] = aList 

1379 

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

1381 def rememberOpenFile(self, fn): 

1382 

1383 # 

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

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

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

1387 pass 

1388 elif g.app.preReadFlag: 

1389 pass 

1390 else: 

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

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

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

1394 d[tag] = aList 

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

1396 def runAlreadyOpenDialog(self, c): 

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

1398 if g.app.already_open_files: 

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

1400 g.app.already_open_files = [] 

1401 g.app.gui.dismiss_splash_screen() 

1402 message = ( 

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

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

1405 '\n'.join(aList)) 

1406 g.app.gui.runAskOkDialog(c, 

1407 title='Already Open Files', 

1408 message=message, 

1409 text="Ok") 

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

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

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

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

1414 d = g.app.atAutoDict 

1415 for key in d: 

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

1417 func = d.get(key) 

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

1419 return func 

1420 return None 

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

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

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

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

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

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

1427 @cmd('log-listen') 

1428 def listenToLog(self, event=None): 

1429 """ 

1430 A socket listener, listening on localhost. See: 

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

1432 

1433 Start this listener first, then start the broadcaster. 

1434 

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

1436 """ 

1437 app = self 

1438 # Kill any previous listener. 

1439 if app.log_listener: 

1440 g.es_print('Killing previous listener') 

1441 try: 

1442 app.log_listener.kill() 

1443 except Exception: 

1444 g.es_exception() 

1445 app.log_listener = None 

1446 # Start a new listener. 

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

1448 path = g.os_path_finalize_join(app.loadDir, 

1449 '..', 'external', 'log_listener.py') 

1450 app.log_listener = subprocess.Popen( 

1451 [sys.executable, path], 

1452 shell=False, 

1453 universal_newlines=True, 

1454 ) 

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

1456 def makeAllBindings(self): 

1457 """ 

1458 LeoApp.makeAllBindings: 

1459 

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

1461 """ 

1462 app = self 

1463 for c in app.commanders(): 

1464 c.k.makeAllBindings() 

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

1466 def newCommander(self, fileName, 

1467 gui=None, 

1468 parentFrame=None, 

1469 previousSettings=None, 

1470 relativeFileName=None, 

1471 ): 

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

1473 # Create the commander and its subcommanders. 

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

1475 # Timeit reports 0.0175 sec when using a nullGui. 

1476 from leo.core import leoCommands 

1477 c = leoCommands.Commands(fileName, 

1478 gui=gui, 

1479 parentFrame=parentFrame, 

1480 previousSettings=previousSettings, 

1481 relativeFileName=relativeFileName, 

1482 ) 

1483 return c 

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

1485 def selectLeoWindow(self, c): 

1486 frame = c.frame 

1487 frame.deiconify() 

1488 frame.lift() 

1489 c.setLog() 

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

1491 if master: 

1492 # master is a TabbedTopLevel. 

1493 # Selecting the new tab ensures focus is set. 

1494 master.select(c) 

1495 if 1: 

1496 c.initialFocusHelper() 

1497 else: 

1498 c.bodyWantsFocus() 

1499 c.outerUpdate() 

1500 #@-others 

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

1502class LoadManager: 

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

1504 #@+others 

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

1506 def __init__(self): 

1507 

1508 # Global settings & shortcuts dicts... 

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

1510 

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

1512 self.globalSettingsDict = None 

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

1514 self.globalBindingsDict = None 

1515 

1516 # LoadManager ivars corresponding to user options... 

1517 

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

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

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

1521 

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

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

1524 # "file already open, open again". 

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

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

1527 self.more_cmdline_files = False 

1528 

1529 # Themes... 

1530 self.leo_settings_c = None 

1531 self.leo_settings_path = None 

1532 self.my_settings_c = None 

1533 self.my_settings_path = None 

1534 self.theme_c = None # #1374. 

1535 self.theme_path = None 

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

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

1538 def completeFileName(self, fileName): 

1539 fileName = g.toUnicode(fileName) 

1540 fileName = g.os_path_finalize(fileName) 

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

1542 return fileName 

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

1544 def computeLeoSettingsPath(self): 

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

1546 # lm = self 

1547 join = g.os_path_finalize_join 

1548 settings_fn = 'leoSettings.leo' 

1549 table = ( 

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

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

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

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

1554 join(g.app.globalConfigDir, settings_fn) 

1555 ) 

1556 for path in table: 

1557 if g.os_path_exists(path): 

1558 break 

1559 else: 

1560 path = None 

1561 return path 

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

1563 def computeMyLeoSettingsPath(self): 

1564 """ 

1565 Return the full path to myLeoSettings.leo. 

1566 

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

1568 """ 

1569 lm = self 

1570 join = g.os_path_finalize_join 

1571 settings_fn = 'myLeoSettings.leo' 

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

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

1574 machine_fn = lm.computeMachineName() + settings_fn 

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

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

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

1578 table = ( 

1579 # First, myLeoSettings.leo in the local directory 

1580 join(localDir, settings_fn), 

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

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

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

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

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

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

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

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

1589 ) 

1590 for path in table: 

1591 if g.os_path_exists(path): 

1592 break 

1593 else: 

1594 path = None 

1595 return path 

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

1597 def computeStandardDirectories(self): 

1598 """ 

1599 Compute the locations of standard directories and 

1600 set the corresponding ivars. 

1601 """ 

1602 lm = self 

1603 join = os.path.join 

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

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

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

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

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

1609 # These use g.app.loadDir... 

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

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

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

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

1614 def computeGlobalConfigDir(self): 

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

1616 if leo_config_dir: 

1617 theDir = leo_config_dir 

1618 else: 

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

1620 if theDir: 

1621 theDir = os.path.abspath(theDir) 

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

1623 theDir = None 

1624 return theDir 

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

1626 def computeHomeDir(self): 

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

1628 # Windows searches the HOME, HOMEPATH and HOMEDRIVE 

1629 # environment vars, then gives up. 

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

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

1632 # Get the indirect reference to the true home. 

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

1634 if home: 

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

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

1637 home = g.os_path_finalize(home) 

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

1639 home = None 

1640 return home 

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

1642 def computeHomeLeoDir(self): 

1643 # lm = self 

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

1645 if g.os_path_exists(homeLeoDir): 

1646 return homeLeoDir 

1647 ok = g.makeAllNonExistentDirectories(homeLeoDir) 

1648 return homeLeoDir if ok else '' # #1450 

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

1650 def computeLeoDir(self): 

1651 # lm = self 

1652 loadDir = g.app.loadDir 

1653 return g.os_path_dirname(loadDir) 

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

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

1656 def computeLoadDir(self): 

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

1658 try: 

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

1660 # __file__ is randomly upper or lower case! 

1661 # The made for an ugly recent files list. 

1662 path = g.__file__ # was leo.__file__ 

1663 if path: 

1664 # Possible fix for bug 735938: 

1665 # Do the following only if path exists. 

1666 #@+<< resolve symlinks >> 

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

1668 if path.endswith('pyc'): 

1669 srcfile = path[:-1] 

1670 if os.path.islink(srcfile): 

1671 path = os.path.realpath(srcfile) 

1672 #@-<< resolve symlinks >> 

1673 if sys.platform == 'win32': 

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

1675 # Convert the drive name to upper case. 

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

1677 path = g.os_path_finalize(path) 

1678 loadDir = g.os_path_dirname(path) 

1679 else: loadDir = None 

1680 if ( 

1681 not loadDir or 

1682 not g.os_path_exists(loadDir) or 

1683 not g.os_path_isdir(loadDir) 

1684 ): 

1685 loadDir = os.getcwd() 

1686 # From Marc-Antoine Parent. 

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

1688 loadDir += "/leo/plugins" 

1689 else: 

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

1691 loadDir = g.os_path_finalize(loadDir) 

1692 return loadDir 

1693 except Exception: 

1694 print("Exception getting load directory") 

1695 raise 

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

1697 def computeMachineName(self): 

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

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

1700 # to give the machine-specific setting name. 

1701 # How can this be worth doing?? 

1702 try: 

1703 name = os.getenv('HOSTNAME') 

1704 if not name: 

1705 name = os.getenv('COMPUTERNAME') 

1706 if not name: 

1707 import socket 

1708 name = socket.gethostname() 

1709 except Exception: 

1710 name = '' 

1711 return name 

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

1713 def computeThemeDirectories(self): 

1714 """ 

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

1716 """ 

1717 join = g.os_path_finalize_join 

1718 home = g.app.homeDir 

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

1720 table = [ 

1721 home, 

1722 join(home, 'themes'), 

1723 join(home, '.leo'), 

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

1725 join(leo, 'themes'), 

1726 ] 

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

1728 # Make sure home has normalized slashes. 

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

1730 def computeThemeFilePath(self): 

1731 """ 

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

1733 

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

1735 

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

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

1738 

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

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

1741 """ 

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

1743 lm = self 

1744 resolve = self.resolve_theme_path 

1745 # 

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

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

1748 if path: 

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

1750 if trace: 

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

1752 return path 

1753 # 

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

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

1756 if path and g.os_path_exists(path): 

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

1758 theme_c = lm.openSettingsFile(path) 

1759 if theme_c: 

1760 settings_d, junk_shortcuts_d = lm.computeLocalSettings( 

1761 c=theme_c, 

1762 settings_d=lm.globalSettingsDict, 

1763 bindings_d=lm.globalBindingsDict, 

1764 localFlag=False, 

1765 ) 

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

1767 if setting: 

1768 tag = theme_c.shortFileName() 

1769 path = resolve(setting, tag=tag) 

1770 if path: 

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

1772 if trace: 

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

1774 return path 

1775 # 

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

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

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

1779 tag = 'myLeoSettings.leo' 

1780 path = resolve(setting, tag=tag) 

1781 if trace: 

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

1783 return path 

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

1785 def resolve_theme_path(self, fn, tag): 

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

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

1788 return None 

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

1790 fn += '.leo' 

1791 for directory in self.computeThemeDirectories(): 

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

1793 if g.os_path_exists(path): 

1794 return path 

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

1796 return None 

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

1798 def computeWorkbookFileName(self): 

1799 """ 

1800 Return full path to the workbook. 

1801 

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

1803 directory does not exist. 

1804 """ 

1805 # lm = self 

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

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

1808 return None 

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

1810 fn = g.os_path_finalize(fn) 

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

1812 # #1415. 

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

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

1815 def reportDirectories(self): 

1816 """Report directories.""" 

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

1818 for kind, theDir in ( 

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

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

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

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

1823 ): 

1824 # g.blue calls g.es_print, and that's annoying. 

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

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

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

1828 def computeBindingLetter(self, c, path): 

1829 lm = self 

1830 if not path: 

1831 return 'D' 

1832 path = path.lower() 

1833 table = ( 

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

1835 (' ', 'leoSettings.leo'), 

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

1837 ) 

1838 for letter, path2 in table: 

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

1840 return letter 

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

1842 return 'T' 

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

1844 return '@' 

1845 return 'D' 

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

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

1848 """ 

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

1850 settings_d and bindings_d. 

1851 """ 

1852 lm = self 

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

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

1855 settings_d, bindings_d = lm.createDefaultSettingsDicts() 

1856 if settings_d2: 

1857 if g.app.trace_setting: 

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

1859 val = settings_d2.d.get(key) 

1860 if val: 

1861 fn = g.shortFileName(val.path) 

1862 g.es_print( 

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

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

1865 settings_d = settings_d.copy() 

1866 settings_d.update(settings_d2) 

1867 if shortcuts_d2: 

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

1869 return settings_d, bindings_d 

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

1871 def createDefaultSettingsDicts(self): 

1872 """Create lm.globalSettingsDict & lm.globalBindingsDict.""" 

1873 settings_d = g.app.config.defaultsDict 

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

1875 settings_d.setName('lm.globalSettingsDict') 

1876 bindings_d = g.TypedDict( # was TypedDictOfLists. 

1877 name='lm.globalBindingsDict', 

1878 keyType=type('s'), 

1879 valType=g.BindingInfo, 

1880 ) 

1881 return settings_d, bindings_d 

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

1883 def createSettingsDicts(self, c, localFlag): 

1884 

1885 from leo.core import leoConfig 

1886 if c: 

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

1888 parser = leoConfig.SettingsTreeParser(c, localFlag) 

1889 shortcutsDict, settingsDict = parser.traverse() 

1890 return shortcutsDict, settingsDict 

1891 return None, None 

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

1893 def getPreviousSettings(self, fn): 

1894 """ 

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

1896 pre-reading fn. 

1897 """ 

1898 lm = self 

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

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

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

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

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

1904 exists = g.os_path_exists(fn) 

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

1906 # Open the file usinging a null gui. 

1907 try: 

1908 g.app.preReadFlag = True 

1909 c = lm.openSettingsFile(fn) 

1910 finally: 

1911 g.app.preReadFlag = False 

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

1913 d1, d2 = lm.computeLocalSettings(c, 

1914 lm.globalSettingsDict, 

1915 lm.globalBindingsDict, 

1916 localFlag=True) 

1917 # d1 and d2 are copies. 

1918 d1.setName(settingsName) 

1919 d2.setName(shortcutsName) 

1920 return PreviousSettings(d1, d2) 

1921 # 

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

1923 # Get the settings from the globals settings dicts. 

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

1925 d1 = lm.globalSettingsDict.copy(settingsName) 

1926 d2 = lm.globalBindingsDict.copy(shortcutsName) 

1927 else: 

1928 d1 = d2 = None 

1929 return PreviousSettings(d1, d2) 

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

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

1932 """ 

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

1934 

1935 Both old_d and new_d remain unchanged. 

1936 """ 

1937 lm = self 

1938 if not old_d: 

1939 return new_d 

1940 if not new_d: 

1941 return old_d 

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

1943 if bi_list: 

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

1945 for bi in bi_list: 

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

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

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

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

1950 else: 

1951 pane = '' 

1952 inverted_old_d = lm.invert(old_d) 

1953 inverted_new_d = lm.invert(new_d) 

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

1955 if g.app.trace_binding: 

1956 binding = g.app.trace_binding 

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

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

1959 g.es_print( 

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

1961 f"sets {binding} to None") 

1962 elif localFlag and binding in c.commandsDict: 

1963 d = c.k.computeInverseBindingDict() 

1964 g.trace( 

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

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

1967 else: 

1968 binding = g.app.trace_binding 

1969 stroke = g.KeyStroke(binding) 

1970 bi_list = inverted_new_d.get(stroke) 

1971 if bi_list: 

1972 print('') 

1973 for bi in bi_list: 

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

1975 stroke2 = c.k.prettyPrintKey(stroke) 

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

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

1978 else: 

1979 pane = '' 

1980 g.es_print( 

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

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

1983 print('') 

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

1985 lm.checkForDuplicateShortcuts(c, inverted_new_d) 

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

1987 result = lm.uninvert(inverted_old_d) 

1988 return result 

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

1990 def checkForDuplicateShortcuts(self, c, d): 

1991 """ 

1992 Check for duplicates in an "inverted" dictionary d 

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

1994 

1995 Duplicates happen only if panes conflict. 

1996 """ 

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

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

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

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

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

2002 if len(aList) > 1: 

2003 for bi in aList2: 

2004 if bi.pane in panes: 

2005 duplicates.append(bi) 

2006 else: 

2007 panes.append(bi.pane) 

2008 if duplicates: 

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

2010 if len(bindings) == 1: 

2011 kind = 'duplicate, (not conflicting)' 

2012 else: 

2013 kind = 'conflicting' 

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

2015 for bi in aList2: 

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

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

2018 def invert(self, d): 

2019 """ 

2020 Invert a shortcut dict whose keys are command names, 

2021 returning a dict whose keys are strokes. 

2022 """ 

2023 result = g.TypedDict( # was TypedDictOfLists. 

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

2025 keyType=g.KeyStroke, 

2026 valType=g.BindingInfo, 

2027 ) 

2028 for commandName in d.keys(): 

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

2030 stroke = bi.stroke # This is canonicalized. 

2031 bi.commandName = commandName # Add info. 

2032 assert stroke 

2033 result.add_to_list(stroke, bi) 

2034 return result 

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

2036 def uninvert(self, d): 

2037 """ 

2038 Uninvert an inverted shortcut dict whose keys are strokes, 

2039 returning a dict whose keys are command names. 

2040 """ 

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

2042 result = g.TypedDict( # was TypedDictOfLists. 

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

2044 keyType=type('commandName'), 

2045 valType=g.BindingInfo, 

2046 ) 

2047 for stroke in d.keys(): 

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

2049 commandName = bi.commandName 

2050 assert commandName 

2051 result.add_to_list(commandName, bi) 

2052 return result 

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

2054 def openSettingsFile(self, fn): 

2055 """ 

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

2057 

2058 The caller must init the c.config object. 

2059 """ 

2060 lm = self 

2061 if not fn: 

2062 return None 

2063 theFile = lm.openAnyLeoFile(fn) 

2064 if not theFile: 

2065 return None # Fix #843. 

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

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

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

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

2070 print(s) 

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

2072 # A useful trace. 

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

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

2075 oldGui = g.app.gui 

2076 g.app.gui = g.app.nullGui 

2077 c = g.app.newCommander(fn) 

2078 frame = c.frame 

2079 frame.log.enable(False) 

2080 g.app.lockLog() 

2081 g.app.openingSettingsFile = True 

2082 try: 

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

2084 readAtFileNodesFlag=False, silent=True) 

2085 # closes theFile. 

2086 finally: 

2087 g.app.openingSettingsFile = False 

2088 g.app.unlockLog() 

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

2090 g.app.gui = oldGui 

2091 return c if ok else None 

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

2093 def readGlobalSettingsFiles(self): 

2094 """ 

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

2096 

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

2098 """ 

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

2100 lm = self 

2101 # Open the standard settings files with a nullGui. 

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

2103 old_commanders = g.app.commanders() 

2104 lm.leo_settings_path = lm.computeLeoSettingsPath() 

2105 lm.my_settings_path = lm.computeMyLeoSettingsPath() 

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

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

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

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

2110 settings_d, bindings_d = lm.createDefaultSettingsDicts() 

2111 for c in commanders: 

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

2113 # *new copies of* settings_d and bindings_d. 

2114 settings_d, bindings_d = lm.computeLocalSettings( 

2115 c, settings_d, bindings_d, localFlag=False) 

2116 # Adjust the name. 

2117 bindings_d.setName('lm.globalBindingsDict') 

2118 lm.globalSettingsDict = settings_d 

2119 lm.globalBindingsDict = bindings_d 

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

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

2122 lm.theme_path = lm.computeThemeFilePath() 

2123 if lm.theme_path: 

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

2125 if lm.theme_c: 

2126 # Merge theme_c's settings into globalSettingsDict. 

2127 settings_d, junk_shortcuts_d = lm.computeLocalSettings( 

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

2129 lm.globalSettingsDict = settings_d 

2130 # Set global var used by the StyleSheetManager. 

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

2132 if trace: 

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

2134 # Clear the cache entries for the commanders. 

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

2136 for c in commanders: 

2137 if c not in old_commanders: 

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

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

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

2141 if verbose: 

2142 print(d) 

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

2144 gs = d.get(key) 

2145 print(f"{key:35} {g.shortFileName(gs.path):17} {gs.val}") 

2146 if d: 

2147 print('') 

2148 else: 

2149 # print(d) 

2150 print(f"{d.name} {len(d.d.keys())}") 

2151 #@+node:ekr.20120214165710.10822: *4* LM.traceShortcutsDict 

2152 def traceShortcutsDict(self, d, verbose=False): 

2153 if verbose: 

2154 print(d) 

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

2156 val = d.get(key) 

2157 # print('%20s %s' % (key,val.dump())) 

2158 print(f"{key:35} {[z.stroke for z in val]}") 

2159 if d: 

2160 print('') 

2161 else: 

2162 print(d) 

2163 #@+node:ekr.20120219154958.10452: *3* LM.load & helpers 

2164 def load(self, fileName=None, pymacs=None): 

2165 """This is Leo's main startup method.""" 

2166 lm = self 

2167 # 

2168 # Phase 1: before loading plugins. 

2169 # Scan options, set directories and read settings. 

2170 t1 = time.process_time() 

2171 print('') # Give some separation for the coming traces. 

2172 if not lm.isValidPython(): 

2173 return 

2174 lm.doPrePluginsInit(fileName, pymacs) 

2175 # sets lm.options and lm.files 

2176 g.app.computeSignon() 

2177 g.app.printSignon() 

2178 if lm.options.get('version'): 

2179 return 

2180 if not g.app.gui: 

2181 return 

2182 # Disable redraw until all files are loaded. 

2183 g.app.disable_redraw = True 

2184 # 

2185 # Phase 2: load plugins: the gui has already been set. 

2186 t2 = time.process_time() 

2187 g.doHook("start1") 

2188 t3 = time.process_time() 

2189 if g.app.killed: 

2190 return 

2191 g.app.idleTimeManager.start() 

2192 # 

2193 # Phase 3: after loading plugins. Create one or more frames. 

2194 t3 = time.process_time() 

2195 if lm.options.get('script') and not self.files: 

2196 ok = True 

2197 else: 

2198 ok = lm.doPostPluginsInit() 

2199 # Fix #579: Key bindings don't take for commands defined in plugins 

2200 g.app.makeAllBindings() 

2201 if ok and g.app.diff: 

2202 lm.doDiff() 

2203 if not ok: 

2204 return 

2205 g.es('') # Clears horizontal scrolling in the log pane. 

2206 if g.app.listen_to_log_flag: 

2207 g.app.listenToLog() 

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

2209 t4 = time.process_time() 

2210 print('') 

2211 g.es_print(f"settings:{t2 - t1:5.2f} sec") 

2212 g.es_print(f" plugins:{t3 - t2:5.2f} sec") 

2213 g.es_print(f" files:{t4 - t3:5.2f} sec") 

2214 g.es_print(f" total:{t4 - t1:5.2f} sec") 

2215 print('') 

2216 # -- quit 

2217 if g.app.quit_after_load: 

2218 if 'shutdown' in g.app.debug or 'startup' in g.app.debug: 

2219 print('--quit') 

2220 g.app.forceShutdown() 

2221 return 

2222 # #1128: support for restart-leo. 

2223 if not g.app.start_minimized: 

2224 try: # Careful: we may be unit testing. 

2225 g.app.log.c.frame.bringToFront() 

2226 except Exception: 

2227 pass 

2228 g.app.gui.runMainLoop() 

2229 # For scripts, the gui is a nullGui. 

2230 # and the gui.setScript has already been called. 

2231 #@+node:ekr.20150225133846.7: *4* LM.doDiff 

2232 def doDiff(self): 

2233 """Support --diff option after loading Leo.""" 

2234 if len(self.old_argv[2:]) == 2: 

2235 pass # c.editFileCommands.compareAnyTwoFiles gives a message. 

2236 else: 

2237 # This is an unusual situation. 

2238 g.es('--diff mode. sys.argv[2:]...', color='red') 

2239 for z in self.old_argv[2:]: 

2240 g.es(g.shortFileName(z) if z else repr(z), color='blue') 

2241 commanders = g.app.commanders() 

2242 if len(commanders) == 2: 

2243 c = commanders[0] 

2244 c.editFileCommands.compareAnyTwoFiles(event=None) 

2245 #@+node:ekr.20120219154958.10487: *4* LM.doPostPluginsInit & helpers 

2246 def doPostPluginsInit(self): 

2247 """Create a Leo window for each file in the lm.files list.""" 

2248 # Clear g.app.initing _before_ creating commanders. 

2249 lm = self 

2250 g.app.initing = False # "idle" hooks may now call g.app.forceShutdown. 

2251 # Create the main frame.Show it and all queued messages. 

2252 c = c1 = fn = None 

2253 if lm.files: 

2254 try: # #1403. 

2255 for n, fn in enumerate(lm.files): 

2256 lm.more_cmdline_files = n < len(lm.files) - 1 

2257 # Returns None if the file is open in another instance of Leo. 

2258 c = lm.loadLocalFile(fn, gui=g.app.gui, old_c=None) 

2259 if c and not c1: # #1416: 

2260 c1 = c 

2261 except Exception: 

2262 g.es_print(f"Unexpected exception reading {fn!r}") 

2263 g.es_exception() 

2264 c = None 

2265 # Load (and save later) a session *only* if the command line contains no files. 

2266 g.app.loaded_session = not lm.files 

2267 if g.app.sessionManager and g.app.loaded_session: 

2268 try: # #1403. 

2269 aList = g.app.sessionManager.load_snapshot() 

2270 if aList: 

2271 g.app.sessionManager.load_session(c1, aList) 

2272 # #659. 

2273 if g.app.windowList: 

2274 c = c1 = g.app.windowList[0].c 

2275 else: 

2276 c = c1 = None 

2277 except Exception: 

2278 g.es_print('Can not load session') 

2279 g.es_exception() 

2280 # Enable redraws. 

2281 g.app.disable_redraw = False 

2282 if not c1: 

2283 try: # #1403. 

2284 c1 = lm.openEmptyWorkBook() # Calls LM.loadLocalFile. 

2285 except Exception: 

2286 g.es_print('Can not create empty workbook') 

2287 g.es_exception() 

2288 c = c1 

2289 if not c: 

2290 # Leo is out of options: Force an immediate exit. 

2291 return False 

2292 # #199. 

2293 g.app.runAlreadyOpenDialog(c1) 

2294 # 

2295 # Final inits... 

2296 # For qt gui, select the first-loaded tab. 

2297 if hasattr(g.app.gui, 'frameFactory'): 

2298 factory = g.app.gui.frameFactory 

2299 if factory and hasattr(factory, 'setTabForCommander'): 

2300 factory.setTabForCommander(c) 

2301 g.app.logInited = True 

2302 g.app.initComplete = True 

2303 c.setLog() 

2304 c.redraw() 

2305 g.doHook("start2", c=c, p=c.p, fileName=c.fileName()) 

2306 c.initialFocusHelper() 

2307 screenshot_fn = lm.options.get('screenshot_fn') 

2308 if screenshot_fn: 

2309 lm.make_screen_shot(screenshot_fn) 

2310 return False # Force an immediate exit. 

2311 return True 

2312 #@+node:ekr.20120219154958.10489: *5* LM.make_screen_shot 

2313 def make_screen_shot(self, fn): 

2314 """Create a screenshot of the present Leo outline and save it to path.""" 

2315 if g.app.gui.guiName() == 'qt': 

2316 m = g.loadOnePlugin('screenshots') 

2317 m.make_screen_shot(fn) 

2318 #@+node:ekr.20131028155339.17098: *5* LM.openEmptyWorkBook 

2319 def openEmptyWorkBook(self): 

2320 """Open an empty frame and paste the contents of CheatSheet.leo into it.""" 

2321 lm = self 

2322 # Create an empty frame. 

2323 fn = lm.computeWorkbookFileName() 

2324 if not fn: 

2325 return None # #1415 

2326 c = lm.loadLocalFile(fn, gui=g.app.gui, old_c=None) 

2327 if not c: 

2328 return None # #1201: AttributeError below. 

2329 if g.app.batchMode or g.os_path_exists(fn): 

2330 return c 

2331 # Open the cheatsheet. 

2332 fn = g.os_path_finalize_join(g.app.loadDir, '..', 'doc', 'CheatSheet.leo') 

2333 if not g.os_path_exists(fn): 

2334 g.es(f"file not found: {fn}") 

2335 return None 

2336 # Paste the contents of CheetSheet.leo into c. 

2337 old_clipboard = g.app.gui.getTextFromClipboard() # #933: Save clipboard. 

2338 c2 = g.openWithFileName(fn, old_c=c) 

2339 for p2 in c2.rootPosition().self_and_siblings(): 

2340 c2.setCurrentPosition(p2) # 1380 

2341 c2.copyOutline() 

2342 # #1380 & #1381: Add guard & use vnode methods to prevent redraw. 

2343 p = c.pasteOutline() 

2344 if p: 

2345 c.setCurrentPosition(p) # 1380 

2346 p.v.contract() 

2347 p.v.clearDirty() 

2348 c2.close(new_c=c) 

2349 # Delete the dummy first node. 

2350 root = c.rootPosition() 

2351 root.doDelete(newNode=root.next()) 

2352 c.target_language = 'rest' 

2353 c.clearChanged() 

2354 c.redraw(c.rootPosition()) # # 1380: Select the root. 

2355 g.app.gui.replaceClipboardWith(old_clipboard) # #933: Restore clipboard 

2356 return c 

2357 #@+node:ekr.20120219154958.10477: *4* LM.doPrePluginsInit & helpers 

2358 def doPrePluginsInit(self, fileName, pymacs): 

2359 """ Scan options, set directories and read settings.""" 

2360 lm = self 

2361 lm.computeStandardDirectories() 

2362 # Scan the options as early as possible. 

2363 lm.options = options = lm.scanOptions(fileName, pymacs) # also sets lm.files. 

2364 if options.get('version'): 

2365 return 

2366 script = options.get('script') 

2367 verbose = script is None 

2368 # Init the app. 

2369 lm.initApp(verbose) 

2370 g.app.setGlobalDb() 

2371 if verbose: 

2372 lm.reportDirectories() 

2373 # Read settings *after* setting g.app.config and *before* opening plugins. 

2374 # This means if-gui has effect only in per-file settings. 

2375 if g.app.quit_after_load: 

2376 localConfigFile = None 

2377 else: 

2378 # Read only standard settings files, using a null gui. 

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

2380 # that might contain myLeoSettings.leo. 

2381 lm.readGlobalSettingsFiles() 

2382 # Read the recent files file. 

2383 localConfigFile = lm.files[0] if lm.files else None 

2384 g.app.recentFilesManager.readRecentFiles(localConfigFile) 

2385 # Create the gui after reading options and settings. 

2386 lm.createGui(pymacs) 

2387 # We can't print the signon until we know the gui. 

2388 g.app.computeSignon() # Set app.signon/signon1 for commanders. 

2389 #@+node:ekr.20170302093006.1: *5* LM.createAllImporterData & helpers 

2390 def createAllImporterData(self): 

2391 """ 

2392 New in Leo 5.5: 

2393 

2394 Create global data structures describing importers and writers. 

2395 """ 

2396 assert g.app.loadDir 

2397 # This is the only data required. 

2398 self.createWritersData() 

2399 # Was an AtFile method. 

2400 self.createImporterData() 

2401 # Was a LeoImportCommands method. 

2402 #@+node:ekr.20140724064952.18037: *6* LM.createImporterData & helper 

2403 def createImporterData(self): 

2404 """Create the data structures describing importer plugins.""" 

2405 # Allow plugins to be defined in ~/.leo/plugins. 

2406 plugins1 = g.os_path_finalize_join(g.app.homeDir, '.leo', 'plugins') 

2407 plugins2 = g.os_path_finalize_join(g.app.loadDir, '..', 'plugins') 

2408 for kind, plugins in (('home', plugins1), ('leo', plugins2)): 

2409 pattern = g.os_path_finalize_join( 

2410 g.app.loadDir, '..', 'plugins', 'importers', '*.py') 

2411 for fn in g.glob_glob(pattern): 

2412 sfn = g.shortFileName(fn) 

2413 if sfn != '__init__.py': 

2414 try: 

2415 module_name = sfn[:-3] 

2416 # Important: use importlib to give imported modules 

2417 # their fully qualified names. 

2418 m = importlib.import_module( 

2419 f"leo.plugins.importers.{module_name}") 

2420 self.parse_importer_dict(sfn, m) 

2421 # print('createImporterData', m.__name__) 

2422 except Exception: 

2423 g.warning(f"can not import leo.plugins.importers.{module_name}") 

2424 #@+node:ekr.20140723140445.18076: *7* LM.parse_importer_dict 

2425 def parse_importer_dict(self, sfn, m): 

2426 """ 

2427 Set entries in g.app.classDispatchDict, g.app.atAutoDict and 

2428 g.app.atAutoNames using entries in m.importer_dict. 

2429 """ 

2430 importer_d = getattr(m, 'importer_dict', None) 

2431 if importer_d: 

2432 at_auto = importer_d.get('@auto', []) 

2433 scanner_func = importer_d.get('func', None) 

2434 # scanner_name = scanner_class.__name__ 

2435 extensions = importer_d.get('extensions', []) 

2436 if at_auto: 

2437 # Make entries for each @auto type. 

2438 d = g.app.atAutoDict 

2439 for s in at_auto: 

2440 d[s] = scanner_func 

2441 g.app.atAutoDict[s] = scanner_func 

2442 g.app.atAutoNames.add(s) 

2443 if extensions: 

2444 # Make entries for each extension. 

2445 d = g.app.classDispatchDict 

2446 for ext in extensions: 

2447 d[ext] = scanner_func #importer_d.get('func')#scanner_class 

2448 elif sfn not in ( 

2449 # These are base classes, not real plugins. 

2450 'basescanner.py', 

2451 'linescanner.py', 

2452 ): 

2453 g.warning(f"leo/plugins/importers/{sfn} has no importer_dict") 

2454 #@+node:ekr.20140728040812.17990: *6* LM.createWritersData & helper 

2455 def createWritersData(self): 

2456 """Create the data structures describing writer plugins.""" 

2457 # Do *not* remove this trace. 

2458 trace = False and 'createWritersData' not in g.app.debug_dict 

2459 if trace: 

2460 # Suppress multiple traces. 

2461 g.app.debug_dict['createWritersData'] = True 

2462 g.app.writersDispatchDict = {} 

2463 g.app.atAutoWritersDict = {} 

2464 plugins1 = g.os_path_finalize_join(g.app.homeDir, '.leo', 'plugins') 

2465 plugins2 = g.os_path_finalize_join(g.app.loadDir, '..', 'plugins') 

2466 for kind, plugins in (('home', plugins1), ('leo', plugins2)): 

2467 pattern = g.os_path_finalize_join(g.app.loadDir, 

2468 '..', 'plugins', 'writers', '*.py') 

2469 for fn in g.glob_glob(pattern): 

2470 sfn = g.shortFileName(fn) 

2471 if sfn != '__init__.py': 

2472 try: 

2473 # Important: use importlib to give imported modules their fully qualified names. 

2474 m = importlib.import_module(f"leo.plugins.writers.{sfn[:-3]}") 

2475 self.parse_writer_dict(sfn, m) 

2476 except Exception: 

2477 g.es_exception() 

2478 g.warning(f"can not import leo.plugins.writers.{sfn}") 

2479 if trace: 

2480 g.trace('LM.writersDispatchDict') 

2481 g.printDict(g.app.writersDispatchDict) 

2482 g.trace('LM.atAutoWritersDict') 

2483 g.printDict(g.app.atAutoWritersDict) 

2484 #@+node:ekr.20140728040812.17991: *7* LM.parse_writer_dict 

2485 def parse_writer_dict(self, sfn, m): 

2486 """ 

2487 Set entries in g.app.writersDispatchDict and g.app.atAutoWritersDict 

2488 using entries in m.writers_dict. 

2489 """ 

2490 writer_d = getattr(m, 'writer_dict', None) 

2491 if writer_d: 

2492 at_auto = writer_d.get('@auto', []) 

2493 scanner_class = writer_d.get('class', None) 

2494 extensions = writer_d.get('extensions', []) 

2495 if at_auto: 

2496 # Make entries for each @auto type. 

2497 d = g.app.atAutoWritersDict 

2498 for s in at_auto: 

2499 aClass = d.get(s) 

2500 if aClass and aClass != scanner_class: 

2501 g.trace( 

2502 f"{sfn}: duplicate {s} class {aClass.__name__} " 

2503 f"in {m.__file__}:") 

2504 else: 

2505 d[s] = scanner_class 

2506 g.app.atAutoNames.add(s) 

2507 if extensions: 

2508 # Make entries for each extension. 

2509 d = g.app.writersDispatchDict 

2510 for ext in extensions: 

2511 aClass = d.get(ext) 

2512 if aClass and aClass != scanner_class: 

2513 g.trace(f"{sfn}: duplicate {ext} class", aClass, scanner_class) 

2514 else: 

2515 d[ext] = scanner_class 

2516 elif sfn not in ('basewriter.py',): 

2517 g.warning(f"leo/plugins/writers/{sfn} has no writer_dict") 

2518 #@+node:ekr.20120219154958.10478: *5* LM.createGui 

2519 def createGui(self, pymacs): 

2520 lm = self 

2521 gui_option = lm.options.get('gui') 

2522 windowFlag = lm.options.get('windowFlag') 

2523 script = lm.options.get('script') 

2524 if g.app.gui: 

2525 if g.app.gui == g.app.nullGui: 

2526 g.app.gui = None # Enable g.app.createDefaultGui 

2527 g.app.createDefaultGui(__file__) 

2528 else: 

2529 pass 

2530 # This can happen when launching Leo from IPython. 

2531 # This can also happen when leoID does not exist. 

2532 elif gui_option is None: 

2533 if script and not windowFlag: 

2534 # Always use null gui for scripts. 

2535 g.app.createNullGuiWithScript(script) 

2536 else: 

2537 g.app.createDefaultGui(__file__) 

2538 else: 

2539 lm.createSpecialGui(gui_option, pymacs, script, windowFlag) 

2540 #@+node:ekr.20120219154958.10479: *5* LM.createSpecialGui 

2541 def createSpecialGui(self, gui, pymacs, script, windowFlag): 

2542 # lm = self 

2543 if pymacs: 

2544 g.app.createNullGuiWithScript(script=None) 

2545 elif script: 

2546 if windowFlag: 

2547 g.app.createDefaultGui() 

2548 g.app.gui.setScript(script=script) 

2549 sys.argv = [] # 2021/06/24: corrected by mypy. 

2550 else: 

2551 g.app.createNullGuiWithScript(script=script) 

2552 else: 

2553 g.app.createDefaultGui() 

2554 #@+node:ekr.20120219154958.10482: *5* LM.getDefaultFile 

2555 def getDefaultFile(self): 

2556 # Get the name of the workbook. 

2557 fn = g.app.config.getString('default-leo-file') 

2558 fn = g.os_path_finalize(fn) 

2559 if not fn: 

2560 return None 

2561 if g.os_path_exists(fn): 

2562 return fn 

2563 if g.os_path_isabs(fn): 

2564 # Create the file. 

2565 g.error(f"Using default leo file name:\n{fn}") 

2566 return fn 

2567 # It's too risky to open a default file if it is relative. 

2568 return None 

2569 #@+node:ekr.20120219154958.10484: *5* LM.initApp 

2570 def initApp(self, verbose): 

2571 

2572 self.createAllImporterData() 

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

2574 assert g.app.loadManager 

2575 from leo.core import leoBackground 

2576 from leo.core import leoConfig 

2577 from leo.core import leoNodes 

2578 from leo.core import leoPlugins 

2579 from leo.core import leoSessions 

2580 # Import leoIPython only if requested. The import is quite slow. 

2581 self.setStdStreams() 

2582 if g.app.useIpython: 

2583 from leo.core import leoIPython 

2584 # This launches the IPython Qt Console. It *is* required. 

2585 assert leoIPython # suppress pyflakes/flake8 warning. 

2586 # Make sure we call the new leoPlugins.init top-level function. 

2587 leoPlugins.init() 

2588 # Force the user to set g.app.leoID. 

2589 g.app.setLeoID(verbose=verbose) 

2590 # Create early classes *after* doing plugins.init() 

2591 g.app.idleTimeManager = IdleTimeManager() 

2592 g.app.backgroundProcessManager = leoBackground.BackgroundProcessManager() 

2593 g.app.externalFilesController = leoExternalFiles.ExternalFilesController() 

2594 g.app.recentFilesManager = RecentFilesManager() 

2595 g.app.config = leoConfig.GlobalConfigManager() 

2596 g.app.nodeIndices = leoNodes.NodeIndices(g.app.leoID) 

2597 g.app.sessionManager = leoSessions.SessionManager() 

2598 # Complete the plugins class last. 

2599 g.app.pluginsController.finishCreate() 

2600 #@+node:ekr.20210927034148.1: *5* LM.scanOptions & helpers 

2601 def scanOptions(self, fileName, pymacs): 

2602 """Handle all options, remove them from sys.argv and set lm.options.""" 

2603 lm = self 

2604 table = ( 

2605 '--dock', 

2606 '--global-docks', # #1643. use --use-docks instead. 

2607 '--init-docks', 

2608 '--no-cache', 

2609 '--no-dock', # #1171 and #1514: use --use-docks instead. 

2610 '--session-restore', 

2611 '--session-save', 

2612 '--use-docks', 

2613 ) 

2614 trace_m = textwrap.dedent("""\ 

2615 abbrev, beauty, cache, coloring, drawing, events, focus, git, gnx 

2616 importers, ipython, keys, layouts, plugins, save, select, sections, 

2617 shutdown, size, speed, startup, themes, undo, verbose, zoom 

2618 """) 

2619 for bad_option in table: 

2620 if bad_option in sys.argv: 

2621 sys.argv.remove(bad_option) 

2622 print(f"Ignoring the unused/deprecated {bad_option} option") 

2623 lm.old_argv = sys.argv[:] 

2624 # Automatically implements the --help option. 

2625 description = "usage: launchLeo.py [options] file1, file2, ..." 

2626 parser = argparse.ArgumentParser( 

2627 description=description, 

2628 formatter_class=argparse.RawTextHelpFormatter) 

2629 # 

2630 # Parse the options, and remove them from sys.argv. 

2631 self.addOptionsToParser(parser, trace_m) 

2632 args = parser.parse_args() 

2633 # Handle simple args... 

2634 self.doSimpleOptions(args, trace_m) 

2635 # Compute the lm.files ivar. 

2636 lm.files = lm.computeFilesList(fileName) 

2637 # Compute the script. Used twice below. 

2638 script = None if pymacs else self.doScriptOption(args, parser) 

2639 # Return the dictionary of options. 

2640 return { 

2641 'gui': lm.doGuiOption(args), 

2642 'load_type': lm.doLoadTypeOption(args), 

2643 'screenshot_fn': lm.doScreenShotOption(args), # --screen-shot=fn 

2644 'script': script, 

2645 'select': args.select and args.select.strip('"'), 

2646 # --select=headline 

2647 'theme_path': args.theme, # --theme=name 

2648 'version': args.version, # --version: print the version and exit. 

2649 'windowFlag': script and args.script_window, 

2650 'windowSize': lm.doWindowSizeOption(args), 

2651 'windowSpot': lm.doWindowSpotOption(args), 

2652 } 

2653 #@+node:ekr.20210927034148.2: *6* LM.addOptionsToParser 

2654 #@@nobeautify 

2655 

2656 def addOptionsToParser(self, parser, trace_m): 

2657 """Init the argsparse parser.""" 

2658 add = parser.add_argument 

2659 add('PATHS', nargs='*', metavar='FILES', 

2660 help='list of files') 

2661 add('--diff', dest='diff', action='store_true', 

2662 help='use Leo as an external git diff') 

2663 add('--fail-fast', dest='fail_fast', action='store_true', 

2664 help='stop unit tests after the first failure') 

2665 add('--fullscreen', dest='fullscreen', action='store_true', 

2666 help='start fullscreen') 

2667 add('--ipython', dest='ipython', action='store_true', 

2668 help='enable ipython support') 

2669 add('--gui', dest='gui', metavar='GUI', 

2670 help='gui to use (qt/console/null)') 

2671 add('--listen-to-log', dest='listen_to_log', action='store_true', 

2672 help='start log_listener.py on startup') 

2673 add('--load-type', dest='load_type', metavar='TYPE', 

2674 help='@<file> type for non-outlines') 

2675 add('--maximized', dest='maximized', action='store_true', 

2676 help='start maximized') 

2677 add('--minimized', dest='minimized', action='store_true', 

2678 help='start minimized') 

2679 add('--no-plugins', dest='no_plugins', action='store_true', 

2680 help='disable all plugins') 

2681 add('--no-splash', dest='no_splash', action='store_true', 

2682 help='disable the splash screen') 

2683 add('--quit', dest='quit', action='store_true', 

2684 help='quit immediately after loading') 

2685 add('--screen-shot', dest='screen_shot', metavar='PATH', 

2686 help='take a screen shot and then exit') 

2687 add('--script', dest='script', metavar="PATH", 

2688 help='execute a script and then exit') 

2689 add('--script-window', dest='script_window', action='store_true', 

2690 help='execute script using default gui') 

2691 add('--select', dest='select', metavar='ID', 

2692 help='headline or gnx of node to select') 

2693 add('--silent', dest='silent', action='store_true', 

2694 help='disable all log messages') 

2695 add('--theme', dest='theme', metavar='NAME', 

2696 help='use the named theme file') 

2697 add('--trace', dest='trace', metavar='TRACE-KEY', 

2698 help=f"add one or more strings to g.app.debug. One or more of...\n{trace_m}") 

2699 add('--trace-binding', dest='trace_binding', metavar='KEY', 

2700 help='trace commands bound to a key') 

2701 add('--trace-setting', dest='trace_setting', metavar="NAME", 

2702 help='trace where named setting is set') 

2703 add('--window-size', dest='window_size', metavar='SIZE', 

2704 help='initial window size (height x width)') 

2705 add('--window-spot', dest='window_spot', metavar='SPOT', 

2706 help='initial window position (top x left)') 

2707 add('-v', '--version', dest='version', action='store_true', 

2708 help='print version number and exit') 

2709 #@+node:ekr.20210927034148.3: *6* LM.computeFilesList 

2710 def computeFilesList(self, fileName): 

2711 """Return the list of files on the command line.""" 

2712 lm = self 

2713 files = [] 

2714 if fileName: 

2715 files.append(fileName) 

2716 for arg in sys.argv[1:]: 

2717 if arg and not arg.startswith('-'): 

2718 files.append(arg) 

2719 result = [] 

2720 for z in files: 

2721 # Fix #245: wrong: result.extend(glob.glob(lm.completeFileName(z))) 

2722 aList = g.glob_glob(lm.completeFileName(z)) 

2723 if aList: 

2724 result.extend(aList) 

2725 else: 

2726 result.append(z) 

2727 return [g.os_path_normslashes(z) for z in result] 

2728 #@+node:ekr.20210927034148.4: *6* LM.doGuiOption 

2729 def doGuiOption(self, args): 

2730 gui = args.gui 

2731 if gui: 

2732 gui = gui.lower() 

2733 if gui in ('qt', 'qttabs'): 

2734 gui = 'qt' # Allow qttabs gui. 

2735 elif gui.startswith('browser'): 

2736 pass 

2737 elif gui in ('console', 'curses', 'text', 'null'): 

2738 pass 

2739 else: 

2740 print(f"scanOptions: unknown gui: {gui}. Using qt gui") 

2741 gui = 'qt' 

2742 else: 

2743 gui = 'qt' 

2744 assert gui 

2745 g.app.guiArgName = gui 

2746 return gui 

2747 #@+node:ekr.20210927034148.5: *6* LM.doLoadTypeOption 

2748 def doLoadTypeOption(self, args): 

2749 

2750 s = args.load_type 

2751 s = s.lower() if s else 'edit' 

2752 return '@' + s 

2753 #@+node:ekr.20210927034148.6: *6* LM.doScreenShotOption 

2754 def doScreenShotOption(self, args): 

2755 

2756 # --screen-shot=fn 

2757 s = args.screen_shot 

2758 if s: 

2759 s = s.strip('"') 

2760 return s 

2761 #@+node:ekr.20210927034148.7: *6* LM.doScriptOption 

2762 def doScriptOption(self, args, parser): 

2763 

2764 # --script 

2765 script = args.script 

2766 if script: 

2767 # #1090: use cwd, not g.app.loadDir, to find scripts. 

2768 fn = g.os_path_finalize_join(os.getcwd(), script) 

2769 script, e = g.readFileIntoString(fn, kind='script:', verbose=False) 

2770 if not script: 

2771 print(f"script not found: {fn}") 

2772 sys.exit(1) 

2773 else: 

2774 script = None 

2775 return script 

2776 #@+node:ekr.20210927034148.8: *6* LM.doSimpleOptions 

2777 def doSimpleOptions(self, args, trace_m): 

2778 """These args just set g.app ivars.""" 

2779 # --fail-fast 

2780 g.app.failFast = args.fail_fast 

2781 # --fullscreen 

2782 g.app.start_fullscreen = args.fullscreen 

2783 # --git-diff 

2784 g.app.diff = args.diff 

2785 # --listen-to-log 

2786 g.app.listen_to_log_flag = args.listen_to_log 

2787 # --ipython 

2788 g.app.useIpython = args.ipython 

2789 # --maximized 

2790 g.app.start_maximized = args.maximized 

2791 # --minimized 

2792 g.app.start_minimized = args.minimized 

2793 # --no-plugins 

2794 if args.no_plugins: 

2795 g.app.enablePlugins = False 

2796 # --no-splash: --minimized disables the splash screen 

2797 g.app.use_splash_screen = not args.no_splash and not args.minimized 

2798 # -- quit 

2799 g.app.quit_after_load = args.quit 

2800 # --silent 

2801 g.app.silentMode = args.silent 

2802 # --trace=... 

2803 valid = trace_m.replace(' ', '').replace('\n', '').split(',') 

2804 if args.trace: 

2805 ok = True 

2806 values = args.trace.lstrip('(').lstrip('[').rstrip(')').rstrip(']') 

2807 for val in values.split(','): 

2808 if val in valid: 

2809 g.app.debug.append(val) 

2810 else: 

2811 g.es_print(f"unknown --trace value: {val}") 

2812 ok = False 

2813 if not ok: 

2814 g.es_print('Valid --trace values are...') 

2815 for line in trace_m.split('\n'): 

2816 print(' ', line.rstrip()) 

2817 # 

2818 # These are not bool args. 

2819 # --trace-binding 

2820 g.app.trace_binding = args.trace_binding # g.app.config does not exist yet. 

2821 # 

2822 # --trace-setting=setting 

2823 g.app.trace_setting = args.trace_setting # g.app.config does not exist yet. 

2824 #@+node:ekr.20210927034148.9: *6* LM.doWindowSpotOption 

2825 def doWindowSpotOption(self, args): 

2826 

2827 # --window-spot 

2828 spot = args.window_spot 

2829 if spot: 

2830 try: 

2831 top, left = spot.split('x') 

2832 spot = int(top), int(left) 

2833 except ValueError: 

2834 print('scanOptions: bad --window-spot:', spot) 

2835 spot = None 

2836 

2837 return spot 

2838 #@+node:ekr.20210927034148.10: *6* LM.doWindowSizeOption 

2839 def doWindowSizeOption(self, args): 

2840 

2841 # --window-size 

2842 windowSize = args.window_size 

2843 if windowSize: 

2844 try: 

2845 h, w = windowSize.split('x') 

2846 windowSize = int(h), int(w) 

2847 except ValueError: 

2848 windowSize = None 

2849 print('scanOptions: bad --window-size:', windowSize) 

2850 return windowSize 

2851 #@+node:ekr.20160718072648.1: *5* LM.setStdStreams 

2852 def setStdStreams(self): 

2853 """ 

2854 Make sure that stdout and stderr exist. 

2855 This is an issue when running Leo with pythonw.exe. 

2856 """ 

2857 # Define class LeoStdOut 

2858 #@+others 

2859 #@+node:ekr.20160718091844.1: *6* class LeoStdOut 

2860 class LeoStdOut: 

2861 """A class to put stderr & stdout to Leo's log pane.""" 

2862 

2863 def __init__(self, kind): 

2864 self.kind = kind 

2865 g.es_print = self.write 

2866 g.pr = self.write 

2867 

2868 def flush(self, *args, **keys): 

2869 pass 

2870 #@+others 

2871 #@+node:ekr.20160718102306.1: *7* LeoStdOut.write 

2872 def write(self, *args, **keys): 

2873 """Put all non-keyword args to the log pane, as in g.es.""" 

2874 # 

2875 # Tracing will lead to unbounded recursion unless 

2876 # sys.stderr has been redirected on the command line. 

2877 app = g.app 

2878 if not app or app.killed: 

2879 return 

2880 if app.gui and app.gui.consoleOnly: 

2881 return 

2882 log = app.log 

2883 # Compute the effective args. 

2884 d = { 

2885 'color': None, 

2886 'commas': False, 

2887 'newline': True, 

2888 'spaces': True, 

2889 'tabName': 'Log', 

2890 } 

2891 # Handle keywords for g.pr and g.es_print. 

2892 d = g.doKeywordArgs(keys, d) 

2893 color: Any = d.get('color') 

2894 if color == 'suppress': 

2895 return 

2896 if log and color is None: 

2897 color = g.actualColor('black') 

2898 color = g.actualColor(color) 

2899 tabName = d.get('tabName') or 'Log' 

2900 newline = d.get('newline') 

2901 s = g.translateArgs(args, d) 

2902 if app.batchMode: 

2903 if log: 

2904 log.put(s) 

2905 elif log and app.logInited: 

2906 # from_redirect is the big difference between this and g.es. 

2907 log.put(s, color=color, tabName=tabName, from_redirect=True) 

2908 else: 

2909 app.logWaiting.append((s, color, newline),) 

2910 #@-others 

2911 #@-others 

2912 if not sys.stdout: 

2913 sys.stdout = sys.__stdout__ = LeoStdOut('stdout') # type:ignore 

2914 if not sys.stderr: 

2915 sys.stderr = sys.__stderr__ = LeoStdOut('stderr') # type:ignore 

2916 #@+node:ekr.20120219154958.10491: *4* LM.isValidPython 

2917 def isValidPython(self): 

2918 if sys.platform == 'cli': 

2919 return True 

2920 message = ( 

2921 f"Leo requires Python {g.minimum_python_version} or higher" 

2922 f"You may download Python from http://python.org/download/") 

2923 try: 

2924 version = '.'.join([str(sys.version_info[i]) for i in (0, 1, 2)]) 

2925 ok = g.CheckVersion(version, g.minimum_python_version) 

2926 if not ok: 

2927 print(message) 

2928 try: 

2929 # g.app.gui does not exist yet. 

2930 d = g.EmergencyDialog( 

2931 title='Python Version Error', 

2932 message=message) 

2933 d.run() 

2934 except Exception: 

2935 g.es_exception() 

2936 return ok 

2937 except Exception: 

2938 print("isValidPython: unexpected exception: g.CheckVersion") 

2939 traceback.print_exc() 

2940 return 0 

2941 #@+node:ekr.20120223062418.10393: *4* LM.loadLocalFile & helpers 

2942 def loadLocalFile(self, fn, gui, old_c): 

2943 """Completely read a file, creating the corresonding outline. 

2944 

2945 1. If fn is an existing .leo, .db or .leojs file, read it twice: 

2946 the first time with a NullGui to discover settings, 

2947 the second time with the requested gui to create the outline. 

2948 

2949 2. If fn is an external file: 

2950 get settings from the leoSettings.leo and myLeoSetting.leo, then 

2951 create a "wrapper" outline continain an @file node for the external file. 

2952 

2953 3. If fn is empty: 

2954 get settings from the leoSettings.leo and myLeoSetting.leo or default settings, 

2955 or open an empty outline. 

2956 """ 

2957 lm = self 

2958 # #2489: If fn is empty, open an empty, untitled .leo file. 

2959 if not fn: 

2960 return lm.openEmptyLeoFile(gui, old_c) 

2961 # Return the commander if the file is an already open outline. 

2962 fn = g.os_path_finalize(fn) # #2489. 

2963 c = lm.findOpenFile(fn) 

2964 if c: 

2965 return c 

2966 # Open the file, creating a wrapper .leo file if necessary. 

2967 previousSettings = lm.getPreviousSettings(fn) 

2968 c = lm.openFileByName(fn, gui, old_c, previousSettings) 

2969 return c 

2970 #@+node:ekr.20220318033804.1: *5* LM.openEmptyLeoFile 

2971 def openEmptyLeoFile(self, gui, old_c): 

2972 """Open an empty, untitled, new Leo file.""" 

2973 lm = self 

2974 # Disable the log. 

2975 g.app.setLog(None) 

2976 g.app.lockLog() 

2977 # Create the commander for the .leo file. 

2978 c = g.app.newCommander( 

2979 fileName=None, 

2980 gui=gui, 

2981 previousSettings=lm.getPreviousSettings(None), 

2982 ) 

2983 g.doHook('open0') 

2984 # Enable the log. 

2985 g.app.unlockLog() 

2986 c.frame.log.enable(True) 

2987 g.doHook("open1", old_c=old_c, c=c, new_c=c, fileName=None) 

2988 # Init the frame. 

2989 c.frame.setInitialWindowGeometry() 

2990 c.frame.deiconify() 

2991 c.frame.lift() 

2992 c.frame.splitVerticalFlag, r1, r2 = c.frame.initialRatios() 

2993 c.frame.resizePanesToRatio(r1, r2) 

2994 c.mFileName = None 

2995 c.wrappedFileName = None 

2996 c.frame.title = c.computeWindowTitle(c.mFileName) 

2997 c.frame.setTitle(c.frame.title) 

2998 # Late inits. Order matters. 

2999 if c.config.getBool('use-chapters') and c.chapterController: 

3000 c.chapterController.finishCreate() 

3001 c.clearChanged() 

3002 g.doHook("open2", old_c=old_c, c=c, new_c=c, fileName=None) 

3003 g.doHook("new", old_c=old_c, c=c, new_c=c) 

3004 g.app.writeWaitingLog(c) 

3005 c.setLog() 

3006 lm.createMenu(c) 

3007 lm.finishOpen(c) 

3008 return c 

3009 #@+node:ekr.20120223062418.10394: *5* LM.openFileByName & helpers 

3010 def openFileByName(self, fn, gui, old_c, previousSettings): 

3011 """ 

3012 Create an outline (Commander) for either: 

3013 - a Leo file (including .leo or zipped file), 

3014 - an external file. 

3015  

3016 Note: The settings don't matter for pre-reads! 

3017 For second read, the settings for the file are *exactly* previousSettings. 

3018 """ 

3019 lm = self 

3020 # Disable the log. 

3021 g.app.setLog(None) 

3022 g.app.lockLog() 

3023 # Create the a commander for the .leo file. 

3024 c = g.app.newCommander( 

3025 fileName=fn, 

3026 gui=gui, 

3027 previousSettings=previousSettings, 

3028 ) 

3029 g.doHook('open0') 

3030 # Open the file before the open1 hook. 

3031 theFile = lm.openAnyLeoFile(fn) 

3032 if isinstance(theFile, sqlite3.Connection): 

3033 # This commander is associated with sqlite db. 

3034 c.sqlite_connection = theFile 

3035 # Enable the log and do the open1 hook. 

3036 g.app.unlockLog() 

3037 c.frame.log.enable(True) 

3038 # Create the outline. 

3039 g.doHook("open1", old_c=None, c=c, new_c=c, fileName=fn) 

3040 if theFile: 

3041 # Some kind of existing Leo file. 

3042 readAtFileNodesFlag = bool(previousSettings) 

3043 # Read the Leo file. 

3044 ok = lm.readOpenedLeoFile(c, fn, readAtFileNodesFlag, theFile) 

3045 if not ok: 

3046 return None 

3047 else: 

3048 # Not a any kind of Leo file. Create a wrapper .leo file. 

3049 c = lm.initWrapperLeoFile(c, fn) # #2489 

3050 g.doHook("new", old_c=old_c, c=c, new_c=c) # #2489. 

3051 g.doHook("open2", old_c=old_c, c=c, new_c=c, fileName=fn) 

3052 # Complete the inits. 

3053 g.app.writeWaitingLog(c) 

3054 c.setLog() 

3055 lm.createMenu(c, fn) 

3056 lm.finishOpen(c) 

3057 return c 

3058 #@+node:ekr.20120223062418.10405: *6* LM.createMenu 

3059 def createMenu(self, c, fn=None): 

3060 # lm = self 

3061 # Create the menu as late as possible so it can use user commands. 

3062 if not g.doHook("menu1", c=c, p=c.p, v=c.p): 

3063 c.frame.menu.createMenuBar(c.frame) 

3064 g.app.recentFilesManager.updateRecentFiles(fn) 

3065 g.doHook("menu2", c=c, p=c.p, v=c.p) 

3066 g.doHook("after-create-leo-frame", c=c) 

3067 g.doHook("after-create-leo-frame2", c=c) 

3068 # Fix bug 844953: tell Unity which menu to use. 

3069 # c.enableMenuBar() 

3070 #@+node:ekr.20120223062418.10406: *6* LM.findOpenFile 

3071 def findOpenFile(self, fn): 

3072 

3073 def munge(name): 

3074 return g.os_path_normpath(name or '').lower() 

3075 

3076 for frame in g.app.windowList: 

3077 c = frame.c 

3078 if g.os_path_realpath(munge(fn)) == g.os_path_realpath(munge(c.mFileName)): 

3079 # Don't call frame.bringToFront(), it breaks --minimize 

3080 c.setLog() 

3081 # Selecting the new tab ensures focus is set. 

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

3083 if master: # master is a TabbedTopLevel. 

3084 master.select(frame.c) 

3085 c.outerUpdate() 

3086 return c 

3087 return None 

3088 #@+node:ekr.20120223062418.10407: *6* LM.finishOpen 

3089 def finishOpen(self, c): 

3090 # lm = self 

3091 k = c.k 

3092 assert k 

3093 # New in Leo 4.6: provide an official way for very late initialization. 

3094 c.frame.tree.initAfterLoad() 

3095 c.initAfterLoad() 

3096 # chapterController.finishCreate must be called after the first real redraw 

3097 # because it requires a valid value for c.rootPosition(). 

3098 if c.chapterController: 

3099 c.chapterController.finishCreate() 

3100 if k: 

3101 k.setDefaultInputState() 

3102 c.initialFocusHelper() 

3103 if k: 

3104 k.showStateAndMode() 

3105 c.frame.initCompleteHint() 

3106 c.outerUpdate() # #181: Honor focus requests. 

3107 #@+node:ekr.20120223062418.10408: *6* LM.initWrapperLeoFile 

3108 def initWrapperLeoFile(self, c, fn): 

3109 """ 

3110 Create an empty file if the external fn is empty. 

3111 

3112 Otherwise, create an @edit or @file node for the external file. 

3113 """ 

3114 # lm = self 

3115 # Use the config params to set the size and location of the window. 

3116 frame = c.frame 

3117 frame.setInitialWindowGeometry() 

3118 frame.deiconify() 

3119 frame.lift() 

3120 # #1570: Resize the _new_ frame. 

3121 frame.splitVerticalFlag, r1, r2 = frame.initialRatios() 

3122 frame.resizePanesToRatio(r1, r2) 

3123 if not g.os_path_exists(fn): 

3124 p = c.rootPosition() 

3125 # Create an empty @edit node unless fn is an .leo file. 

3126 # Fix #1070: Use "newHeadline", not fn. 

3127 p.h = "newHeadline" if fn.endswith('.leo') else f"@edit {fn}" 

3128 c.selectPosition(p) 

3129 elif c.looksLikeDerivedFile(fn): 

3130 # 2011/10/10: Create an @file node. 

3131 p = c.importCommands.importDerivedFiles(parent=c.rootPosition(), 

3132 paths=[fn], command=None) # Not undoable. 

3133 if p and p.hasBack(): 

3134 p.back().doDelete() 

3135 p = c.rootPosition() 

3136 if not p: 

3137 return None 

3138 else: 

3139 # Create an @<file> node. 

3140 p = c.rootPosition() 

3141 if p: 

3142 # The 'load_type' key may not exist when run from the bridge. 

3143 load_type = self.options.get('load_type', '@edit') # #2489. 

3144 p.setHeadString(f"{load_type} {fn}") 

3145 c.refreshFromDisk() 

3146 c.selectPosition(p) 

3147 # Fix critical bug 1184855: data loss with command line 'leo somefile.ext' 

3148 # Fix smallish bug 1226816 Command line "leo xxx.leo" creates file xxx.leo.leo. 

3149 c.mFileName = fn if fn.endswith('.leo') else f"{fn}.leo" 

3150 c.wrappedFileName = fn 

3151 c.frame.title = c.computeWindowTitle(c.mFileName) 

3152 c.frame.setTitle(c.frame.title) 

3153 if c.config.getBool('use-chapters') and c.chapterController: 

3154 c.chapterController.finishCreate() 

3155 frame.c.clearChanged() 

3156 return c 

3157 #@+node:ekr.20120223062418.10419: *6* LM.isLeoFile & LM.isZippedFile 

3158 def isLeoFile(self, fn): 

3159 """ 

3160 Return True if fn is any kind of Leo file, 

3161 including a zipped file or .leo, .db, or .leojs file. 

3162 """ 

3163 if not fn: 

3164 return False 

3165 return zipfile.is_zipfile(fn) or fn.endswith(('.leo', 'db', '.leojs')) 

3166 

3167 def isZippedFile(self, fn): 

3168 """Return True if fn is a zipped file.""" 

3169 return fn and zipfile.is_zipfile(fn) 

3170 #@+node:ekr.20120224161905.10030: *6* LM.openAnyLeoFile 

3171 def openAnyLeoFile(self, fn): 

3172 """Open a .leo, .leojs or .db file.""" 

3173 lm = self 

3174 if fn.endswith('.db'): 

3175 return sqlite3.connect(fn) 

3176 if lm.isLeoFile(fn) and g.os_path_exists(fn): 

3177 if lm.isZippedFile(fn): 

3178 theFile = lm.openZipFile(fn) 

3179 else: 

3180 theFile = lm.openLeoFile(fn) 

3181 else: 

3182 theFile = None 

3183 return theFile 

3184 #@+node:ekr.20120223062418.10416: *6* LM.openLeoFile 

3185 def openLeoFile(self, fn): 

3186 """Open the file for reading.""" 

3187 try: 

3188 theFile = open(fn, 'rb') 

3189 return theFile 

3190 except IOError: 

3191 # Do not use string + here: it will fail for non-ascii strings! 

3192 if not g.unitTesting: 

3193 g.error("can not open:", fn) 

3194 return None 

3195 #@+node:ekr.20120223062418.10410: *6* LM.openZipFile 

3196 def openZipFile(self, fn): 

3197 """ 

3198 Open a zipped file for reading. 

3199 Return a StringIO file if successful. 

3200 """ 

3201 try: 

3202 theFile = zipfile.ZipFile(fn, 'r') 

3203 if not theFile: 

3204 return None 

3205 # Read the file into an StringIO file. 

3206 aList = theFile.namelist() 

3207 name = aList and len(aList) == 1 and aList[0] 

3208 if not name: 

3209 return None 

3210 s = theFile.read(name) 

3211 s2 = g.toUnicode(s, 'utf-8') 

3212 return StringIO(s2) 

3213 except IOError: 

3214 # Do not use string + here: it will fail for non-ascii strings! 

3215 if not g.unitTesting: 

3216 g.error("can not open:", fn) 

3217 return None 

3218 #@+node:ekr.20120223062418.10412: *6* LM.readOpenedLeoFile 

3219 def readOpenedLeoFile(self, c, fn, readAtFileNodesFlag, theFile): 

3220 """ 

3221 Call c.fileCommands.openLeoFile to open some kind of Leo file. 

3222  

3223 the_file: An open file, which is a StringIO file for zipped files. 

3224 

3225 Note: g.app.log is not inited here. 

3226 """ 

3227 # New in Leo 4.10: The open1 event does not allow an override of the init logic. 

3228 assert theFile 

3229 # Read and close the file. 

3230 ok = c.fileCommands.openLeoFile( 

3231 theFile, fn, readAtFileNodesFlag=readAtFileNodesFlag) 

3232 if ok: 

3233 if not c.openDirectory: 

3234 theDir = g.os_path_finalize(g.os_path_dirname(fn)) # 1341 

3235 c.openDirectory = c.frame.openDirectory = theDir 

3236 else: 

3237 # #970: Never close Leo here. 

3238 g.app.closeLeoWindow(c.frame, finish_quit=False) 

3239 return ok 

3240 #@+node:ekr.20160430063406.1: *3* LM.revertCommander 

3241 def revertCommander(self, c): 

3242 """Revert c to the previously saved contents.""" 

3243 lm = self 

3244 fn = c.mFileName 

3245 # Re-read the file. 

3246 theFile = lm.openAnyLeoFile(fn) 

3247 if theFile: 

3248 c.fileCommands.initIvars() 

3249 c.fileCommands.getLeoFile(theFile, fn, checkOpenFiles=False) 

3250 # Closes the file. 

3251 #@-others 

3252#@+node:ekr.20120223062418.10420: ** class PreviousSettings 

3253class PreviousSettings: 

3254 """ 

3255 A class holding the settings and shortcuts dictionaries 

3256 that are computed in the first pass when loading local 

3257 files and passed to the second pass. 

3258 """ 

3259 

3260 def __init__(self, settingsDict, shortcutsDict): 

3261 if not shortcutsDict or not settingsDict: # #1766: unit tests. 

3262 lm = g.app.loadManager 

3263 settingsDict, shortcutsDict = lm.createDefaultSettingsDicts() 

3264 self.settingsDict = settingsDict 

3265 self.shortcutsDict = shortcutsDict 

3266 

3267 def __repr__(self): 

3268 return ( 

3269 f"<PreviousSettings\n" 

3270 f"{self.settingsDict}\n" 

3271 f"{self.shortcutsDict}\n>") 

3272 

3273 __str__ = __repr__ 

3274#@+node:ekr.20120225072226.10283: ** class RecentFilesManager 

3275class RecentFilesManager: 

3276 """A class to manipulate leoRecentFiles.txt.""" 

3277 

3278 def __init__(self): 

3279 

3280 self.edit_headline = 'Recent files. Do not change this headline!' 

3281 self.groupedMenus = [] # Set in rf.createRecentFilesMenuItems. 

3282 self.recentFiles = [] # List of g.Bunches describing .leoRecentFiles.txt files. 

3283 self.recentFilesMenuName = 'Recent Files' # May be changed later. 

3284 self.recentFileMessageWritten = False # To suppress all but the first message. 

3285 self.write_recent_files_as_needed = False # Will be set later. 

3286 

3287 #@+others 

3288 #@+node:ekr.20041201080436: *3* rf.appendToRecentFiles 

3289 def appendToRecentFiles(self, files): 

3290 rf = self 

3291 files = [theFile.strip() for theFile in files] 

3292 

3293 def munge(name): 

3294 return g.os_path_normpath(name or '').lower() 

3295 

3296 for name in files: 

3297 # Remove all variants of name. 

3298 for name2 in rf.recentFiles[:]: 

3299 if munge(name) == munge(name2): 

3300 rf.recentFiles.remove(name2) 

3301 rf.recentFiles.append(name) 

3302 #@+node:ekr.20120225072226.10289: *3* rf.cleanRecentFiles 

3303 def cleanRecentFiles(self, c): 

3304 """ 

3305 Remove items from the recent files list that no longer exist. 

3306 

3307 This almost never does anything because Leo's startup logic removes 

3308 nonexistent files from the recent files list. 

3309 """ 

3310 result = [z for z in self.recentFiles if g.os_path_exists(z)] 

3311 if result != self.recentFiles: 

3312 for path in result: 

3313 self.updateRecentFiles(path) 

3314 self.writeRecentFilesFile(c) 

3315 #@+node:ekr.20180212141017.1: *3* rf.demangleRecentFiles 

3316 def demangleRecentFiles(self, c, data): 

3317 """Rewrite recent files based on c.config.getData('path-demangle')""" 

3318 changes = [] 

3319 replace = None 

3320 for line in data: 

3321 text = line.strip() 

3322 if text.startswith('REPLACE: '): 

3323 replace = text.split(None, 1)[1].strip() 

3324 if text.startswith('WITH:') and replace is not None: 

3325 with_ = text[5:].strip() 

3326 changes.append((replace, with_)) 

3327 g.es(f"{replace} -> {with_}") 

3328 orig = [z for z in self.recentFiles if z.startswith("/")] 

3329 self.recentFiles = [] 

3330 for i in orig: 

3331 t = i 

3332 for change in changes: 

3333 t = t.replace(*change) 

3334 self.updateRecentFiles(t) 

3335 self.writeRecentFilesFile(c) 

3336 #@+node:ekr.20120225072226.10297: *3* rf.clearRecentFiles 

3337 def clearRecentFiles(self, c): 

3338 """Clear the recent files list, then add the present file.""" 

3339 rf = self 

3340 menu, u = c.frame.menu, c.undoer 

3341 bunch = u.beforeClearRecentFiles() 

3342 recentFilesMenu = menu.getMenu(self.recentFilesMenuName) 

3343 menu.deleteRecentFilesMenuItems(recentFilesMenu) 

3344 rf.recentFiles = [c.fileName()] 

3345 for frame in g.app.windowList: 

3346 rf.createRecentFilesMenuItems(frame.c) 

3347 u.afterClearRecentFiles(bunch) 

3348 # Write the file immediately. 

3349 rf.writeRecentFilesFile(c) 

3350 #@+node:ekr.20120225072226.10301: *3* rf.createRecentFilesMenuItems 

3351 def createRecentFilesMenuItems(self, c): 

3352 rf = self 

3353 menu = c.frame.menu 

3354 recentFilesMenu = menu.getMenu(self.recentFilesMenuName) 

3355 if not recentFilesMenu: 

3356 return 

3357 # Delete all previous entries. 

3358 menu.deleteRecentFilesMenuItems(recentFilesMenu) 

3359 # Create the permanent (static) menu entries. 

3360 table = rf.getRecentFilesTable() 

3361 menu.createMenuEntries(recentFilesMenu, table) 

3362 # Create all the other entries (a maximum of 36). 

3363 accel_ch = string.digits + string.ascii_uppercase # Not a unicode problem. 

3364 i = 0 

3365 n = len(accel_ch) 

3366 # see if we're grouping when files occur in more than one place 

3367 rf_group = c.config.getBool("recent-files-group") 

3368 rf_always = c.config.getBool("recent-files-group-always") 

3369 groupedEntries = rf_group or rf_always 

3370 if groupedEntries: # if so, make dict of groups 

3371 dirCount: Dict[str, Any] = {} 

3372 for fileName in rf.getRecentFiles()[:n]: 

3373 dirName, baseName = g.os_path_split(fileName) 

3374 if baseName not in dirCount: 

3375 dirCount[baseName] = {'dirs': [], 'entry': None} 

3376 dirCount[baseName]['dirs'].append(dirName) 

3377 for name in rf.getRecentFiles()[:n]: 

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

3379 if name.strip() == "": 

3380 continue # happens with empty list/new file 

3381 

3382 def recentFilesCallback(event=None, c=c, name=name): 

3383 c.openRecentFile(fn=name) 

3384 

3385 if groupedEntries: 

3386 dirName, baseName = g.os_path_split(name) 

3387 entry = dirCount[baseName] 

3388 if len(entry['dirs']) > 1 or rf_always: # sub menus 

3389 if entry['entry'] is None: 

3390 entry['entry'] = menu.createNewMenu(baseName, "Recent Files...") 

3391 # acts as a flag for the need to create the menu 

3392 c.add_command(menu.getMenu(baseName), label=dirName, 

3393 command=recentFilesCallback, underline=0) 

3394 else: # single occurence, no submenu 

3395 c.add_command(recentFilesMenu, label=baseName, 

3396 command=recentFilesCallback, underline=0) 

3397 else: # original behavior 

3398 label = f"{accel_ch[i]} {g.computeWindowTitle(name)}" 

3399 c.add_command(recentFilesMenu, label=label, 

3400 command=recentFilesCallback, underline=0) 

3401 i += 1 

3402 if groupedEntries: # store so we can delete them later 

3403 rf.groupedMenus = [z for z in dirCount 

3404 if dirCount[z]['entry'] is not None] 

3405 #@+node:vitalije.20170703115609.1: *3* rf.editRecentFiles 

3406 def editRecentFiles(self, c): 

3407 """ 

3408 Dump recentFiles into new node appended as lastTopLevel, selects it and 

3409 request focus in body. 

3410 

3411 NOTE: command write-edited-recent-files assume that headline of this 

3412 node is not changed by user. 

3413 """ 

3414 rf = self 

3415 p1 = c.lastTopLevel().insertAfter() 

3416 p1.h = self.edit_headline 

3417 p1.b = '\n'.join(rf.recentFiles) 

3418 c.redraw() 

3419 c.selectPosition(p1) 

3420 c.redraw() 

3421 c.bodyWantsFocusNow() 

3422 g.es('edit list and run write-rff to save recentFiles') 

3423 #@+node:ekr.20120225072226.10286: *3* rf.getRecentFiles 

3424 def getRecentFiles(self): 

3425 # Fix #299: Leo loads a deleted file. 

3426 self.recentFiles = [z for z in self.recentFiles 

3427 if g.os_path_exists(z)] 

3428 return self.recentFiles 

3429 #@+node:ekr.20120225072226.10304: *3* rf.getRecentFilesTable 

3430 def getRecentFilesTable(self): 

3431 return ( 

3432 "*clear-recent-files", 

3433 "*clean-recent-files", 

3434 "*demangle-recent-files", 

3435 "*sort-recent-files", 

3436 ("-", None, None), 

3437 ) 

3438 #@+node:ekr.20070224115832: *3* rf.readRecentFiles & helpers 

3439 def readRecentFiles(self, localConfigFile): 

3440 """Read all .leoRecentFiles.txt files.""" 

3441 # The order of files in this list affects the order of the recent files list. 

3442 rf = self 

3443 seen = [] 

3444 localConfigPath = g.os_path_dirname(localConfigFile) 

3445 for path in (g.app.homeLeoDir, g.app.globalConfigDir, localConfigPath): 

3446 if path: 

3447 path = g.os_path_realpath(g.os_path_finalize(path)) 

3448 if path and path not in seen: 

3449 ok = rf.readRecentFilesFile(path) 

3450 if ok: 

3451 seen.append(path) 

3452 if not seen and rf.write_recent_files_as_needed: 

3453 rf.createRecentFiles() 

3454 #@+node:ekr.20061010121944: *4* rf.createRecentFiles 

3455 def createRecentFiles(self): 

3456 """ 

3457 Try to create .leoRecentFiles.txt, in the users home directory, or in 

3458 Leo's config directory if that fails. 

3459 """ 

3460 for theDir in (g.app.homeLeoDir, g.app.globalConfigDir): 

3461 if theDir: 

3462 fn = g.os_path_join(theDir, '.leoRecentFiles.txt') 

3463 try: 

3464 with open(fn, 'w'): 

3465 g.red('created', fn) 

3466 return 

3467 except IOError: 

3468 g.error('can not create', fn) 

3469 g.es_exception() 

3470 #@+node:ekr.20050424115658: *4* rf.readRecentFilesFile 

3471 def readRecentFilesFile(self, path): 

3472 

3473 fileName = g.os_path_join(path, '.leoRecentFiles.txt') 

3474 if not g.os_path_exists(fileName): 

3475 return False 

3476 try: 

3477 with io.open(fileName, encoding='utf-8', mode='r') as f: 

3478 try: # Fix #471. 

3479 lines = f.readlines() 

3480 except Exception: 

3481 lines = None 

3482 except IOError: 

3483 # The file exists, so FileNotFoundError is not possible. 

3484 g.trace('can not open', fileName) 

3485 return False 

3486 if lines and self.sanitize(lines[0]) == 'readonly': 

3487 lines = lines[1:] 

3488 if lines: 

3489 lines = [g.toUnicode(g.os_path_normpath(line)) for line in lines] 

3490 self.appendToRecentFiles(lines) 

3491 return True 

3492 #@+node:ekr.20120225072226.10285: *3* rf.sanitize 

3493 def sanitize(self, name): 

3494 """Return a sanitized file name.""" 

3495 if name is None: 

3496 return None 

3497 name = name.lower() 

3498 for ch in ('-', '_', ' ', '\n'): 

3499 name = name.replace(ch, '') 

3500 return name or None 

3501 #@+node:ekr.20120215072959.12478: *3* rf.setRecentFiles 

3502 def setRecentFiles(self, files): 

3503 """Update the recent files list.""" 

3504 rf = self 

3505 rf.appendToRecentFiles(files) 

3506 #@+node:ekr.20120225072226.10293: *3* rf.sortRecentFiles 

3507 def sortRecentFiles(self, c): 

3508 """Sort the recent files list.""" 

3509 rf = self 

3510 

3511 def key(path): 

3512 # Sort only the base name. That's what will appear in the menu. 

3513 s = g.os_path_basename(path) 

3514 return s.lower() if sys.platform.lower().startswith('win') else s 

3515 

3516 aList = sorted(rf.recentFiles, key=key) 

3517 rf.recentFiles = [] 

3518 for z in reversed(aList): 

3519 rf.updateRecentFiles(z) 

3520 rf.writeRecentFilesFile(c) 

3521 #@+node:ekr.20031218072017.2083: *3* rf.updateRecentFiles 

3522 def updateRecentFiles(self, fileName): 

3523 """Create the RecentFiles menu. May be called with Null fileName.""" 

3524 rf = self 

3525 if g.unitTesting: 

3526 return 

3527 

3528 def munge(name): 

3529 return g.os_path_finalize(name or '').lower() 

3530 

3531 def munge2(name): 

3532 return g.os_path_finalize_join(g.app.loadDir, name or '') 

3533 

3534 # Update the recent files list in all windows. 

3535 

3536 if fileName: 

3537 for frame in g.app.windowList: 

3538 # Remove all versions of the file name. 

3539 for name in rf.recentFiles: 

3540 if ( 

3541 munge(fileName) == munge(name) or 

3542 munge2(fileName) == munge2(name) 

3543 ): 

3544 rf.recentFiles.remove(name) 

3545 rf.recentFiles.insert(0, fileName) 

3546 # Recreate the Recent Files menu. 

3547 rf.createRecentFilesMenuItems(frame.c) 

3548 else: 

3549 for frame in g.app.windowList: 

3550 rf.createRecentFilesMenuItems(frame.c) 

3551 #@+node:vitalije.20170703115616.1: *3* rf.writeEditedRecentFiles 

3552 def writeEditedRecentFiles(self, c): 

3553 """ 

3554 Write content of "edit_headline" node as recentFiles and recreates 

3555 menues. 

3556 """ 

3557 p, rf = c.p, self 

3558 p = g.findNodeAnywhere(c, self.edit_headline) 

3559 if p: 

3560 files = [z for z in p.b.splitlines() if z and g.os_path_exists(z)] 

3561 rf.recentFiles = files 

3562 rf.writeRecentFilesFile(c) 

3563 rf.updateRecentFiles(None) 

3564 c.selectPosition(p) 

3565 c.deleteOutline() 

3566 else: 

3567 g.red('not found:', self.edit_headline) 

3568 #@+node:ekr.20050424114937.2: *3* rf.writeRecentFilesFile & helper 

3569 def writeRecentFilesFile(self, c): 

3570 """Write the appropriate .leoRecentFiles.txt file.""" 

3571 tag = '.leoRecentFiles.txt' 

3572 rf = self 

3573 # tag:#661. Do nothing if in leoBride. 

3574 if g.unitTesting or g.app.inBridge: 

3575 return 

3576 localFileName = c.fileName() 

3577 if localFileName: 

3578 localPath, junk = g.os_path_split(localFileName) 

3579 else: 

3580 localPath = None 

3581 written = False 

3582 seen = [] 

3583 for path in (localPath, g.app.globalConfigDir, g.app.homeLeoDir): 

3584 if path: 

3585 fileName = g.os_path_join(path, tag) 

3586 if g.os_path_exists(fileName) and fileName.lower() not in seen: 

3587 seen.append(fileName.lower()) 

3588 ok = rf.writeRecentFilesFileHelper(fileName) 

3589 if ok: 

3590 written = True 

3591 if not rf.recentFileMessageWritten and not g.unitTesting and not g.app.silentMode: # #459: 

3592 if ok: 

3593 g.es_print(f"wrote recent file: {fileName}") 

3594 else: 

3595 g.error(f"failed to write recent file: {fileName}") 

3596 if written: 

3597 rf.recentFileMessageWritten = True 

3598 else: 

3599 # Attempt to create .leoRecentFiles.txt in the user's home directory. 

3600 if g.app.homeLeoDir: 

3601 fileName = g.os_path_finalize_join(g.app.homeLeoDir, tag) 

3602 if not g.os_path_exists(fileName): 

3603 g.red(f"creating: {fileName}") 

3604 rf.writeRecentFilesFileHelper(fileName) 

3605 #@+node:ekr.20050424131051: *4* rf.writeRecentFilesFileHelper 

3606 def writeRecentFilesFileHelper(self, fileName): 

3607 # Don't update the file if it begins with read-only. 

3608 # 

3609 # Part 1: Return False if the first line is "readonly". 

3610 # It's ok if the file doesn't exist. 

3611 if g.os_path_exists(fileName): 

3612 with io.open(fileName, encoding='utf-8', mode='r') as f: 

3613 try: 

3614 # Fix #471. 

3615 lines = f.readlines() 

3616 except Exception: 

3617 lines = None 

3618 if lines and self.sanitize(lines[0]) == 'readonly': 

3619 return False 

3620 # Part 2: write the files. 

3621 try: 

3622 with io.open(fileName, encoding='utf-8', mode='w') as f: 

3623 s = '\n'.join(self.recentFiles) if self.recentFiles else '\n' 

3624 f.write(g.toUnicode(s)) 

3625 return True 

3626 except IOError: 

3627 g.error('error writing', fileName) 

3628 g.es_exception() 

3629 except Exception: 

3630 g.error('unexpected exception writing', fileName) 

3631 g.es_exception() 

3632 if g.unitTesting: 

3633 raise 

3634 return False 

3635 #@-others 

3636#@+node:ekr.20150514125218.1: ** Top-level-commands 

3637#@+node:ekr.20150514125218.2: *3* ctrl-click-at-cursor 

3638@g.command('ctrl-click-at-cursor') 

3639def ctrlClickAtCursor(event): 

3640 """Simulate a control-click at the cursor.""" 

3641 c = event.get('c') 

3642 if c: 

3643 g.openUrlOnClick(event) 

3644#@+node:ekr.20180213045148.1: *3* demangle-recent-files 

3645@g.command('demangle-recent-files') 

3646def demangle_recent_files_command(event): 

3647 """ 

3648 Path demangling potentially alters the paths in the recent files list 

3649 according to find/replace patterns in the @data path-demangle setting. 

3650 For example: 

3651 

3652 REPLACE: .gnome-desktop 

3653 WITH: My Desktop 

3654 

3655 The default setting specifies no patterns. 

3656 """ 

3657 c = event and event.get('c') 

3658 if c: 

3659 data = c.config.getData('path-demangle') 

3660 if data: 

3661 g.app.recentFilesManager.demangleRecentFiles(c, data) 

3662 else: 

3663 g.es_print('No patterns in @data path-demangle') 

3664#@+node:ekr.20150514125218.3: *3* enable/disable/toggle-idle-time-events 

3665@g.command('disable-idle-time-events') 

3666def disable_idle_time_events(event): 

3667 """Disable default idle-time event handling.""" 

3668 g.app.idle_time_hooks_enabled = False 

3669 

3670@g.command('enable-idle-time-events') 

3671def enable_idle_time_events(event): 

3672 """Enable default idle-time event handling.""" 

3673 g.app.idle_time_hooks_enabled = True 

3674 

3675@g.command('toggle-idle-time-events') 

3676def toggle_idle_time_events(event): 

3677 """Toggle default idle-time event handling.""" 

3678 g.app.idle_time_hooks_enabled = not g.app.idle_time_hooks_enabled 

3679#@+node:ekr.20150514125218.4: *3* join-leo-irc 

3680@g.command('join-leo-irc') 

3681def join_leo_irc(event=None): 

3682 """Open the web page to Leo's irc channel on freenode.net.""" 

3683 import webbrowser 

3684 webbrowser.open("http://webchat.freenode.net/?channels=%23leo&uio=d4") 

3685#@+node:ekr.20150514125218.5: *3* open-url 

3686@g.command('open-url') 

3687def openUrl(event=None): 

3688 """ 

3689 Open the url in the headline or body text of the selected node. 

3690 

3691 Use the headline if it contains a valid url. 

3692 Otherwise, look *only* at the first line of the body. 

3693 """ 

3694 c = event.get('c') 

3695 if c: 

3696 g.openUrl(c.p) 

3697#@+node:ekr.20150514125218.6: *3* open-url-under-cursor 

3698@g.command('open-url-under-cursor') 

3699def openUrlUnderCursor(event=None): 

3700 """Open the url under the cursor.""" 

3701 return g.openUrlOnClick(event) 

3702#@-others 

3703#@@language python 

3704#@@tabwidth -4 

3705#@@pagewidth 70 

3706#@-leo