Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# -*- 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 # Most of these are defined in initApp. 

170 self.backgroundProcessManager = None 

171 # The singleton BackgroundProcessManager instance. 

172 self.commander_cacher = None 

173 # The singleton leoCacher.CommanderCacher instance. 

174 self.commander_db = None 

175 # The singleton db, managed by g.app.commander_cacher. 

176 self.config = None 

177 # The singleton leoConfig instance. 

178 self.db = None 

179 # The singleton global db, managed by g.app.global_cacher. 

180 self.externalFilesController = None 

181 # The singleton ExternalFilesController instance. 

182 self.global_cacher = None 

183 # The singleton leoCacher.GlobalCacher instance. 

184 self.idleTimeManager = None 

185 # The singleton IdleTimeManager instance. 

186 self.ipk = None 

187 # python kernel instance 

188 self.loadManager = None 

189 # The singleton LoadManager instance. 

190 # self.logManager = None 

191 # The singleton LogManager instance. 

192 # self.openWithManager = None 

193 # The singleton OpenWithManager instance. 

194 self.nodeIndices = None 

195 # The singleton nodeIndices instance. 

196 self.pluginsController = None 

197 # The singleton PluginsManager instance. 

198 self.sessionManager = None 

199 # The singleton SessionManager instance. 

200 # The Commands class... 

201 self.commandName = None 

202 # The name of the command being executed. 

203 self.commandInterruptFlag = False 

204 # True: command within a command. 

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

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

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

208 # From leoAtFile.py. 

209 self.atAutoWritersDict = {} 

210 self.writersDispatchDict = {} 

211 # From leoImport.py 

212 self.atAutoDict = {} 

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

214 self.classDispatchDict = {} 

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

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

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

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

219 self.dragging = False # True: dragging. 

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

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

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

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

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

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

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

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

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

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

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

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

232 self.syntax_error_files = [] 

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

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

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

236 # To be moved to the LogManager. 

237 self.log = None 

238 # The LeoFrame containing the present log. 

239 self.logInited = False 

240 # False: all log message go to logWaiting list. 

241 self.logIsLocked = False 

242 # True: no changes to log are allowed. 

243 self.logWaiting = [] 

244 # List of tuples (s, color, newline) waiting to go to a log. 

245 self.printWaiting = [] 

246 # Queue of messages to be sent to the printer. 

247 self.signon = '' 

248 self.signon1 = '' 

249 self.signon2 = '' 

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

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

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

253 from leo.core import leoFrame 

254 from leo.core import leoGui 

255 self.nullGui = leoGui.NullGui() 

256 self.nullLog = leoFrame.NullLog() 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

274 # Define all global data. 

275 self.init_at_auto_names() 

276 self.init_at_file_names() 

277 self.define_global_constants() 

278 self.define_language_delims_dict() 

279 self.define_language_extension_dict() 

280 self.define_extension_dict() 

281 self.define_delegate_language_dict() 

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

283 def define_delegate_language_dict(self): 

284 self.delegate_language_dict = { 

285 # Keys are new language names. 

286 # Values are existing languages in leo/modes. 

287 "less": "css", 

288 "hbs": "html", 

289 "handlebars": "html", 

290 #"rust": "c", 

291 # "vue": "c", 

292 } 

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

294 #@@nobeautify 

295 

296 def define_extension_dict(self): 

297 

298 # Keys are extensions, values are languages 

299 self.extension_dict = { 

300 # "ada": "ada", 

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

302 "ahk": "autohotkey", 

303 "aj": "aspect_j", 

304 "apdl": "apdl", 

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

306 "asp": "asp", 

307 "awk": "awk", 

308 "b": "b", 

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

310 "bash": "shellscript", 

311 "bat": "batch", 

312 "bbj": "bbj", 

313 "bcel": "bcel", 

314 "bib": "bibtex", 

315 "c": "c", 

316 "c++": "cplusplus", 

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

318 "cfg": "config", 

319 "cfm": "coldfusion", 

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

321 "cljs": "clojure", 

322 "cljc": "clojure", 

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

324 "coffee": "coffeescript", 

325 "conf": "apacheconf", 

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

327 "css": "css", 

328 "d": "d", 

329 "dart": "dart", 

330 "e": "eiffel", 

331 "el": "elisp", 

332 "eml": "mail", 

333 "erl": "erlang", 

334 "ex": "elixir", 

335 "f": "fortran", 

336 "f90": "fortran90", 

337 "factor": "factor", 

338 "forth": "forth", 

339 "g": "antlr", 

340 "go": "go", 

341 "groovy": "groovy", 

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

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

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

345 "hs": "haskell", 

346 "html": "html", 

347 "hx": "haxe", 

348 "i": "swig", 

349 "i4gl": "i4gl", 

350 "icn": "icon", 

351 "idl": "idl", 

352 "inf": "inform", 

353 "info": "texinfo", 

354 "ini": "ini", 

355 "io": "io", 

356 "ipynb": "jupyter", 

357 "iss": "inno_setup", 

358 "java": "java", 

359 "jhtml": "jhtml", 

360 "jmk": "jmk", 

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

362 "jsp": "javaserverpage", 

363 "json": "json", 

364 # "jsp": "jsp", 

365 "ksh": "kshell", 

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

367 "latex": "latex", 

368 "less": "css", # McNab 

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

370 "ly": "lilypond", 

371 "m": "matlab", 

372 "mak": "makefile", 

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

374 "ml": "ml", 

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

376 "mod": "modula3", 

377 "mpl": "maple", 

378 "mqsc": "mqsc", 

379 "nqc": "nqc", 

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

381 # "nsi": "nsis2", 

382 "nw": "noweb", 

383 "occ": "occam", 

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

385 "p": "pascal", 

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

387 "php": "php", 

388 "pike": "pike", 

389 "pl": "perl", 

390 "pl1": "pl1", 

391 "po": "gettext", 

392 "pod": "perlpod", 

393 "pov": "povray", 

394 "prg": "foxpro", 

395 "pro": "prolog", 

396 "ps": "postscript", 

397 "psp": "psp", 

398 "ptl": "ptl", 

399 "py": "python", 

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

401 # "pyx": "pyrex", 

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

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

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

405 "rest": "rst", 

406 "rex": "objectrexx", 

407 "rhtml": "rhtml", 

408 "rib": "rib", 

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

410 "sas": "sas", 

411 "scala": "scala", 

412 "scm": "scheme", 

413 "scpt": "applescript", 

414 "sgml": "sgml", 

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

416 "shtml": "shtml", 

417 "sm": "smalltalk", 

418 "splus": "splus", 

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

420 "sqr": "sqr", 

421 "ss": "ssharp", 

422 "ssi": "shtml", 

423 "sty": "latex", 

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

425 # "tcl": "tcltk", 

426 "tex": "latex", 

427 # "tex": "tex", 

428 "tpl": "tpl", 

429 "ts": "typescript", 

430 "txt": "plain", 

431 # "txt": "text", 

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

433 "uc": "uscript", 

434 "v": "verilog", 

435 "vbs": "vbscript", 

436 "vhd": "vhdl", 

437 "vhdl": "vhdl", 

438 "vim": "vim", 

439 "vtl": "velocity", 

440 "w": "cweb", 

441 "wiki": "moin", 

442 "xml": "xml", 

443 "xom": "omnimark", 

444 "xsl": "xsl", 

445 "yaml": "yaml", 

446 "vue": "javascript", 

447 "zpt": "zpt", 

448 } 

449 

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

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

452 # patch, phpsection, progress, props, pseudoplain, 

453 # relax_ng_compact, rtf, svn_commit. 

454 

455 # These have extensions which conflict with other languages. 

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

457 # assembly_macro32: .asm or .a 

458 # assembly_mcs51: .asm or .a 

459 # assembly_parrot: .asm or .a 

460 # assembly_r2000: .asm or .a 

461 # assembly_x86: .asm or .a 

462 # squidconf: .conf 

463 # rpmspec: .rpm 

464 

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

466 # Used by importCommands.languageForExtension. 

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

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

469 self.extra_extension_dict = { 

470 'pod' : 'perl', 

471 'unknown_language': 'none', 

472 'w' : 'c', 

473 } 

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

475 def define_global_constants(self): 

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

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

478 self.prolog_postfix_string = "?>" 

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

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

481 #@@nobeautify 

482 

483 def define_language_delims_dict(self): 

484 

485 self.language_delims_dict = { 

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

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

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

489 "ada" : "--", 

490 "ada95" : "--", 

491 "ahk" : ";", 

492 "antlr" : "// /* */", 

493 "apacheconf" : "#", 

494 "apdl" : "!", 

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

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

497 "aspect_j" : "// /* */", 

498 "assembly_6502" : ";", 

499 "assembly_macro32" : ";", 

500 "assembly_mcs51" : ";", 

501 "assembly_parrot" : "#", 

502 "assembly_r2000" : "#", 

503 "assembly_x86" : ";", 

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

505 "awk" : "#", 

506 "b" : "// /* */", 

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

508 "bbj" : "/* */", 

509 "bcel" : "// /* */", 

510 "bibtex" : "%", 

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

512 "chill" : "/* */", 

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

514 "cobol" : "*", 

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

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

517 "config" : "#", # Leo 4.5.1 

518 "cplusplus" : "// /* */", 

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

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

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

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

523 "cython" : "#", 

524 "d" : "// /* */", 

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

526 "doxygen" : "#", 

527 "eiffel" : "--", 

528 "elisp" : ";", 

529 "erlang" : "%", 

530 "elixir" : "#", 

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

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

533 "fortran" : "C", 

534 "fortran90" : "!", 

535 "foxpro" : "&&", 

536 "gettext" : "# ", 

537 "go" : "//", 

538 "groovy" : "// /* */", 

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

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

541 "haxe" : "// /* */", 

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

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

544 "i4gl" : "-- { }", 

545 "icon" : "#", 

546 "idl" : "// /* */", 

547 "inform" : "!", 

548 "ini" : ";", 

549 "inno_setup" : ";", 

550 "interlis" : "/* */", 

551 "io" : "// */", 

552 "java" : "// /* */", 

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

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

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

556 "jmk" : "#", 

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

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

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

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

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

562 "latex" : "%", 

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

564 "lilypond" : "% %{ %}", 

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

566 "lotos" : "(* *)", 

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

568 "mail" : ">", 

569 "makefile" : "#", 

570 "maple" : "//", 

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

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

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

574 "ml" : "(* *)", 

575 "modula3" : "(* *)", 

576 "moin" : "##", 

577 "mqsc" : "*", 

578 "netrexx" : "-- /* */", 

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

580 "nqc" : "// /* */", 

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

582 "nsis2" : ";", 

583 "objective_c" : "// /* */", 

584 "objectrexx" : "-- /* */", 

585 "occam" : "--", 

586 "omnimark" : ";", 

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

588 "pascal" : "// { }", 

589 "perl" : "#", 

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

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

592 "pike" : "// /* */", 

593 "pl1" : "/* */", 

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

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

596 "pop11" : ";;; /* */", 

597 "postscript" : "%", 

598 "povray" : "// /* */", 

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

600 "prolog" : "% /* */", 

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

602 "ptl" : "#", 

603 "pvwave" : ";", 

604 "pyrex" : "#", 

605 "python" : "#", 

606 "r" : "#", 

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

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

609 "redcode" : ";", 

610 "rest" : ".._", 

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

612 "rib" : "#", 

613 "rpmspec" : "#", 

614 "rst" : ".._", 

615 "rust" : "// /* */", 

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

617 "rview" : "// /* */", 

618 "sas" : "* /* */", 

619 "scala" : "// /* */", 

620 "scheme" : "; #| |#", 

621 "sdl_pr" : "/* */", 

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

623 "shell" : "#", # shell scripts 

624 "shellscript" : "#", 

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

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

627 "smi_mib" : "--", 

628 "splus" : "#", 

629 "sqr" : "!", 

630 "squidconf" : "#", 

631 "ssharp" : "#", 

632 "swig" : "// /* */", 

633 "tcl" : "#", 

634 "tcltk" : "#", 

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

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

637 "texinfo" : "@c", 

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

639 "tsql" : "-- /* */", 

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

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

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

643 "uscript" : "// /* */", 

644 "vbscript" : "'", 

645 "velocity" : "## #* *#", 

646 "verilog" : "// /* */", 

647 "vhdl" : "--", 

648 "vim" : "\"", 

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

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

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

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

653 "yaml" : "#", 

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

655 

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

657 # "cvs_commit" : "", 

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

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

660 # "freemarker" : "", 

661 # "hex" : "", 

662 # "jcl" : "", 

663 # "patch" : "", 

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

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

666 # "pseudoplain" : "", 

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

668 # "rtf" : "", 

669 # "svn_commit" : "", 

670 } 

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

672 #@@nobeautify 

673 

674 def define_language_extension_dict(self): 

675 

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

677 

678 # Keys are languages, values are extensions. 

679 self.language_extension_dict = { 

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

681 "ada" : "ada", 

682 "ada95" : "ada", 

683 "ahk" : "ahk", 

684 "antlr" : "g", 

685 "apacheconf" : "conf", 

686 "apdl" : "apdl", 

687 "applescript" : "scpt", 

688 "asp" : "asp", 

689 "aspect_j" : "aj", 

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

691 "awk" : "awk", 

692 "b" : "b", 

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

694 "bbj" : "bbj", 

695 "bcel" : "bcel", 

696 "bibtex" : "bib", 

697 "c" : "c", 

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

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

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

701 "coldfusion" : "cfm", 

702 "coffeescript" : "coffee", 

703 "config" : "cfg", 

704 "cplusplus" : "c++", 

705 "cpp" : "cpp", 

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

707 "cweb" : "w", 

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

709 "d" : "d", 

710 "dart" : "dart", 

711 "eiffel" : "e", 

712 "elisp" : "el", 

713 "erlang" : "erl", 

714 "elixir" : "ex", 

715 "factor" : "factor", 

716 "forth" : "forth", 

717 "fortran" : "f", 

718 "fortran90" : "f90", 

719 "foxpro" : "prg", 

720 "gettext" : "po", 

721 "go" : "go", 

722 "groovy" : "groovy", 

723 "haskell" : "hs", 

724 "haxe" : "hx", 

725 "html" : "html", 

726 "i4gl" : "i4gl", 

727 "icon" : "icn", 

728 "idl" : "idl", 

729 "inform" : "inf", 

730 "ini" : "ini", 

731 "inno_setup" : "iss", 

732 "io" : "io", 

733 "java" : "java", 

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

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

736 "jhtml" : "jhtml", 

737 "jmk" : "jmk", 

738 "json" : "json", 

739 "jsp" : "jsp", 

740 "jupyter" : "ipynb", 

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

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

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

744 "lilypond" : "ly", 

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

746 "mail" : "eml", 

747 "makefile" : "mak", 

748 "maple" : "mpl", 

749 "matlab" : "m", 

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

751 "ml" : "ml", 

752 "modula3" : "mod", 

753 "moin" : "wiki", 

754 "mqsc" : "mqsc", 

755 "noweb" : "nw", 

756 "nqc" : "nqc", 

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

758 "nsis2" : "nsi", 

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

760 "objectrexx" : "rex", 

761 "occam" : "occ", 

762 "omnimark" : "xom", 

763 "pascal" : "p", 

764 "perl" : "pl", 

765 "perlpod" : "pod", 

766 "php" : "php", 

767 "pike" : "pike", 

768 "pl1" : "pl1", 

769 "plain" : "txt", 

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

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

772 "postscript" : "ps", 

773 "povray" : "pov", 

774 "prolog" : "pro", 

775 "psp" : "psp", 

776 "ptl" : "ptl", 

777 "pyrex" : "pyx", 

778 "python" : "py", 

779 "r" : "r", 

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

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

782 "rhtml" : "rhtml", 

783 "rib" : "rib", 

784 "rst" : "rest", 

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

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

787 "sas" : "sas", 

788 "scala" : "scala", 

789 "scheme" : "scm", 

790 "sgml" : "sgml", 

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

792 "shellscript" : "bash", 

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

794 "smalltalk" : "sm", 

795 "splus" : "splus", 

796 "sqr" : "sqr", 

797 "ssharp" : "ss", 

798 "swig" : "i", 

799 "tcl" : "tcl", 

800 "tcltk" : "tcl", 

801 "tex" : "tex", 

802 "texinfo" : "info", 

803 "text" : "txt", 

804 "tpl" : "tpl", 

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

806 "typescript" : "ts", 

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

808 "uscript" : "uc", 

809 "vbscript" : "vbs", 

810 "velocity" : "vtl", 

811 "verilog" : "v", 

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

813 "vim" : "vim", 

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

815 "xml" : "xml", 

816 "xsl" : "xsl", 

817 "xslt" : "xsl", 

818 "yaml" : "yaml", 

819 "zpt" : "zpt", 

820 } 

821 

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

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

824 # patch, phpsection, progress, props, pseudoplain, 

825 # relax_ng_compact, rtf, svn_commit. 

826 

827 # These have extensions which conflict with other languages. 

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

829 # assembly_macro32: .asm or .a 

830 # assembly_mcs51: .asm or .a 

831 # assembly_parrot: .asm or .a 

832 # assembly_r2000: .asm or .a 

833 # assembly_x86: .asm or .a 

834 # squidconf: .conf 

835 # rpmspec: .rpm 

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

837 def init_at_auto_names(self): 

838 """Init the app.atAutoNames set.""" 

839 self.atAutoNames = set([ 

840 "@auto-rst", "@auto", 

841 ]) 

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

843 def init_at_file_names(self): 

844 """Init the app.atFileNames set.""" 

845 self.atFileNames = set([ 

846 "@asis", 

847 "@edit", 

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

849 "@clean", "@nosent", 

850 "@shadow", 

851 "@thin", 

852 ]) 

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

854 def computeSignon(self): 

855 from leo.core import leoVersion 

856 app = self 

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

858 leoVer = leoVersion.version 

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

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

861 sysVersion = 'Windows ' 

862 try: 

863 # peckj 20140416: determine true OS architecture 

864 # the following code should return the proper architecture 

865 # regardless of whether or not the python architecture matches 

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

867 v = platform.win32_ver() 

868 release, winbuild, sp, ptype = v 

869 true_platform = os.environ['PROCESSOR_ARCHITECTURE'] 

870 try: 

871 true_platform = os.environ['PROCESSOR_ARCHITEw6432'] 

872 except KeyError: 

873 pass 

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

875 except Exception: 

876 pass 

877 else: sysVersion = sys.platform 

878 branch, junk_commit = g.gitInfo() 

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

880 # Compute g.app.signon. 

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

882 if branch: 

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

884 if commit: 

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

886 if date: 

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

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

889 # Compute g.app.signon1. 

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

891 

892 def printSignon(self, verbose=False): 

893 """Print a minimal sigon to the log.""" 

894 app = self 

895 if app.silentMode: 

896 return 

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

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

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

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

901 print('') 

902 print(app.signon) 

903 if verbose: 

904 print(app.signon1) 

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

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

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

908 try: 

909 import curses 

910 assert curses 

911 except Exception: 

912 # g.es_exception() 

913 print('can not import curses.') 

914 if g.isWindows: 

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

916 sys.exit() 

917 try: 

918 from leo.plugins import cursesGui2 

919 ok = cursesGui2.init() 

920 if ok: 

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

922 except Exception: 

923 g.es_exception() 

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

925 sys.exit() 

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

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

928 app = self 

929 try: 

930 from flexx import flx 

931 assert flx 

932 except Exception: 

933 g.es_exception() 

934 print('can not import flexx') 

935 sys.exit(1) 

936 try: 

937 from leo.plugins import leoflexx 

938 assert leoflexx 

939 except Exception: 

940 g.es_exception() 

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

942 sys.exit(1) 

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

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

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

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

947 app = self 

948 argName = app.guiArgName 

949 if g.in_bridge: 

950 return # The bridge will create the gui later. 

951 if app.gui: 

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

953 if argName == 'qt': 

954 app.createQtGui(fileName, verbose=verbose) 

955 elif argName == 'null': 

956 g.app.gui = g.app.nullGui 

957 elif argName.startswith('browser'): 

958 app.createBrowserGui() 

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

960 app.createCursesGui() 

961 elif argName == 'text': 

962 app.createTextGui() 

963 if not app.gui: 

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

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

966 def createNullGuiWithScript(self, script=None): 

967 app = self 

968 app.batchMode = True 

969 app.gui = g.app.nullGui 

970 app.gui.setScript(script) 

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

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

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

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

975 app = self 

976 try: 

977 from leo.core.leoQt import Qt 

978 assert Qt 

979 except Exception: 

980 # #1215: Raise an emergency dialog. 

981 message = 'Can not Import Qt' 

982 print(message) 

983 try: 

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

985 d.run() 

986 except Exception: 

987 g.es_exception() 

988 sys.exit(1) 

989 try: 

990 from leo.plugins import qt_gui 

991 except Exception: 

992 g.es_exception() 

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

994 sys.exit(1) 

995 try: 

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

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

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

999 except ImportError: 

1000 # g.es_exception() 

1001 print('Failed to import editpane') 

1002 # 

1003 # Complete the initialization. 

1004 qt_gui.init() 

1005 if app.gui and fileName and verbose: 

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

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

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

1009 app = self 

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

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

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

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

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

1015 app = self 

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

1017 if fileName and verbose: 

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

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

1020 def setGlobalDb(self): 

1021 """ Create global pickleshare db 

1022 

1023 Usable by:: 

1024 

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

1026 

1027 """ 

1028 # Fixes bug 670108. 

1029 from leo.core import leoCache 

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

1031 g.app.db = g.app.global_cacher.db 

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

1033 g.app.commander_db = g.app.commander_cacher.db 

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

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

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

1037 self.leoID = None 

1038 assert self == g.app 

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

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

1041 for func in table: 

1042 func(verbose) 

1043 if self.leoID: 

1044 return self.leoID 

1045 if useDialog: 

1046 self.setIdFromDialog() 

1047 if self.leoID: 

1048 self.setIDFile() 

1049 return self.leoID 

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

1051 def cleanLeoID(self, id_, tag): 

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

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

1054 try: 

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

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

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

1058 except Exception: 

1059 g.es_exception() 

1060 id_ = '' 

1061 if len(id_) < 3: 

1062 g.EmergencyDialog( 

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

1064 message=( 

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

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

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

1068 return id_ 

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

1070 def setIDFromSys(self, verbose): 

1071 """ 

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

1073 

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

1075 """ 

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

1077 if id_: 

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

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

1080 # cleanLeoID raises a warning dialog. 

1081 if len(id_) > 2: 

1082 self.leoID = id_ 

1083 if verbose: 

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

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

1086 def setIDFromFile(self, verbose): 

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

1088 tag = ".leoID.txt" 

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

1090 if not theDir: 

1091 continue # Do not use the current directory! 

1092 fn = g.os_path_join(theDir, tag) 

1093 try: 

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

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

1096 if not s: 

1097 continue 

1098 # #1404: Ensure valid ID. 

1099 id_ = self.cleanLeoID(s, tag) 

1100 # cleanLeoID raises a warning dialog. 

1101 if len(id_) > 2: 

1102 self.leoID = id_ 

1103 return 

1104 except IOError: 

1105 pass 

1106 except Exception: 

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

1108 g.es_exception() 

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

1110 def setIDFromEnv(self, verbose): 

1111 """Set leoID from environment vars.""" 

1112 try: 

1113 id_ = os.getenv('USER') 

1114 if id_: 

1115 if verbose: 

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

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

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

1119 # cleanLeoID raises a warning dialog. 

1120 if len(id_) > 2: 

1121 self.leoID = id_ 

1122 except Exception: 

1123 pass 

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

1125 def setIdFromDialog(self): 

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

1127 # 

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

1129 self.use_splash_screen = False 

1130 # 

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

1132 while True: 

1133 dialog = g.TkIDDialog() 

1134 dialog.run() 

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

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

1137 if id_ and len(id_) > 2: 

1138 break 

1139 # 

1140 # Put result in g.app.leoID. 

1141 self.leoID = id_ 

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

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

1144 def setIDFile(self): 

1145 """Create leoID.txt.""" 

1146 tag = ".leoID.txt" 

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

1148 if theDir: 

1149 try: 

1150 fn = g.os_path_join(theDir, tag) 

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

1152 f.write(self.leoID) 

1153 if g.os_path_exists(fn): 

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

1155 return 

1156 except IOError: 

1157 pass 

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

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

1160 def setLog(self, log): 

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

1162 if not self.logIsLocked: 

1163 self.log = log 

1164 

1165 def lockLog(self): 

1166 """Disable changes to the log""" 

1167 # print("app.lockLog:") 

1168 self.logIsLocked = True 

1169 

1170 def unlockLog(self): 

1171 """Enable changes to the log""" 

1172 # print("app.unlockLog:") 

1173 self.logIsLocked = False 

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

1175 def writeWaitingLog(self, c): 

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

1177 # 

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

1179 app = self 

1180 if not c or not c.exists: 

1181 return 

1182 if g.unitTesting: 

1183 app.logWaiting = [] 

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

1185 return 

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

1187 table = [ 

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

1189 (app.signon, None), 

1190 (app.signon1, None), 

1191 ] 

1192 table.reverse() 

1193 c.setLog() 

1194 app.logInited = True # Prevent recursive call. 

1195 if not app.silentMode: 

1196 # Write the signon. 

1197 for s, color in table: 

1198 if s: 

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

1200 # Write all the queued log entries. 

1201 for msg in app.logWaiting: 

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

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

1204 kwargs = { 

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

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

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

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

1209 app.logWaiting = [] 

1210 # Essential when opening multiple files... 

1211 g.app.setLog(None) 

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

1213 @property 

1214 def c(self): 

1215 return self.log and self.log.c 

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

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

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

1219 """ 

1220 Attempt to close a Leo window. 

1221 

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

1223 

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

1225 False when closing an already-open-elsewhere file 

1226 during initial load, so UI remains for files 

1227 further along the command line. 

1228 """ 

1229 c = frame.c 

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

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

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

1233 if c.promptingForClose: 

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

1235 return False 

1236 g.app.recentFilesManager.writeRecentFilesFile(c) 

1237 # Make sure .leoRecentFiles.txt is written. 

1238 if c.changed: 

1239 c.promptingForClose = True 

1240 veto = frame.promptForSave() 

1241 c.promptingForClose = False 

1242 if veto: 

1243 return False 

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

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

1246 # 

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

1248 g.app.commander_cacher.commit() 

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

1250 # This may remove frame from the window list. 

1251 if frame in g.app.windowList: 

1252 g.app.destroyWindow(frame) 

1253 g.app.windowList.remove(frame) 

1254 else: 

1255 # #69. 

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

1257 if g.app.windowList: 

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

1259 g.app.selectLeoWindow(c2) 

1260 elif finish_quit and not g.unitTesting: 

1261 g.app.finishQuit() 

1262 return True # The window has been closed. 

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

1264 def destroyAllOpenWithFiles(self): 

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

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

1267 g.pr('destroyAllOpenWithFiles') 

1268 if g.app.externalFilesController: 

1269 g.app.externalFilesController.shut_down() 

1270 g.app.externalFilesController = None 

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

1272 def destroyWindow(self, frame): 

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

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

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

1276 if g.app.externalFilesController: 

1277 g.app.externalFilesController.destroy_frame(frame) 

1278 if frame in g.app.windowList: 

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

1280 # force the window to go away now. 

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

1282 frame.destroySelf() 

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

1284 def finishQuit(self): 

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

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

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

1288 if trace: 

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

1290 if not g.app.killed: 

1291 g.doHook("end1") 

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

1293 g.app.global_cacher.commit_and_close() 

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

1295 g.app.commander_cacher.commit() 

1296 g.app.commander_cacher.close() 

1297 if g.app.ipk: 

1298 g.app.ipk.cleanup_consoles() 

1299 g.app.destroyAllOpenWithFiles() 

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

1301 # pylint: disable=no-member 

1302 g.app.pyzo_close_handler() 

1303 g.app.killed = True 

1304 # Disable all further hooks and events. 

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

1306 # even after the following code. 

1307 if g.app.gui: 

1308 g.app.gui.destroySelf() 

1309 # Calls qtApp.quit() 

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

1311 def forceShutdown(self): 

1312 """ 

1313 Forces an immediate shutdown of Leo at any time. 

1314 

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

1316 """ 

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

1318 app = self 

1319 if trace: 

1320 g.pr('forceShutdown') 

1321 for c in app.commanders(): 

1322 app.forgetOpenFile(c.fileName()) 

1323 # Wait until everything is quiet before really quitting. 

1324 if trace: 

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

1326 g.doHook("end1") 

1327 if trace: 

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

1329 self.log = None # Disable writeWaitingLog 

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

1331 for w in self.windowList[:]: 

1332 if trace: 

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

1334 self.destroyWindow(w) 

1335 if trace: 

1336 g.pr('before finishQuit') 

1337 self.finishQuit() 

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

1339 @cmd('exit-leo') 

1340 @cmd('quit-leo') 

1341 def onQuit(self, event=None): 

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

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

1344 g.trace() 

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

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

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

1348 def commanders(self): 

1349 """ Return list of currently active controllers """ 

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

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

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

1353 def checkForOpenFile(self, c, fn): 

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

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

1356 if g.app.reverting: 

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

1358 g.app.already_open_files = [] 

1359 if (d is None or 

1360 g.unitTesting or 

1361 g.app.batchMode or 

1362 g.app.reverting or 

1363 g.app.inBridge 

1364 ): 

1365 return 

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

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

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

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

1370 # another Leo may have been killed prematurely. 

1371 # Put the file on the global list. 

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

1373 fn = os.path.normpath(fn) 

1374 if fn not in g.app.already_open_files: 

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

1376 g.es(fn) 

1377 g.app.already_open_files.append(fn) 

1378 else: 

1379 g.app.rememberOpenFile(fn) 

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

1381 def forgetOpenFile(self, fn): 

1382 """ 

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

1384 """ 

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

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

1387 if not d or not fn: 

1388 return # #69. 

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

1390 fn = os.path.normpath(fn) 

1391 if fn in aList: 

1392 aList.remove(fn) 

1393 if trace: 

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

1395 d[tag] = aList 

1396 

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

1398 def rememberOpenFile(self, fn): 

1399 

1400 # 

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

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

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

1404 pass 

1405 elif g.app.preReadFlag: 

1406 pass 

1407 else: 

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

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

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

1411 d[tag] = aList 

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

1413 def runAlreadyOpenDialog(self, c): 

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

1415 if g.app.already_open_files: 

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

1417 g.app.already_open_files = [] 

1418 g.app.gui.dismiss_splash_screen() 

1419 message = ( 

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

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

1422 '\n'.join(aList)) 

1423 g.app.gui.runAskOkDialog(c, 

1424 title='Already Open Files', 

1425 message=message, 

1426 text="Ok") 

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

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

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

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

1431 d = g.app.atAutoDict 

1432 for key in d: 

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

1434 func = d.get(key) 

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

1436 return func 

1437 return None 

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

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

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

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

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

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

1444 @cmd('log-listen') 

1445 def listenToLog(self, event=None): 

1446 """ 

1447 A socket listener, listening on localhost. See: 

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

1449 

1450 Start this listener first, then start the broadcaster. 

1451 

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

1453 """ 

1454 app = self 

1455 # Kill any previous listener. 

1456 if app.log_listener: 

1457 g.es_print('Killing previous listener') 

1458 try: 

1459 app.log_listener.kill() 

1460 except Exception: 

1461 g.es_exception() 

1462 app.log_listener = None 

1463 # Start a new listener. 

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

1465 path = g.os_path_finalize_join(app.loadDir, 

1466 '..', 'external', 'log_listener.py') 

1467 app.log_listener = subprocess.Popen( 

1468 [sys.executable, path], 

1469 shell=False, 

1470 universal_newlines=True, 

1471 ) 

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

1473 def makeAllBindings(self): 

1474 """ 

1475 LeoApp.makeAllBindings: 

1476 

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

1478 """ 

1479 app = self 

1480 for c in app.commanders(): 

1481 c.k.makeAllBindings() 

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

1483 def newCommander(self, fileName, 

1484 gui=None, 

1485 parentFrame=None, 

1486 previousSettings=None, 

1487 relativeFileName=None, 

1488 ): 

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

1490 # Create the commander and its subcommanders. 

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

1492 # Timeit reports 0.0175 sec when using a nullGui. 

1493 from leo.core import leoCommands 

1494 c = leoCommands.Commands(fileName, 

1495 gui=gui, 

1496 parentFrame=parentFrame, 

1497 previousSettings=previousSettings, 

1498 relativeFileName=relativeFileName, 

1499 ) 

1500 return c 

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

1502 def selectLeoWindow(self, c): 

1503 frame = c.frame 

1504 frame.deiconify() 

1505 frame.lift() 

1506 c.setLog() 

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

1508 if master: 

1509 # master is a TabbedTopLevel. 

1510 # Selecting the new tab ensures focus is set. 

1511 master.select(c) 

1512 if 1: 

1513 c.initialFocusHelper() 

1514 else: 

1515 c.bodyWantsFocus() 

1516 c.outerUpdate() 

1517 #@-others 

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

1519class LoadManager: 

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

1521 #@+others 

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

1523 def __init__(self): 

1524 

1525 # 

1526 # Global settings & shortcuts dicts... 

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

1528 # 

1529 self.globalSettingsDict = None 

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

1531 self.globalBindingsDict = None 

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

1533 # 

1534 # LoadManager ivars corresponding to user options... 

1535 # 

1536 self.files = [] 

1537 # List of files to be loaded. 

1538 self.options = {} 

1539 # Dictionary of user options. Keys are option names. 

1540 self.old_argv = [] 

1541 # A copy of sys.argv for debugging. 

1542 self.more_cmdline_files = False 

1543 # True when more files remain on the command line to be 

1544 # loaded. If the user is answering "No" to each file as Leo asks 

1545 # "file already open, open again", this must be False for 

1546 # a complete exit to be appropriate (finish_quit=True param for 

1547 # closeLeoWindow()) 

1548 # 

1549 # Themes. 

1550 self.leo_settings_c = None 

1551 self.leo_settings_path = None 

1552 self.my_settings_c = None 

1553 self.my_settings_path = None 

1554 self.theme_c = None 

1555 # #1374. 

1556 self.theme_path = None 

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

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

1559 def completeFileName(self, fileName): 

1560 fileName = g.toUnicode(fileName) 

1561 fileName = g.os_path_finalize(fileName) 

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

1563 return fileName 

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

1565 def computeLeoSettingsPath(self): 

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

1567 # lm = self 

1568 join = g.os_path_finalize_join 

1569 settings_fn = 'leoSettings.leo' 

1570 table = ( 

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

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

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

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

1575 join(g.app.globalConfigDir, settings_fn) 

1576 ) 

1577 for path in table: 

1578 if g.os_path_exists(path): 

1579 break 

1580 else: 

1581 path = None 

1582 return path 

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

1584 def computeMyLeoSettingsPath(self): 

1585 """ 

1586 Return the full path to myLeoSettings.leo. 

1587 

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

1589 """ 

1590 lm = self 

1591 join = g.os_path_finalize_join 

1592 settings_fn = 'myLeoSettings.leo' 

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

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

1595 machine_fn = lm.computeMachineName() + settings_fn 

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

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

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

1599 table = ( 

1600 # First, myLeoSettings.leo in the local directory 

1601 join(localDir, settings_fn), 

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

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

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

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

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

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

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

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

1610 ) 

1611 for path in table: 

1612 if g.os_path_exists(path): 

1613 break 

1614 else: 

1615 path = None 

1616 return path 

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

1618 def computeStandardDirectories(self): 

1619 """ 

1620 Compute the locations of standard directories and 

1621 set the corresponding ivars. 

1622 """ 

1623 lm = self 

1624 join = os.path.join 

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

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

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

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

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

1630 # These use g.app.loadDir... 

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

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

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

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

1635 def computeGlobalConfigDir(self): 

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

1637 if leo_config_dir: 

1638 theDir = leo_config_dir 

1639 else: 

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

1641 if theDir: 

1642 theDir = os.path.abspath(theDir) 

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

1644 theDir = None 

1645 return theDir 

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

1647 def computeHomeDir(self): 

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

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

1650 # Windows searches the HOME, HOMEPATH and HOMEDRIVE 

1651 # environment vars, then gives up. 

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

1653 # Get the indirect reference to the true home. 

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

1655 if home: 

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

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

1658 home = g.os_path_finalize(home) 

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

1660 home = None 

1661 return home 

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

1663 def computeHomeLeoDir(self): 

1664 # lm = self 

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

1666 if g.os_path_exists(homeLeoDir): 

1667 return homeLeoDir 

1668 ok = g.makeAllNonExistentDirectories(homeLeoDir) 

1669 return homeLeoDir if ok else '' # #1450 

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

1671 def computeLeoDir(self): 

1672 # lm = self 

1673 loadDir = g.app.loadDir 

1674 return g.os_path_dirname(loadDir) 

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

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

1677 def computeLoadDir(self): 

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

1679 try: 

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

1681 # __file__ is randomly upper or lower case! 

1682 # The made for an ugly recent files list. 

1683 path = g.__file__ # was leo.__file__ 

1684 if path: 

1685 # Possible fix for bug 735938: 

1686 # Do the following only if path exists. 

1687 #@+<< resolve symlinks >> 

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

1689 if path.endswith('pyc'): 

1690 srcfile = path[:-1] 

1691 if os.path.islink(srcfile): 

1692 path = os.path.realpath(srcfile) 

1693 #@-<< resolve symlinks >> 

1694 if sys.platform == 'win32': 

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

1696 # Convert the drive name to upper case. 

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

1698 path = g.os_path_finalize(path) 

1699 loadDir = g.os_path_dirname(path) 

1700 else: loadDir = None 

1701 if ( 

1702 not loadDir or 

1703 not g.os_path_exists(loadDir) or 

1704 not g.os_path_isdir(loadDir) 

1705 ): 

1706 loadDir = os.getcwd() 

1707 # From Marc-Antoine Parent. 

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

1709 loadDir += "/leo/plugins" 

1710 else: 

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

1712 loadDir = g.os_path_finalize(loadDir) 

1713 return loadDir 

1714 except Exception: 

1715 print("Exception getting load directory") 

1716 raise 

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

1718 def computeMachineName(self): 

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

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

1721 # to give the machine-specific setting name. 

1722 # How can this be worth doing?? 

1723 try: 

1724 name = os.getenv('HOSTNAME') 

1725 if not name: 

1726 name = os.getenv('COMPUTERNAME') 

1727 if not name: 

1728 import socket 

1729 name = socket.gethostname() 

1730 except Exception: 

1731 name = '' 

1732 return name 

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

1734 def computeThemeDirectories(self): 

1735 """ 

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

1737 """ 

1738 join = g.os_path_finalize_join 

1739 home = g.app.homeDir 

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

1741 table = [ 

1742 home, 

1743 join(home, 'themes'), 

1744 join(home, '.leo'), 

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

1746 join(leo, 'themes'), 

1747 ] 

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

1749 # Make sure home has normalized slashes. 

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

1751 def computeThemeFilePath(self): 

1752 """ 

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

1754 

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

1756 

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

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

1759 

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

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

1762 """ 

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

1764 lm = self 

1765 resolve = self.resolve_theme_path 

1766 # 

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

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

1769 if path: 

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

1771 if trace: 

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

1773 return path 

1774 # 

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

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

1777 if path and g.os_path_exists(path): 

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

1779 theme_c = lm.openSettingsFile(path) 

1780 if theme_c: 

1781 settings_d, junk_shortcuts_d = lm.computeLocalSettings( 

1782 c=theme_c, 

1783 settings_d=lm.globalSettingsDict, 

1784 bindings_d=lm.globalBindingsDict, 

1785 localFlag=False, 

1786 ) 

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

1788 if setting: 

1789 tag = theme_c.shortFileName() 

1790 path = resolve(setting, tag=tag) 

1791 if path: 

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

1793 if trace: 

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

1795 return path 

1796 # 

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

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

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

1800 tag = 'myLeoSettings.leo' 

1801 path = resolve(setting, tag=tag) 

1802 if trace: 

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

1804 return path 

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

1806 def resolve_theme_path(self, fn, tag): 

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

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

1809 return None 

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

1811 fn += '.leo' 

1812 for directory in self.computeThemeDirectories(): 

1813 path = g.os_path_join(directory, fn) 

1814 # Normalizes slashes, etc. 

1815 if g.os_path_exists(path): 

1816 return path 

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

1818 return None 

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

1820 def computeWorkbookFileName(self): 

1821 """ 

1822 Return full path to the workbook. 

1823 

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

1825 directory does not exist. 

1826 """ 

1827 # lm = self 

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

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

1830 return None 

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

1832 fn = g.os_path_finalize(fn) 

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

1834 # #1415. 

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

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

1837 def reportDirectories(self): 

1838 """Report directories.""" 

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

1840 for kind, theDir in ( 

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

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

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

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

1845 ): 

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

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

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

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

1850 def computeBindingLetter(self, c, path): 

1851 lm = self 

1852 if not path: 

1853 return 'D' 

1854 path = path.lower() 

1855 table = ( 

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

1857 (' ', 'leoSettings.leo'), 

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

1859 ) 

1860 for letter, path2 in table: 

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

1862 return letter 

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

1864 return 'T' 

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

1866 return '@' 

1867 return 'D' 

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

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

1870 """ 

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

1872 settings_d and bindings_d. 

1873 """ 

1874 lm = self 

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

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

1877 settings_d, bindings_d = lm.createDefaultSettingsDicts() 

1878 if settings_d2: 

1879 if g.app.trace_setting: 

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

1881 val = settings_d2.d.get(key) 

1882 if val: 

1883 fn = g.shortFileName(val.path) 

1884 g.es_print( 

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

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

1887 settings_d = settings_d.copy() 

1888 settings_d.update(settings_d2) 

1889 if shortcuts_d2: 

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

1891 return settings_d, bindings_d 

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

1893 def createDefaultSettingsDicts(self): 

1894 """Create lm.globalSettingsDict & lm.globalBindingsDict.""" 

1895 settings_d = g.app.config.defaultsDict 

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

1897 settings_d.setName('lm.globalSettingsDict') 

1898 bindings_d = g.TypedDict( # was TypedDictOfLists. 

1899 name='lm.globalBindingsDict', 

1900 keyType=type('s'), 

1901 valType=g.BindingInfo, 

1902 ) 

1903 return settings_d, bindings_d 

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

1905 def createSettingsDicts(self, c, localFlag): 

1906 

1907 from leo.core import leoConfig 

1908 if c: 

1909 parser = leoConfig.SettingsTreeParser(c, localFlag) 

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

1911 shortcutsDict, settingsDict = parser.traverse() 

1912 return shortcutsDict, settingsDict 

1913 return None, None 

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

1915 def getPreviousSettings(self, fn): 

1916 """ 

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

1918 pre-reading fn. 

1919 """ 

1920 lm = self 

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

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

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

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

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

1926 exists = g.os_path_exists(fn) 

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

1928 # Open the file usinging a null gui. 

1929 try: 

1930 g.app.preReadFlag = True 

1931 c = lm.openSettingsFile(fn) 

1932 finally: 

1933 g.app.preReadFlag = False 

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

1935 d1, d2 = lm.computeLocalSettings(c, 

1936 lm.globalSettingsDict, 

1937 lm.globalBindingsDict, 

1938 localFlag=True) 

1939 # d1 and d2 are copies. 

1940 d1.setName(settingsName) 

1941 d2.setName(shortcutsName) 

1942 return PreviousSettings(d1, d2) 

1943 # 

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

1945 # Get the settings from the globals settings dicts. 

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

1947 d1 = lm.globalSettingsDict.copy(settingsName) 

1948 d2 = lm.globalBindingsDict.copy(shortcutsName) 

1949 else: 

1950 d1 = d2 = None 

1951 return PreviousSettings(d1, d2) 

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

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

1954 """ 

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

1956 

1957 Both old_d and new_d remain unchanged. 

1958 """ 

1959 lm = self 

1960 if not old_d: 

1961 return new_d 

1962 if not new_d: 

1963 return old_d 

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

1965 if bi_list: 

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

1967 for bi in bi_list: 

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

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

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

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

1972 else: 

1973 pane = '' 

1974 inverted_old_d = lm.invert(old_d) 

1975 inverted_new_d = lm.invert(new_d) 

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

1977 if g.app.trace_binding: 

1978 binding = g.app.trace_binding 

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

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

1981 g.es_print( 

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

1983 f"sets {binding} to None") 

1984 elif localFlag and binding in c.commandsDict: 

1985 d = c.k.computeInverseBindingDict() 

1986 g.trace( 

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

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

1989 else: 

1990 binding = g.app.trace_binding 

1991 stroke = g.KeyStroke(binding) 

1992 bi_list = inverted_new_d.get(stroke) 

1993 if bi_list: 

1994 print('') 

1995 for bi in bi_list: 

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

1997 stroke2 = c.k.prettyPrintKey(stroke) 

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

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

2000 else: 

2001 pane = '' 

2002 g.es_print( 

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

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

2005 print('') 

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

2007 lm.checkForDuplicateShortcuts(c, inverted_new_d) 

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

2009 result = lm.uninvert(inverted_old_d) 

2010 return result 

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

2012 def checkForDuplicateShortcuts(self, c, d): 

2013 """ 

2014 Check for duplicates in an "inverted" dictionary d 

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

2016 

2017 Duplicates happen only if panes conflict. 

2018 """ 

2019 # lm = self 

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

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

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

2023 aList = d.get(ks) 

2024 # A list of bi objects. 

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

2026 if len(aList) > 1: 

2027 for bi in aList2: 

2028 if bi.pane in panes: 

2029 duplicates.append(bi) 

2030 else: 

2031 panes.append(bi.pane) 

2032 if duplicates: 

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

2034 if len(bindings) == 1: 

2035 kind = 'duplicate, (not conflicting)' 

2036 else: 

2037 kind = 'conflicting' 

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

2039 for bi in aList2: 

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

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

2042 def invert(self, d): 

2043 """ 

2044 Invert a shortcut dict whose keys are command names, 

2045 returning a dict whose keys are strokes. 

2046 """ 

2047 result = g.TypedDict( # was TypedDictOfLists. 

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

2049 keyType=g.KeyStroke, 

2050 valType=g.BindingInfo, 

2051 ) 

2052 for commandName in d.keys(): 

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

2054 stroke = bi.stroke # This is canonicalized. 

2055 bi.commandName = commandName # Add info. 

2056 assert stroke 

2057 result.add_to_list(stroke, bi) 

2058 return result 

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

2060 def uninvert(self, d): 

2061 """ 

2062 Uninvert an inverted shortcut dict whose keys are strokes, 

2063 returning a dict whose keys are command names. 

2064 """ 

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

2066 result = g.TypedDict( # was TypedDictOfLists. 

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

2068 keyType=type('commandName'), 

2069 valType=g.BindingInfo, 

2070 ) 

2071 for stroke in d.keys(): 

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

2073 commandName = bi.commandName 

2074 assert commandName 

2075 result.add_to_list(commandName, bi) 

2076 return result 

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

2078 def openSettingsFile(self, fn): 

2079 """ 

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

2081 

2082 The caller must init the c.config object. 

2083 """ 

2084 lm = self 

2085 if not fn: 

2086 return None 

2087 theFile = lm.openAnyLeoFile(fn) 

2088 if not theFile: 

2089 return None # Fix #843. 

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

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

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

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

2094 print(s) 

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

2096 # A useful trace. 

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

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

2099 oldGui = g.app.gui 

2100 g.app.gui = g.app.nullGui 

2101 c = g.app.newCommander(fn) 

2102 frame = c.frame 

2103 frame.log.enable(False) 

2104 g.app.lockLog() 

2105 g.app.openingSettingsFile = True 

2106 try: 

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

2108 readAtFileNodesFlag=False, silent=True) 

2109 # closes theFile. 

2110 finally: 

2111 g.app.openingSettingsFile = False 

2112 g.app.unlockLog() 

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

2114 g.app.gui = oldGui 

2115 return c if ok else None 

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

2117 def readGlobalSettingsFiles(self): 

2118 """ 

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

2120 

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

2122 """ 

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

2124 lm = self 

2125 # Open the standard settings files with a nullGui. 

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

2127 old_commanders = g.app.commanders() 

2128 lm.leo_settings_path = lm.computeLeoSettingsPath() 

2129 lm.my_settings_path = lm.computeMyLeoSettingsPath() 

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

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

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

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

2134 settings_d, bindings_d = lm.createDefaultSettingsDicts() 

2135 for c in commanders: 

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

2137 # *new copies of* settings_d and bindings_d. 

2138 settings_d, bindings_d = lm.computeLocalSettings( 

2139 c, settings_d, bindings_d, localFlag=False) 

2140 # Adjust the name. 

2141 bindings_d.setName('lm.globalBindingsDict') 

2142 lm.globalSettingsDict = settings_d 

2143 lm.globalBindingsDict = bindings_d 

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

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

2146 lm.theme_path = lm.computeThemeFilePath() 

2147 if lm.theme_path: 

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

2149 if lm.theme_c: 

2150 # Merge theme_c's settings into globalSettingsDict. 

2151 settings_d, junk_shortcuts_d = lm.computeLocalSettings( 

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

2153 lm.globalSettingsDict = settings_d 

2154 # Set global vars 

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

2156 # Used by the StyleSheetManager. 

2157 if trace: 

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

2159 # Clear the cache entries for the commanders. 

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

2161 for c in commanders: 

2162 if c not in old_commanders: 

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

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

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

2166 if verbose: 

2167 print(d) 

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

2169 gs = d.get(key) 

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

2171 if d: 

2172 print('') 

2173 else: 

2174 # print(d) 

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

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

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

2178 if verbose: 

2179 print(d) 

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

2181 val = d.get(key) 

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

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

2184 if d: 

2185 print('') 

2186 else: 

2187 print(d) 

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

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

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

2191 lm = self 

2192 # 

2193 # Phase 1: before loading plugins. 

2194 # Scan options, set directories and read settings. 

2195 t1 = time.process_time() 

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

2197 if not lm.isValidPython(): 

2198 return 

2199 lm.doPrePluginsInit(fileName, pymacs) 

2200 # sets lm.options and lm.files 

2201 g.app.computeSignon() 

2202 g.app.printSignon() 

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

2204 return 

2205 if not g.app.gui: 

2206 return 

2207 g.app.disable_redraw = True 

2208 # Disable redraw until all files are loaded. 

2209 # 

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

2211 t2 = time.process_time() 

2212 g.doHook("start1") 

2213 t3 = time.process_time() 

2214 if g.app.killed: 

2215 return 

2216 g.app.idleTimeManager.start() 

2217 # 

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

2219 t3 = time.process_time() 

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

2221 ok = True 

2222 else: 

2223 ok = lm.doPostPluginsInit() 

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

2225 g.app.makeAllBindings() 

2226 if ok and g.app.diff: 

2227 lm.doDiff() 

2228 if not ok: 

2229 return 

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

2231 if g.app.listen_to_log_flag: 

2232 g.app.listenToLog() 

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

2234 t4 = time.process_time() 

2235 print('') 

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

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

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

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

2240 print('') 

2241 # -- quit 

2242 if g.app.quit_after_load: 

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

2244 print('--quit') 

2245 g.app.forceShutdown() 

2246 return 

2247 # #1128: support for restart-leo. 

2248 if not g.app.start_minimized: 

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

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

2251 except Exception: 

2252 pass 

2253 g.app.gui.runMainLoop() 

2254 # For scripts, the gui is a nullGui. 

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

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

2257 def doDiff(self): 

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

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

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

2261 else: 

2262 # This is an unusual situation. 

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

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

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

2266 commanders = g.app.commanders() 

2267 if len(commanders) == 2: 

2268 c = commanders[0] 

2269 c.editFileCommands.compareAnyTwoFiles(event=None) 

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

2271 def doPostPluginsInit(self): 

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

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

2274 lm = self 

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

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

2277 c = c1 = fn = None 

2278 if lm.files: 

2279 try: # #1403. 

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

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

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

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

2284 if c and not c1: # #1416: 

2285 c1 = c 

2286 except Exception: 

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

2288 g.es_exception() 

2289 c = None 

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

2291 g.app.loaded_session = not lm.files 

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

2293 try: # #1403. 

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

2295 if aList: 

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

2297 # #659. 

2298 if g.app.windowList: 

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

2300 else: 

2301 c = c1 = None 

2302 except Exception: 

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

2304 g.es_exception() 

2305 # Enable redraws. 

2306 g.app.disable_redraw = False 

2307 if not c1: 

2308 try: # #1403. 

2309 c1 = lm.openEmptyWorkBook() 

2310 # Calls LM.loadLocalFile. 

2311 except Exception: 

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

2313 g.es_exception() 

2314 c = c1 

2315 if not c: 

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

2317 return False 

2318 # #199. 

2319 g.app.runAlreadyOpenDialog(c1) 

2320 # 

2321 # Final inits... 

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

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

2324 factory = g.app.gui.frameFactory 

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

2326 factory.setTabForCommander(c) 

2327 g.app.logInited = True 

2328 g.app.initComplete = True 

2329 c.setLog() 

2330 c.redraw() 

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

2332 c.initialFocusHelper() 

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

2334 if screenshot_fn: 

2335 lm.make_screen_shot(screenshot_fn) 

2336 return False # Force an immediate exit. 

2337 return True 

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

2339 def make_screen_shot(self, fn): 

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

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

2342 m = g.loadOnePlugin('screenshots') 

2343 m.make_screen_shot(fn) 

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

2345 def openEmptyWorkBook(self): 

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

2347 lm = self 

2348 # Create an empty frame. 

2349 fn = lm.computeWorkbookFileName() 

2350 if not fn: 

2351 return None # #1415 

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

2353 if not c: 

2354 return None # #1201: AttributeError below. 

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

2356 return c 

2357 # Open the cheatsheet. 

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

2359 if not g.os_path_exists(fn): 

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

2361 return None 

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

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

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

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

2366 c2.setCurrentPosition(p2) # 1380 

2367 c2.copyOutline() 

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

2369 p = c.pasteOutline() 

2370 if p: 

2371 c.setCurrentPosition(p) # 1380 

2372 p.v.contract() 

2373 p.v.clearDirty() 

2374 c2.close(new_c=c) 

2375 # Delete the dummy first node. 

2376 root = c.rootPosition() 

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

2378 c.target_language = 'rest' 

2379 c.clearChanged() 

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

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

2382 return c 

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

2384 def doPrePluginsInit(self, fileName, pymacs): 

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

2386 lm = self 

2387 lm.computeStandardDirectories() 

2388 # Scan the options as early as possible. 

2389 lm.options = options = lm.scanOptions(fileName, pymacs) 

2390 # also sets lm.files. 

2391 if options.get('version'): 

2392 return 

2393 script = options.get('script') 

2394 verbose = script is None 

2395 # Init the app. 

2396 lm.initApp(verbose) 

2397 g.app.setGlobalDb() 

2398 if verbose: 

2399 lm.reportDirectories() 

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

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

2402 if g.app.quit_after_load: 

2403 localConfigFile = None 

2404 else: 

2405 lm.readGlobalSettingsFiles() 

2406 # reads only standard settings files, using a null gui. 

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

2408 # that might contain myLeoSettings.leo. 

2409 # Read the recent files file. 

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

2411 g.app.recentFilesManager.readRecentFiles(localConfigFile) 

2412 # Create the gui after reading options and settings. 

2413 lm.createGui(pymacs) 

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

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

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

2417 def createAllImporterData(self): 

2418 """ 

2419 New in Leo 5.5: 

2420 

2421 Create global data structures describing importers and writers. 

2422 """ 

2423 assert g.app.loadDir 

2424 # This is the only data required. 

2425 self.createWritersData() 

2426 # Was an AtFile method. 

2427 self.createImporterData() 

2428 # Was a LeoImportCommands method. 

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

2430 def createImporterData(self): 

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

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

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

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

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

2436 pattern = g.os_path_finalize_join( 

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

2438 for fn in g.glob_glob(pattern): 

2439 sfn = g.shortFileName(fn) 

2440 if sfn != '__init__.py': 

2441 try: 

2442 module_name = sfn[:-3] 

2443 # Important: use importlib to give imported modules 

2444 # their fully qualified names. 

2445 m = importlib.import_module( 

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

2447 self.parse_importer_dict(sfn, m) 

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

2449 except Exception: 

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

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

2452 def parse_importer_dict(self, sfn, m): 

2453 """ 

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

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

2456 """ 

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

2458 if importer_d: 

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

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

2461 # scanner_name = scanner_class.__name__ 

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

2463 if at_auto: 

2464 # Make entries for each @auto type. 

2465 d = g.app.atAutoDict 

2466 for s in at_auto: 

2467 d[s] = scanner_func 

2468 g.app.atAutoDict[s] = scanner_func 

2469 g.app.atAutoNames.add(s) 

2470 if extensions: 

2471 # Make entries for each extension. 

2472 d = g.app.classDispatchDict 

2473 for ext in extensions: 

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

2475 elif sfn not in ( 

2476 # These are base classes, not real plugins. 

2477 'basescanner.py', 

2478 'linescanner.py', 

2479 ): 

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

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

2482 def createWritersData(self): 

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

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

2485 # Do *not* remove this trace. 

2486 if trace: 

2487 # Suppress multiple traces. 

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

2489 g.app.writersDispatchDict = {} 

2490 g.app.atAutoWritersDict = {} 

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

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

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

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

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

2496 for fn in g.glob_glob(pattern): 

2497 sfn = g.shortFileName(fn) 

2498 if sfn != '__init__.py': 

2499 try: 

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

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

2502 self.parse_writer_dict(sfn, m) 

2503 except Exception: 

2504 g.es_exception() 

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

2506 if trace: 

2507 g.trace('LM.writersDispatchDict') 

2508 g.printDict(g.app.writersDispatchDict) 

2509 g.trace('LM.atAutoWritersDict') 

2510 g.printDict(g.app.atAutoWritersDict) 

2511 # Creates problems: See #40. 

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

2513 def parse_writer_dict(self, sfn, m): 

2514 """ 

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

2516 using entries in m.writers_dict. 

2517 """ 

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

2519 if writer_d: 

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

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

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

2523 if at_auto: 

2524 # Make entries for each @auto type. 

2525 d = g.app.atAutoWritersDict 

2526 for s in at_auto: 

2527 aClass = d.get(s) 

2528 if aClass and aClass != scanner_class: 

2529 g.trace( 

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

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

2532 else: 

2533 d[s] = scanner_class 

2534 g.app.atAutoNames.add(s) 

2535 if extensions: 

2536 # Make entries for each extension. 

2537 d = g.app.writersDispatchDict 

2538 for ext in extensions: 

2539 aClass = d.get(ext) 

2540 if aClass and aClass != scanner_class: 

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

2542 else: 

2543 d[ext] = scanner_class 

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

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

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

2547 def createGui(self, pymacs): 

2548 lm = self 

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

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

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

2552 if g.app.gui: 

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

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

2555 g.app.createDefaultGui(__file__) 

2556 else: 

2557 pass 

2558 # This can happen when launching Leo from IPython. 

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

2560 elif gui_option is None: 

2561 if script and not windowFlag: 

2562 # Always use null gui for scripts. 

2563 g.app.createNullGuiWithScript(script) 

2564 else: 

2565 g.app.createDefaultGui(__file__) 

2566 else: 

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

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

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

2570 # lm = self 

2571 if pymacs: 

2572 g.app.createNullGuiWithScript(script=None) 

2573 elif script: 

2574 if windowFlag: 

2575 g.app.createDefaultGui() 

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

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

2578 else: 

2579 g.app.createNullGuiWithScript(script=script) 

2580 else: 

2581 g.app.createDefaultGui() 

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

2583 def getDefaultFile(self): 

2584 # Get the name of the workbook. 

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

2586 fn = g.os_path_finalize(fn) 

2587 if not fn: 

2588 return None 

2589 if g.os_path_exists(fn): 

2590 return fn 

2591 if g.os_path_isabs(fn): 

2592 # Create the file. 

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

2594 return fn 

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

2596 return None 

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

2598 def initApp(self, verbose): 

2599 

2600 self.createAllImporterData() 

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

2602 assert g.app.loadManager 

2603 from leo.core import leoBackground 

2604 from leo.core import leoConfig 

2605 from leo.core import leoNodes 

2606 from leo.core import leoPlugins 

2607 from leo.core import leoSessions 

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

2609 self.setStdStreams() 

2610 if g.app.useIpython: 

2611 from leo.core import leoIPython 

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

2613 assert leoIPython # suppress pyflakes/flake8 warning. 

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

2615 leoPlugins.init() 

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

2617 g.app.setLeoID(verbose=verbose) 

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

2619 g.app.idleTimeManager = IdleTimeManager() 

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

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

2622 g.app.recentFilesManager = RecentFilesManager() 

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

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

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

2626 # Complete the plugins class last. 

2627 g.app.pluginsController.finishCreate() 

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

2629 def scanOptions(self, fileName, pymacs): 

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

2631 lm = self 

2632 table = ( 

2633 '--dock', 

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

2635 '--init-docks', 

2636 '--no-cache', 

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

2638 '--session-restore', 

2639 '--session-save', 

2640 '--use-docks', 

2641 ) 

2642 trace_m = textwrap.dedent("""\ 

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

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

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

2646 """) 

2647 for bad_option in table: 

2648 if bad_option in sys.argv: 

2649 sys.argv.remove(bad_option) 

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

2651 lm.old_argv = sys.argv[:] 

2652 # Automatically implements the --help option. 

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

2654 parser = argparse.ArgumentParser( 

2655 description=description, 

2656 formatter_class=argparse.RawTextHelpFormatter) 

2657 # 

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

2659 self.addOptionsToParser(parser, trace_m) 

2660 args = parser.parse_args() 

2661 # Handle simple args... 

2662 self.doSimpleOptions(args, trace_m) 

2663 # Compute the lm.files ivar. 

2664 lm.files = lm.computeFilesList(fileName) 

2665 # Compute the script. Used twice below. 

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

2667 # Return the dictionary of options. 

2668 return { 

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

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

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

2672 'script': script, 

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

2674 # --select=headline 

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

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

2677 'windowFlag': script and args.script_window, 

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

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

2680 } 

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

2682 #@@nobeautify 

2683 

2684 def addOptionsToParser(self, parser, trace_m): 

2685 """Init the argsparse parser.""" 

2686 add = parser.add_argument 

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

2688 help='list of files') 

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

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

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

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

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

2694 help='start fullscreen') 

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

2696 help='enable ipython support') 

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

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

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

2700 help='start log_listener.py on startup') 

2701 add('--load-type', dest='load_type', metavar='TYPE', 

2702 help='@<file> type for non-outlines') 

2703 add('--maximized', dest='maximized', action='store_true', 

2704 help='start maximized') 

2705 add('--minimized', dest='minimized', action='store_true', 

2706 help='start minimized') 

2707 add('--no-plugins', dest='no_plugins', action='store_true', 

2708 help='disable all plugins') 

2709 add('--no-splash', dest='no_splash', action='store_true', 

2710 help='disable the splash screen') 

2711 add('--quit', dest='quit', action='store_true', 

2712 help='quit immediately after loading') 

2713 add('--screen-shot', dest='screen_shot', metavar='PATH', 

2714 help='take a screen shot and then exit') 

2715 add('--script', dest='script', metavar="PATH", 

2716 help='execute a script and then exit') 

2717 add('--script-window', dest='script_window', action='store_true', 

2718 help='execute script using default gui') 

2719 add('--select', dest='select', metavar='ID', 

2720 help='headline or gnx of node to select') 

2721 add('--silent', dest='silent', action='store_true', 

2722 help='disable all log messages') 

2723 add('--theme', dest='theme', metavar='NAME', 

2724 help='use the named theme file') 

2725 add('--trace', dest='trace', metavar='TRACE-KEY', 

2726 help=f"add one or more strings to g.app.debug. One or more of...\n{trace_m}") 

2727 add('--trace-binding', dest='trace_binding', metavar='KEY', 

2728 help='trace commands bound to a key') 

2729 add('--trace-setting', dest='trace_setting', metavar="NAME", 

2730 help='trace where named setting is set') 

2731 add('--window-size', dest='window_size', metavar='SIZE', 

2732 help='initial window size (height x width)') 

2733 add('--window-spot', dest='window_spot', metavar='SPOT', 

2734 help='initial window position (top x left)') 

2735 add('-v', '--version', dest='version', action='store_true', 

2736 help='print version number and exit') 

2737 #@+node:ekr.20210927034148.3: *6* LM.computeFilesList 

2738 def computeFilesList(self, fileName): 

2739 """Return the list of files on the command line.""" 

2740 lm = self 

2741 files = [] 

2742 if fileName: 

2743 files.append(fileName) 

2744 for arg in sys.argv[1:]: 

2745 if arg and not arg.startswith('-'): 

2746 files.append(arg) 

2747 result = [] 

2748 for z in files: 

2749 # Fix #245: wrong: result.extend(glob.glob(lm.completeFileName(z))) 

2750 aList = g.glob_glob(lm.completeFileName(z)) 

2751 if aList: 

2752 result.extend(aList) 

2753 else: 

2754 result.append(z) 

2755 return [g.os_path_normslashes(z) for z in result] 

2756 #@+node:ekr.20210927034148.4: *6* LM.doGuiOption 

2757 def doGuiOption(self, args): 

2758 gui = args.gui 

2759 if gui: 

2760 gui = gui.lower() 

2761 if gui in ('qt', 'qttabs'): 

2762 gui = 'qt' # Allow qttabs gui. 

2763 elif gui.startswith('browser'): 

2764 pass 

2765 elif gui in ('console', 'curses', 'text', 'null'): 

2766 pass 

2767 else: 

2768 print(f"scanOptions: unknown gui: {gui}. Using qt gui") 

2769 gui = 'qt' 

2770 else: 

2771 gui = 'qt' 

2772 assert gui 

2773 g.app.guiArgName = gui 

2774 return gui 

2775 #@+node:ekr.20210927034148.5: *6* LM.doLoadTypeOption 

2776 def doLoadTypeOption(self, args): 

2777 

2778 s = args.load_type 

2779 s = s.lower() if s else 'edit' 

2780 return '@' + s 

2781 #@+node:ekr.20210927034148.6: *6* LM.doScreenShotOption 

2782 def doScreenShotOption(self, args): 

2783 

2784 # --screen-shot=fn 

2785 s = args.screen_shot 

2786 if s: 

2787 s = s.strip('"') 

2788 return s 

2789 #@+node:ekr.20210927034148.7: *6* LM.doScriptOption 

2790 def doScriptOption(self, args, parser): 

2791 

2792 # --script 

2793 script = args.script 

2794 if script: 

2795 # #1090: use cwd, not g.app.loadDir, to find scripts. 

2796 fn = g.os_path_finalize_join(os.getcwd(), script) 

2797 script, e = g.readFileIntoString(fn, kind='script:', verbose=False) 

2798 if not script: 

2799 print(f"script not found: {fn}") 

2800 sys.exit(1) 

2801 else: 

2802 script = None 

2803 return script 

2804 #@+node:ekr.20210927034148.8: *6* LM.doSimpleOptions 

2805 def doSimpleOptions(self, args, trace_m): 

2806 """These args just set g.app ivars.""" 

2807 # --fail-fast 

2808 g.app.failFast = args.fail_fast 

2809 # --fullscreen 

2810 g.app.start_fullscreen = args.fullscreen 

2811 # --git-diff 

2812 g.app.diff = args.diff 

2813 # --listen-to-log 

2814 g.app.listen_to_log_flag = args.listen_to_log 

2815 # --ipython 

2816 g.app.useIpython = args.ipython 

2817 # --maximized 

2818 g.app.start_maximized = args.maximized 

2819 # --minimized 

2820 g.app.start_minimized = args.minimized 

2821 # --no-plugins 

2822 if args.no_plugins: 

2823 g.app.enablePlugins = False 

2824 # --no-splash: --minimized disables the splash screen 

2825 g.app.use_splash_screen = not args.no_splash and not args.minimized 

2826 # -- quit 

2827 g.app.quit_after_load = args.quit 

2828 # --silent 

2829 g.app.silentMode = args.silent 

2830 # --trace=... 

2831 valid = trace_m.replace(' ', '').replace('\n', '').split(',') 

2832 if args.trace: 

2833 ok = True 

2834 values = args.trace.lstrip('(').lstrip('[').rstrip(')').rstrip(']') 

2835 for val in values.split(','): 

2836 if val in valid: 

2837 g.app.debug.append(val) 

2838 else: 

2839 g.es_print(f"unknown --trace value: {val}") 

2840 ok = False 

2841 if not ok: 

2842 g.es_print('Valid --trace values are...') 

2843 for line in trace_m.split('\n'): 

2844 print(' ', line.rstrip()) 

2845 # 

2846 # These are not bool args. 

2847 # --trace-binding 

2848 g.app.trace_binding = args.trace_binding 

2849 # g.app.config does not exist yet. 

2850 # 

2851 # --trace-setting=setting 

2852 g.app.trace_setting = args.trace_setting 

2853 # g.app.config does not exist yet. 

2854 #@+node:ekr.20210927034148.9: *6* LM.doWindowSpotOption 

2855 def doWindowSpotOption(self, args): 

2856 

2857 # --window-spot 

2858 spot = args.window_spot 

2859 if spot: 

2860 try: 

2861 top, left = spot.split('x') 

2862 spot = int(top), int(left) 

2863 except ValueError: 

2864 print('scanOptions: bad --window-spot:', spot) 

2865 spot = None 

2866 

2867 return spot 

2868 #@+node:ekr.20210927034148.10: *6* LM.doWindowSizeOption 

2869 def doWindowSizeOption(self, args): 

2870 

2871 # --window-size 

2872 windowSize = args.window_size 

2873 if windowSize: 

2874 try: 

2875 h, w = windowSize.split('x') 

2876 windowSize = int(h), int(w) 

2877 except ValueError: 

2878 windowSize = None 

2879 print('scanOptions: bad --window-size:', windowSize) 

2880 return windowSize 

2881 #@+node:ekr.20160718072648.1: *5* LM.setStdStreams 

2882 def setStdStreams(self): 

2883 """ 

2884 Make sure that stdout and stderr exist. 

2885 This is an issue when running Leo with pythonw.exe. 

2886 """ 

2887 # Define class LeoStdOut 

2888 #@+others 

2889 #@+node:ekr.20160718091844.1: *6* class LeoStdOut 

2890 class LeoStdOut: 

2891 """A class to put stderr & stdout to Leo's log pane.""" 

2892 

2893 def __init__(self, kind): 

2894 self.kind = kind 

2895 g.es_print = self.write 

2896 g.pr = self.write 

2897 

2898 def flush(self, *args, **keys): 

2899 pass 

2900 #@+others 

2901 #@+node:ekr.20160718102306.1: *7* LeoStdOut.write 

2902 def write(self, *args, **keys): 

2903 """Put all non-keyword args to the log pane, as in g.es.""" 

2904 # 

2905 # Tracing will lead to unbounded recursion unless 

2906 # sys.stderr has been redirected on the command line. 

2907 app = g.app 

2908 if not app or app.killed: 

2909 return 

2910 if app.gui and app.gui.consoleOnly: 

2911 return 

2912 log = app.log 

2913 # Compute the effective args. 

2914 d = { 

2915 'color': None, 

2916 'commas': False, 

2917 'newline': True, 

2918 'spaces': True, 

2919 'tabName': 'Log', 

2920 } 

2921 # Handle keywords for g.pr and g.es_print. 

2922 d = g.doKeywordArgs(keys, d) 

2923 color: Any = d.get('color') 

2924 if color == 'suppress': 

2925 return 

2926 if log and color is None: 

2927 color = g.actualColor('black') 

2928 color = g.actualColor(color) 

2929 tabName = d.get('tabName') or 'Log' 

2930 newline = d.get('newline') 

2931 s = g.translateArgs(args, d) 

2932 if app.batchMode: 

2933 if log: 

2934 log.put(s) 

2935 elif log and app.logInited: 

2936 # from_redirect is the big difference between this and g.es. 

2937 log.put(s, color=color, tabName=tabName, from_redirect=True) 

2938 else: 

2939 app.logWaiting.append((s, color, newline),) 

2940 #@-others 

2941 #@-others 

2942 if not sys.stdout: 

2943 sys.stdout = sys.__stdout__ = LeoStdOut('stdout') # type:ignore 

2944 if not sys.stderr: 

2945 sys.stderr = sys.__stderr__ = LeoStdOut('stderr') # type:ignore 

2946 #@+node:ekr.20120219154958.10491: *4* LM.isValidPython 

2947 def isValidPython(self): 

2948 if sys.platform == 'cli': 

2949 return True 

2950 message = ( 

2951 f"Leo requires Python {g.minimum_python_version} or higher" 

2952 f"You may download Python from http://python.org/download/") 

2953 try: 

2954 version = '.'.join([str(sys.version_info[i]) for i in (0, 1, 2)]) 

2955 ok = g.CheckVersion(version, g.minimum_python_version) 

2956 if not ok: 

2957 print(message) 

2958 try: 

2959 # g.app.gui does not exist yet. 

2960 d = g.EmergencyDialog( 

2961 title='Python Version Error', 

2962 message=message) 

2963 d.run() 

2964 except Exception: 

2965 g.es_exception() 

2966 return ok 

2967 except Exception: 

2968 print("isValidPython: unexpected exception: g.CheckVersion") 

2969 traceback.print_exc() 

2970 return 0 

2971 #@+node:ekr.20120223062418.10393: *4* LM.loadLocalFile & helper 

2972 def loadLocalFile(self, fn, gui, old_c): 

2973 """Completely read a file, creating the corresonding outline. 

2974 

2975 1. If fn is an existing .leo, .db or .leojs file, read it twice: 

2976 the first time with a NullGui to discover settings, 

2977 the second time with the requested gui to create the outline. 

2978 

2979 2. If fn is an external file: 

2980 get settings from the leoSettings.leo and myLeoSetting.leo, then 

2981 create a "wrapper" outline continain an @file node for the external file. 

2982 

2983 3. If fn is empty: 

2984 get settings from the leoSettings.leo and myLeoSetting.leo or default settings, 

2985 or open an empty outline. 

2986 """ 

2987 lm = self 

2988 # Step 1: Return if the file is already open. 

2989 fn = g.os_path_finalize(fn) 

2990 if fn: 

2991 c = lm.findOpenFile(fn) 

2992 if c: 

2993 return c 

2994 # 

2995 # Step 2: get the previous settings. 

2996 # For .leo files (and zipped .leo files) this pre-reads the file in a null gui. 

2997 # Otherwise, get settings from leoSettings.leo, myLeoSettings.leo, or default settings. 

2998 previousSettings = lm.getPreviousSettings(fn) 

2999 # 

3000 # Step 3: open the outline in the requested gui. 

3001 # For .leo files (and zipped .leo file) this opens the file a second time. 

3002 c = lm.openFileByName(fn, gui, old_c, previousSettings) 

3003 return c 

3004 #@+node:ekr.20120223062418.10394: *5* LM.openFileByName & helpers 

3005 def openFileByName(self, fn, gui, old_c, previousSettings): 

3006 """Read the local file whose full path is fn using the given gui. 

3007 fn may be a Leo file (including .leo or zipped file) or an external file. 

3008 

3009 This is not a pre-read: the previousSettings always exist and 

3010 the commander created here persists until the user closes the outline. 

3011 

3012 Reads the entire outline if fn exists and is a .leo file or zipped file. 

3013 Creates an empty outline if fn is a non-existent Leo file. 

3014 Creates an wrapper outline if fn is an external file, existing or not. 

3015 """ 

3016 lm = self 

3017 # Disable the log. 

3018 g.app.setLog(None) 

3019 g.app.lockLog() 

3020 # Create the a commander for the .leo file. 

3021 # Important. The settings don't matter for pre-reads! 

3022 # For second read, the settings for the file are *exactly* previousSettings. 

3023 c = g.app.newCommander(fileName=fn, gui=gui, previousSettings=previousSettings) 

3024 # Open the file, if possible. 

3025 g.doHook('open0') 

3026 theFile = lm.openAnyLeoFile(fn) 

3027 if isinstance(theFile, sqlite3.Connection): 

3028 # this commander is associated with sqlite db 

3029 c.sqlite_connection = theFile 

3030 # Enable the log. 

3031 g.app.unlockLog() 

3032 c.frame.log.enable(True) 

3033 # Phase 2: Create the outline. 

3034 g.doHook("open1", old_c=None, c=c, new_c=c, fileName=fn) 

3035 if theFile: 

3036 readAtFileNodesFlag = bool(previousSettings) 

3037 # The log is not set properly here. 

3038 ok = lm.readOpenedLeoFile(c, fn, readAtFileNodesFlag, theFile) 

3039 # Call c.fileCommands.openLeoFile to read the .leo file. 

3040 if not ok: 

3041 return None 

3042 else: 

3043 # Create a wrapper .leo file if: 

3044 # a) fn is a .leo file that does not exist or 

3045 # b) fn is an external file, existing or not. 

3046 lm.initWrapperLeoFile(c, fn) 

3047 g.doHook("open2", old_c=None, c=c, new_c=c, fileName=fn) 

3048 # Phase 3: Complete the initialization. 

3049 g.app.writeWaitingLog(c) 

3050 c.setLog() 

3051 lm.createMenu(c, fn) 

3052 lm.finishOpen(c) 

3053 return c 

3054 #@+node:ekr.20120223062418.10405: *6* LM.createMenu 

3055 def createMenu(self, c, fn=None): 

3056 # lm = self 

3057 # Create the menu as late as possible so it can use user commands. 

3058 if not g.doHook("menu1", c=c, p=c.p, v=c.p): 

3059 c.frame.menu.createMenuBar(c.frame) 

3060 g.app.recentFilesManager.updateRecentFiles(fn) 

3061 g.doHook("menu2", c=c, p=c.p, v=c.p) 

3062 g.doHook("after-create-leo-frame", c=c) 

3063 g.doHook("after-create-leo-frame2", c=c) 

3064 # Fix bug 844953: tell Unity which menu to use. 

3065 # c.enableMenuBar() 

3066 #@+node:ekr.20120223062418.10406: *6* LM.findOpenFile 

3067 def findOpenFile(self, fn): 

3068 

3069 def munge(name): 

3070 return g.os_path_normpath(name or '').lower() 

3071 

3072 for frame in g.app.windowList: 

3073 c = frame.c 

3074 if g.os_path_realpath(munge(fn)) == g.os_path_realpath(munge(c.mFileName)): 

3075 # Don't call frame.bringToFront(), it breaks --minimize 

3076 c.setLog() 

3077 # Selecting the new tab ensures focus is set. 

3078 master = getattr(frame.top, 'leo_master', None) 

3079 if master: # master is a TabbedTopLevel. 

3080 master.select(frame.c) 

3081 c.outerUpdate() 

3082 return c 

3083 return None 

3084 #@+node:ekr.20120223062418.10407: *6* LM.finishOpen 

3085 def finishOpen(self, c): 

3086 # lm = self 

3087 k = c.k 

3088 assert k 

3089 # New in Leo 4.6: provide an official way for very late initialization. 

3090 c.frame.tree.initAfterLoad() 

3091 c.initAfterLoad() 

3092 # chapterController.finishCreate must be called after the first real redraw 

3093 # because it requires a valid value for c.rootPosition(). 

3094 if c.chapterController: 

3095 c.chapterController.finishCreate() 

3096 if k: 

3097 k.setDefaultInputState() 

3098 c.initialFocusHelper() 

3099 if k: 

3100 k.showStateAndMode() 

3101 c.frame.initCompleteHint() 

3102 c.outerUpdate() 

3103 # #181: Honor focus requests. 

3104 #@+node:ekr.20120223062418.10408: *6* LM.initWrapperLeoFile 

3105 def initWrapperLeoFile(self, c, fn): 

3106 """ 

3107 Create an empty file if the external fn is empty. 

3108 

3109 Otherwise, create an @edit or @file node for the external file. 

3110 """ 

3111 # lm = self 

3112 # Use the config params to set the size and location of the window. 

3113 frame = c.frame 

3114 frame.setInitialWindowGeometry() 

3115 frame.deiconify() 

3116 frame.lift() 

3117 # #1570: Resize the _new_ frame. 

3118 frame.splitVerticalFlag, r1, r2 = frame.initialRatios() 

3119 frame.resizePanesToRatio(r1, r2) 

3120 if not g.os_path_exists(fn): 

3121 p = c.rootPosition() 

3122 # Create an empty @edit node unless fn is an .leo file. 

3123 # Fix #1070: Use "newHeadline", not fn. 

3124 p.h = "newHeadline" if fn.endswith('.leo') else f"@edit {fn}" 

3125 c.selectPosition(p) 

3126 elif c.looksLikeDerivedFile(fn): 

3127 # 2011/10/10: Create an @file node. 

3128 p = c.importCommands.importDerivedFiles(parent=c.rootPosition(), 

3129 paths=[fn], command=None) # Not undoable. 

3130 if p and p.hasBack(): 

3131 p.back().doDelete() 

3132 p = c.rootPosition() 

3133 if not p: 

3134 return None 

3135 else: 

3136 # Create an @<file> node. 

3137 p = c.rootPosition() 

3138 if p: 

3139 load_type = self.options['load_type'] 

3140 p.setHeadString(f"{load_type} {fn}") 

3141 c.refreshFromDisk() 

3142 c.selectPosition(p) 

3143 

3144 # Fix critical bug 1184855: data loss with command line 'leo somefile.ext' 

3145 # Fix smallish bug 1226816 Command line "leo xxx.leo" creates file xxx.leo.leo. 

3146 c.mFileName = fn if fn.endswith('.leo') else f"{fn}.leo" 

3147 c.wrappedFileName = fn 

3148 c.frame.title = c.computeWindowTitle(c.mFileName) 

3149 c.frame.setTitle(c.frame.title) 

3150 # chapterController.finishCreate must be called after the first real redraw 

3151 # because it requires a valid value for c.rootPosition(). 

3152 if c.config.getBool('use-chapters') and c.chapterController: 

3153 c.chapterController.finishCreate() 

3154 frame.c.clearChanged() 

3155 # Mark the outline clean. 

3156 # This makes it easy to open non-Leo files for quick study. 

3157 return c 

3158 #@+node:ekr.20120223062418.10419: *6* LM.isLeoFile & LM.isZippedFile 

3159 def isLeoFile(self, fn): 

3160 if not fn: 

3161 return False 

3162 return zipfile.is_zipfile(fn) or fn.endswith(('.leo', 'db', '.leojs')) 

3163 

3164 def isZippedFile(self, fn): 

3165 return fn and zipfile.is_zipfile(fn) 

3166 #@+node:ekr.20120224161905.10030: *6* LM.openAnyLeoFile 

3167 def openAnyLeoFile(self, fn): 

3168 """Open a .leo, .leojs or .db file.""" 

3169 lm = self 

3170 if fn.endswith('.db'): 

3171 return sqlite3.connect(fn) 

3172 if lm.isLeoFile(fn) and g.os_path_exists(fn): 

3173 if lm.isZippedFile(fn): 

3174 theFile = lm.openZipFile(fn) 

3175 else: 

3176 theFile = lm.openLeoFile(fn) 

3177 else: 

3178 theFile = None 

3179 return theFile 

3180 #@+node:ekr.20120223062418.10416: *6* LM.openLeoFile 

3181 def openLeoFile(self, fn): 

3182 # lm = self 

3183 try: 

3184 theFile = open(fn, 'rb') 

3185 return theFile 

3186 except IOError: 

3187 # Do not use string + here: it will fail for non-ascii strings! 

3188 if not g.unitTesting: 

3189 g.error("can not open:", fn) 

3190 return None 

3191 #@+node:ekr.20120223062418.10410: *6* LM.openZipFile 

3192 def openZipFile(self, fn): 

3193 # lm = self 

3194 try: 

3195 theFile = zipfile.ZipFile(fn, 'r') 

3196 if not theFile: 

3197 return None 

3198 # Read the file into an StringIO file. 

3199 aList = theFile.namelist() 

3200 name = aList and len(aList) == 1 and aList[0] 

3201 if not name: 

3202 return None 

3203 s = theFile.read(name) 

3204 s2 = g.toUnicode(s, 'utf-8') 

3205 return StringIO(s2) 

3206 except IOError: 

3207 # Do not use string + here: it will fail for non-ascii strings! 

3208 if not g.unitTesting: 

3209 g.error("can not open:", fn) 

3210 return None 

3211 #@+node:ekr.20120223062418.10412: *6* LM.readOpenedLeoFile 

3212 def readOpenedLeoFile(self, c, fn, readAtFileNodesFlag, theFile): 

3213 # New in Leo 4.10: The open1 event does not allow an override of the init logic. 

3214 assert theFile 

3215 # lm = self 

3216 ok = c.fileCommands.openLeoFile(theFile, fn, 

3217 readAtFileNodesFlag=readAtFileNodesFlag) 

3218 # closes file. 

3219 if ok: 

3220 if not c.openDirectory: 

3221 theDir = g.os_path_finalize(g.os_path_dirname(fn)) # 1341 

3222 c.openDirectory = c.frame.openDirectory = theDir 

3223 else: 

3224 g.app.closeLeoWindow(c.frame, finish_quit=False) 

3225 # #970: Never close Leo here. 

3226 return ok 

3227 #@+node:ekr.20160430063406.1: *3* LM.revertCommander 

3228 def revertCommander(self, c): 

3229 """Revert c to the previously saved contents.""" 

3230 lm = self 

3231 fn = c.mFileName 

3232 # Re-read the file. 

3233 theFile = lm.openAnyLeoFile(fn) 

3234 if theFile: 

3235 c.fileCommands.initIvars() 

3236 c.fileCommands.getLeoFile(theFile, fn, checkOpenFiles=False) 

3237 # Closes the file. 

3238 #@-others 

3239#@+node:ekr.20120223062418.10420: ** class PreviousSettings 

3240class PreviousSettings: 

3241 """ 

3242 A class holding the settings and shortcuts dictionaries 

3243 that are computed in the first pass when loading local 

3244 files and passed to the second pass. 

3245 """ 

3246 

3247 def __init__(self, settingsDict, shortcutsDict): 

3248 if not shortcutsDict or not settingsDict: # #1766: unit tests. 

3249 lm = g.app.loadManager 

3250 settingsDict, shortcutsDict = lm.createDefaultSettingsDicts() 

3251 self.settingsDict = settingsDict 

3252 self.shortcutsDict = shortcutsDict 

3253 

3254 def __repr__(self): 

3255 return ( 

3256 f"<PreviousSettings\n" 

3257 f"{self.settingsDict}\n" 

3258 f"{self.shortcutsDict}\n>") 

3259 

3260 __str__ = __repr__ 

3261#@+node:ekr.20120225072226.10283: ** class RecentFilesManager 

3262class RecentFilesManager: 

3263 """A class to manipulate leoRecentFiles.txt.""" 

3264 

3265 def __init__(self): 

3266 

3267 self.edit_headline = 'Recent files. Do not change this headline!' 

3268 # Headline used by 

3269 self.groupedMenus = [] 

3270 # Set in rf.createRecentFilesMenuItems. 

3271 self.recentFiles = [] 

3272 # List of g.Bunches describing .leoRecentFiles.txt files. 

3273 self.recentFilesMenuName = 'Recent Files' 

3274 # May be changed later. 

3275 self.recentFileMessageWritten = False 

3276 # To suppress all but the first message. 

3277 self.write_recent_files_as_needed = False 

3278 # Will be set later. 

3279 #@+others 

3280 #@+node:ekr.20041201080436: *3* rf.appendToRecentFiles 

3281 def appendToRecentFiles(self, files): 

3282 rf = self 

3283 files = [theFile.strip() for theFile in files] 

3284 

3285 def munge(name): 

3286 return g.os_path_normpath(name or '').lower() 

3287 

3288 for name in files: 

3289 # Remove all variants of name. 

3290 for name2 in rf.recentFiles[:]: 

3291 if munge(name) == munge(name2): 

3292 rf.recentFiles.remove(name2) 

3293 rf.recentFiles.append(name) 

3294 #@+node:ekr.20120225072226.10289: *3* rf.cleanRecentFiles 

3295 def cleanRecentFiles(self, c): 

3296 """ 

3297 Remove items from the recent files list that no longer exist. 

3298 

3299 This almost never does anything because Leo's startup logic removes 

3300 nonexistent files from the recent files list. 

3301 """ 

3302 result = [z for z in self.recentFiles if g.os_path_exists(z)] 

3303 if result != self.recentFiles: 

3304 for path in result: 

3305 self.updateRecentFiles(path) 

3306 self.writeRecentFilesFile(c) 

3307 #@+node:ekr.20180212141017.1: *3* rf.demangleRecentFiles 

3308 def demangleRecentFiles(self, c, data): 

3309 """Rewrite recent files based on c.config.getData('path-demangle')""" 

3310 changes = [] 

3311 replace = None 

3312 for line in data: 

3313 text = line.strip() 

3314 if text.startswith('REPLACE: '): 

3315 replace = text.split(None, 1)[1].strip() 

3316 if text.startswith('WITH:') and replace is not None: 

3317 with_ = text[5:].strip() 

3318 changes.append((replace, with_)) 

3319 g.es(f"{replace} -> {with_}") 

3320 orig = [z for z in self.recentFiles if z.startswith("/")] 

3321 self.recentFiles = [] 

3322 for i in orig: 

3323 t = i 

3324 for change in changes: 

3325 t = t.replace(*change) 

3326 self.updateRecentFiles(t) 

3327 self.writeRecentFilesFile(c) 

3328 #@+node:ekr.20120225072226.10297: *3* rf.clearRecentFiles 

3329 def clearRecentFiles(self, c): 

3330 """Clear the recent files list, then add the present file.""" 

3331 rf = self 

3332 menu, u = c.frame.menu, c.undoer 

3333 bunch = u.beforeClearRecentFiles() 

3334 recentFilesMenu = menu.getMenu(self.recentFilesMenuName) 

3335 menu.deleteRecentFilesMenuItems(recentFilesMenu) 

3336 rf.recentFiles = [c.fileName()] 

3337 for frame in g.app.windowList: 

3338 rf.createRecentFilesMenuItems(frame.c) 

3339 u.afterClearRecentFiles(bunch) 

3340 # Write the file immediately. 

3341 rf.writeRecentFilesFile(c) 

3342 #@+node:ekr.20120225072226.10301: *3* rf.createRecentFilesMenuItems 

3343 def createRecentFilesMenuItems(self, c): 

3344 rf = self 

3345 menu = c.frame.menu 

3346 recentFilesMenu = menu.getMenu(self.recentFilesMenuName) 

3347 if not recentFilesMenu: 

3348 return 

3349 # Delete all previous entries. 

3350 menu.deleteRecentFilesMenuItems(recentFilesMenu) 

3351 # Create the permanent (static) menu entries. 

3352 table = rf.getRecentFilesTable() 

3353 menu.createMenuEntries(recentFilesMenu, table) 

3354 # Create all the other entries (a maximum of 36). 

3355 accel_ch = string.digits + string.ascii_uppercase # Not a unicode problem. 

3356 i = 0 

3357 n = len(accel_ch) 

3358 # see if we're grouping when files occur in more than one place 

3359 rf_group = c.config.getBool("recent-files-group") 

3360 rf_always = c.config.getBool("recent-files-group-always") 

3361 groupedEntries = rf_group or rf_always 

3362 if groupedEntries: # if so, make dict of groups 

3363 dirCount: Dict[str, Any] = {} 

3364 for fileName in rf.getRecentFiles()[:n]: 

3365 dirName, baseName = g.os_path_split(fileName) 

3366 if baseName not in dirCount: 

3367 dirCount[baseName] = {'dirs': [], 'entry': None} 

3368 dirCount[baseName]['dirs'].append(dirName) 

3369 for name in rf.getRecentFiles()[:n]: 

3370 # pylint: disable=cell-var-from-loop 

3371 if name.strip() == "": 

3372 continue # happens with empty list/new file 

3373 

3374 def recentFilesCallback(event=None, c=c, name=name): 

3375 c.openRecentFile(fn=name) 

3376 

3377 if groupedEntries: 

3378 dirName, baseName = g.os_path_split(name) 

3379 entry = dirCount[baseName] 

3380 if len(entry['dirs']) > 1 or rf_always: # sub menus 

3381 if entry['entry'] is None: 

3382 entry['entry'] = menu.createNewMenu(baseName, "Recent Files...") 

3383 # acts as a flag for the need to create the menu 

3384 c.add_command(menu.getMenu(baseName), label=dirName, 

3385 command=recentFilesCallback, underline=0) 

3386 else: # single occurence, no submenu 

3387 c.add_command(recentFilesMenu, label=baseName, 

3388 command=recentFilesCallback, underline=0) 

3389 else: # original behavior 

3390 label = f"{accel_ch[i]} {g.computeWindowTitle(name)}" 

3391 c.add_command(recentFilesMenu, label=label, 

3392 command=recentFilesCallback, underline=0) 

3393 i += 1 

3394 if groupedEntries: # store so we can delete them later 

3395 rf.groupedMenus = [z for z in dirCount 

3396 if dirCount[z]['entry'] is not None] 

3397 #@+node:vitalije.20170703115609.1: *3* rf.editRecentFiles 

3398 def editRecentFiles(self, c): 

3399 """ 

3400 Dump recentFiles into new node appended as lastTopLevel, selects it and 

3401 request focus in body. 

3402 

3403 NOTE: command write-edited-recent-files assume that headline of this 

3404 node is not changed by user. 

3405 """ 

3406 rf = self 

3407 p1 = c.lastTopLevel().insertAfter() 

3408 p1.h = self.edit_headline 

3409 p1.b = '\n'.join(rf.recentFiles) 

3410 c.redraw() 

3411 c.selectPosition(p1) 

3412 c.redraw() 

3413 c.bodyWantsFocusNow() 

3414 g.es('edit list and run write-rff to save recentFiles') 

3415 #@+node:ekr.20120225072226.10286: *3* rf.getRecentFiles 

3416 def getRecentFiles(self): 

3417 # Fix #299: Leo loads a deleted file. 

3418 self.recentFiles = [z for z in self.recentFiles 

3419 if g.os_path_exists(z)] 

3420 return self.recentFiles 

3421 #@+node:ekr.20120225072226.10304: *3* rf.getRecentFilesTable 

3422 def getRecentFilesTable(self): 

3423 return ( 

3424 "*clear-recent-files", 

3425 "*clean-recent-files", 

3426 "*demangle-recent-files", 

3427 "*sort-recent-files", 

3428 ("-", None, None), 

3429 ) 

3430 #@+node:ekr.20070224115832: *3* rf.readRecentFiles & helpers 

3431 def readRecentFiles(self, localConfigFile): 

3432 """Read all .leoRecentFiles.txt files.""" 

3433 # The order of files in this list affects the order of the recent files list. 

3434 rf = self 

3435 seen = [] 

3436 localConfigPath = g.os_path_dirname(localConfigFile) 

3437 for path in (g.app.homeLeoDir, g.app.globalConfigDir, localConfigPath): 

3438 if path: 

3439 path = g.os_path_realpath(g.os_path_finalize(path)) 

3440 if path and path not in seen: 

3441 ok = rf.readRecentFilesFile(path) 

3442 if ok: 

3443 seen.append(path) 

3444 if not seen and rf.write_recent_files_as_needed: 

3445 rf.createRecentFiles() 

3446 #@+node:ekr.20061010121944: *4* rf.createRecentFiles 

3447 def createRecentFiles(self): 

3448 """ 

3449 Try to create .leoRecentFiles.txt, in the users home directory, or in 

3450 Leo's config directory if that fails. 

3451 """ 

3452 for theDir in (g.app.homeLeoDir, g.app.globalConfigDir): 

3453 if theDir: 

3454 fn = g.os_path_join(theDir, '.leoRecentFiles.txt') 

3455 try: 

3456 with open(fn, 'w'): 

3457 g.red('created', fn) 

3458 return 

3459 except IOError: 

3460 g.error('can not create', fn) 

3461 g.es_exception() 

3462 #@+node:ekr.20050424115658: *4* rf.readRecentFilesFile 

3463 def readRecentFilesFile(self, path): 

3464 

3465 fileName = g.os_path_join(path, '.leoRecentFiles.txt') 

3466 if not g.os_path_exists(fileName): 

3467 return False 

3468 try: 

3469 with io.open(fileName, encoding='utf-8', mode='r') as f: 

3470 try: # Fix #471. 

3471 lines = f.readlines() 

3472 except Exception: 

3473 lines = None 

3474 except IOError: 

3475 # The file exists, so FileNotFoundError is not possible. 

3476 g.trace('can not open', fileName) 

3477 return False 

3478 if lines and self.sanitize(lines[0]) == 'readonly': 

3479 lines = lines[1:] 

3480 if lines: 

3481 lines = [g.toUnicode(g.os_path_normpath(line)) for line in lines] 

3482 self.appendToRecentFiles(lines) 

3483 return True 

3484 #@+node:ekr.20120225072226.10285: *3* rf.sanitize 

3485 def sanitize(self, name): 

3486 """Return a sanitized file name.""" 

3487 if name is None: 

3488 return None 

3489 name = name.lower() 

3490 for ch in ('-', '_', ' ', '\n'): 

3491 name = name.replace(ch, '') 

3492 return name or None 

3493 #@+node:ekr.20120215072959.12478: *3* rf.setRecentFiles 

3494 def setRecentFiles(self, files): 

3495 """Update the recent files list.""" 

3496 rf = self 

3497 rf.appendToRecentFiles(files) 

3498 #@+node:ekr.20120225072226.10293: *3* rf.sortRecentFiles 

3499 def sortRecentFiles(self, c): 

3500 """Sort the recent files list.""" 

3501 rf = self 

3502 

3503 def key(path): 

3504 # Sort only the base name. That's what will appear in the menu. 

3505 s = g.os_path_basename(path) 

3506 return s.lower() if sys.platform.lower().startswith('win') else s 

3507 

3508 aList = sorted(rf.recentFiles, key=key) 

3509 rf.recentFiles = [] 

3510 for z in reversed(aList): 

3511 rf.updateRecentFiles(z) 

3512 rf.writeRecentFilesFile(c) 

3513 #@+node:ekr.20031218072017.2083: *3* rf.updateRecentFiles 

3514 def updateRecentFiles(self, fileName): 

3515 """Create the RecentFiles menu. May be called with Null fileName.""" 

3516 rf = self 

3517 if g.unitTesting: 

3518 return 

3519 

3520 def munge(name): 

3521 return g.os_path_finalize(name or '').lower() 

3522 

3523 def munge2(name): 

3524 return g.os_path_finalize_join(g.app.loadDir, name or '') 

3525 

3526 # Update the recent files list in all windows. 

3527 

3528 if fileName: 

3529 for frame in g.app.windowList: 

3530 # Remove all versions of the file name. 

3531 for name in rf.recentFiles: 

3532 if ( 

3533 munge(fileName) == munge(name) or 

3534 munge2(fileName) == munge2(name) 

3535 ): 

3536 rf.recentFiles.remove(name) 

3537 rf.recentFiles.insert(0, fileName) 

3538 # Recreate the Recent Files menu. 

3539 rf.createRecentFilesMenuItems(frame.c) 

3540 else: 

3541 for frame in g.app.windowList: 

3542 rf.createRecentFilesMenuItems(frame.c) 

3543 #@+node:vitalije.20170703115616.1: *3* rf.writeEditedRecentFiles 

3544 def writeEditedRecentFiles(self, c): 

3545 """ 

3546 Write content of "edit_headline" node as recentFiles and recreates 

3547 menues. 

3548 """ 

3549 p, rf = c.p, self 

3550 p = g.findNodeAnywhere(c, self.edit_headline) 

3551 if p: 

3552 files = [z for z in p.b.splitlines() if z and g.os_path_exists(z)] 

3553 rf.recentFiles = files 

3554 rf.writeRecentFilesFile(c) 

3555 rf.updateRecentFiles(None) 

3556 c.selectPosition(p) 

3557 c.deleteOutline() 

3558 else: 

3559 g.red('not found:', self.edit_headline) 

3560 #@+node:ekr.20050424114937.2: *3* rf.writeRecentFilesFile & helper 

3561 def writeRecentFilesFile(self, c): 

3562 """Write the appropriate .leoRecentFiles.txt file.""" 

3563 tag = '.leoRecentFiles.txt' 

3564 rf = self 

3565 # tag:#661. Do nothing if in leoBride. 

3566 if g.unitTesting or g.app.inBridge: 

3567 return 

3568 localFileName = c.fileName() 

3569 if localFileName: 

3570 localPath, junk = g.os_path_split(localFileName) 

3571 else: 

3572 localPath = None 

3573 written = False 

3574 seen = [] 

3575 for path in (localPath, g.app.globalConfigDir, g.app.homeLeoDir): 

3576 if path: 

3577 fileName = g.os_path_join(path, tag) 

3578 if g.os_path_exists(fileName) and fileName.lower() not in seen: 

3579 seen.append(fileName.lower()) 

3580 ok = rf.writeRecentFilesFileHelper(fileName) 

3581 if ok: 

3582 written = True 

3583 if not rf.recentFileMessageWritten and not g.unitTesting and not g.app.silentMode: # #459: 

3584 if ok: 

3585 g.es_print(f"wrote recent file: {fileName}") 

3586 else: 

3587 g.error(f"failed to write recent file: {fileName}") 

3588 if written: 

3589 rf.recentFileMessageWritten = True 

3590 else: 

3591 # Attempt to create .leoRecentFiles.txt in the user's home directory. 

3592 if g.app.homeLeoDir: 

3593 fileName = g.os_path_finalize_join(g.app.homeLeoDir, tag) 

3594 if not g.os_path_exists(fileName): 

3595 g.red(f"creating: {fileName}") 

3596 rf.writeRecentFilesFileHelper(fileName) 

3597 #@+node:ekr.20050424131051: *4* rf.writeRecentFilesFileHelper 

3598 def writeRecentFilesFileHelper(self, fileName): 

3599 # Don't update the file if it begins with read-only. 

3600 # 

3601 # Part 1: Return False if the first line is "readonly". 

3602 # It's ok if the file doesn't exist. 

3603 if g.os_path_exists(fileName): 

3604 with io.open(fileName, encoding='utf-8', mode='r') as f: 

3605 try: 

3606 # Fix #471. 

3607 lines = f.readlines() 

3608 except Exception: 

3609 lines = None 

3610 if lines and self.sanitize(lines[0]) == 'readonly': 

3611 return False 

3612 # Part 2: write the files. 

3613 try: 

3614 with io.open(fileName, encoding='utf-8', mode='w') as f: 

3615 s = '\n'.join(self.recentFiles) if self.recentFiles else '\n' 

3616 f.write(g.toUnicode(s)) 

3617 return True 

3618 except IOError: 

3619 g.error('error writing', fileName) 

3620 g.es_exception() 

3621 except Exception: 

3622 g.error('unexpected exception writing', fileName) 

3623 g.es_exception() 

3624 if g.unitTesting: 

3625 raise 

3626 return False 

3627 #@-others 

3628#@+node:ekr.20150514125218.1: ** Top-level-commands 

3629#@+node:ekr.20150514125218.2: *3* ctrl-click-at-cursor 

3630@g.command('ctrl-click-at-cursor') 

3631def ctrlClickAtCursor(event): 

3632 """Simulate a control-click at the cursor.""" 

3633 c = event.get('c') 

3634 if c: 

3635 g.openUrlOnClick(event) 

3636#@+node:ekr.20180213045148.1: *3* demangle-recent-files 

3637@g.command('demangle-recent-files') 

3638def demangle_recent_files_command(event): 

3639 """ 

3640 Path demangling potentially alters the paths in the recent files list 

3641 according to find/replace patterns in the @data path-demangle setting. 

3642 For example: 

3643 

3644 REPLACE: .gnome-desktop 

3645 WITH: My Desktop 

3646 

3647 The default setting specifies no patterns. 

3648 """ 

3649 c = event and event.get('c') 

3650 if c: 

3651 data = c.config.getData('path-demangle') 

3652 if data: 

3653 g.app.recentFilesManager.demangleRecentFiles(c, data) 

3654 else: 

3655 g.es_print('No patterns in @data path-demangle') 

3656#@+node:ekr.20150514125218.3: *3* enable/disable/toggle-idle-time-events 

3657@g.command('disable-idle-time-events') 

3658def disable_idle_time_events(event): 

3659 """Disable default idle-time event handling.""" 

3660 g.app.idle_time_hooks_enabled = False 

3661 

3662@g.command('enable-idle-time-events') 

3663def enable_idle_time_events(event): 

3664 """Enable default idle-time event handling.""" 

3665 g.app.idle_time_hooks_enabled = True 

3666 

3667@g.command('toggle-idle-time-events') 

3668def toggle_idle_time_events(event): 

3669 """Toggle default idle-time event handling.""" 

3670 g.app.idle_time_hooks_enabled = not g.app.idle_time_hooks_enabled 

3671#@+node:ekr.20150514125218.4: *3* join-leo-irc 

3672@g.command('join-leo-irc') 

3673def join_leo_irc(event=None): 

3674 """Open the web page to Leo's irc channel on freenode.net.""" 

3675 import webbrowser 

3676 webbrowser.open("http://webchat.freenode.net/?channels=%23leo&uio=d4") 

3677#@+node:ekr.20150514125218.5: *3* open-url 

3678@g.command('open-url') 

3679def openUrl(event=None): 

3680 """ 

3681 Open the url in the headline or body text of the selected node. 

3682 

3683 Use the headline if it contains a valid url. 

3684 Otherwise, look *only* at the first line of the body. 

3685 """ 

3686 c = event.get('c') 

3687 if c: 

3688 g.openUrl(c.p) 

3689#@+node:ekr.20150514125218.6: *3* open-url-under-cursor 

3690@g.command('open-url-under-cursor') 

3691def openUrlUnderCursor(event=None): 

3692 """Open the url under the cursor.""" 

3693 return g.openUrlOnClick(event) 

3694#@-others 

3695#@@language python 

3696#@@tabwidth -4 

3697#@@pagewidth 70 

3698#@-leo