Coverage for C:\leo.repo\leo-editor\leo\core\leoCommands.py: 46%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

2716 statements  

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

2#@+leo-ver=5-thin 

3#@+node:ekr.20031218072017.2810: * @file leoCommands.py 

4#@@first 

5#@+<< imports >> 

6#@+node:ekr.20040712045933: ** << imports >> (leoCommands) 

7import itertools 

8import os 

9import re 

10import subprocess 

11import sys 

12import tabnanny 

13import tempfile 

14import time 

15import tokenize 

16from typing import Any, Dict, Callable, List, Optional, Set, Tuple 

17from leo.core import leoGlobals as g 

18from leo.core import leoNodes 

19 # The leoCommands ctor now does most leo.core.leo* imports, 

20 # thereby breaking circular dependencies. 

21#@-<< imports >> 

22 

23def cmd(name) -> Callable: 

24 """Command decorator for the Commands class.""" 

25 return g.new_cmd_decorator(name, ['c',]) 

26 

27#@+others 

28#@+node:ekr.20160514120615.1: ** class Commands 

29class Commands: 

30 """ 

31 A per-outline class that implements most of Leo's commands. The 

32 "c" predefined object is an instance of this class. 

33 

34 c.initObjects() creates sucommanders corresponding to files in the 

35 leo/core and leo/commands. All of Leo's core code is accessible 

36 via this class and its subcommanders. 

37 

38 g.app.pluginsController is Leo's plugins controller. Many plugins 

39 inject controllers objects into the Commands class. These are 

40 another kind of subcommander. 

41 

42 The @g..commander_command decorator injects methods into this class. 

43 """ 

44 #@+others 

45 #@+node:ekr.20031218072017.2811: *3* c.Birth & death 

46 #@+node:ekr.20031218072017.2812: *4* c.__init__ & helpers 

47 def __init__(self, fileName, 

48 gui=None, 

49 parentFrame=None, 

50 previousSettings=None, 

51 relativeFileName=None, 

52 ): 

53 t1 = time.process_time() 

54 c = self 

55 # Official ivars. 

56 self._currentPosition: Optional["leoNodes.Position"] = None 

57 self._topPosition: Optional["leoNodes.Position"] = None 

58 self.frame = None 

59 self.parentFrame = parentFrame # New in Leo 6.0. 

60 self.gui = gui or g.app.gui 

61 self.ipythonController = None # Set only by the ipython plugin. 

62 # The order of these calls does not matter. 

63 c.initCommandIvars() 

64 c.initDebugIvars() 

65 c.initDocumentIvars() 

66 c.initEventIvars() 

67 c.initFileIvars(fileName, relativeFileName) 

68 c.initOptionsIvars() 

69 c.initObjectIvars() 

70 # Init the settings *before* initing the objects. 

71 c.initSettings(previousSettings) 

72 # Initialize all subsidiary objects, including subcommanders. 

73 c.initObjects(self.gui) 

74 assert c.frame 

75 assert c.frame.c 

76 # Complete the init! 

77 t2 = time.process_time() 

78 c.finishCreate() # Slightly slow. 

79 t3 = time.process_time() 

80 if 'speed' in g.app.debug: 

81 print('c.__init__') 

82 print( 

83 f" 1: {t2-t1:5.2f}\n" # 0.00 sec. 

84 f" 2: {t3-t2:5.2f}\n" # 0.53 sec: c.finishCreate. 

85 f"total: {t3-t1:5.2f}" 

86 ) 

87 #@+node:ekr.20120217070122.10475: *5* c.computeWindowTitle 

88 def computeWindowTitle(self, fileName): 

89 """Set the window title and fileName.""" 

90 if fileName: 

91 title = g.computeWindowTitle(fileName) 

92 else: 

93 s = "untitled" 

94 n = g.app.numberOfUntitledWindows 

95 if n > 0: 

96 s += str(n) 

97 title = g.computeWindowTitle(s) 

98 g.app.numberOfUntitledWindows = n + 1 

99 return title 

100 #@+node:ekr.20120217070122.10473: *5* c.initCommandIvars 

101 def initCommandIvars(self): 

102 """Init ivars used while executing a command.""" 

103 self.commandsDict: dict[str, Callable] = {} # Keys are command names, values are functions. 

104 self.disableCommandsMessage = '' # The presence of this message disables all commands. 

105 self.hookFunction: Optional[Callable] = None # One of three places that g.doHook looks for hook functions. 

106 self.ignoreChangedPaths = False # True: disable path changed message in at.WriteAllHelper. 

107 self.inCommand = False # Interlocks to prevent premature closing of a window. 

108 self.outlineToNowebDefaultFileName: str = "noweb.nw" # For Outline To Noweb dialog. 

109 # For hoist/dehoist commands. 

110 # Affects drawing routines and find commands, but *not* generators. 

111 self.hoistStack: List[Any] = [] # Stack of g.Bunches to be root of drawn tree. 

112 # For outline navigation. 

113 self.navPrefix: str = '' # Must always be a string. 

114 self.navTime: Optional[float] = None 

115 self.recent_commands_list: List[str] = [] # List of command names. 

116 self.sqlite_connection = None 

117 #@+node:ekr.20120217070122.10466: *5* c.initDebugIvars 

118 def initDebugIvars(self): 

119 """Init Commander debugging ivars.""" 

120 self.command_count = 0 

121 self.scanAtPathDirectivesCount = 0 

122 self.trace_focus_count = 0 

123 #@+node:ekr.20120217070122.10471: *5* c.initDocumentIvars 

124 def initDocumentIvars(self): 

125 """Init per-document ivars.""" 

126 self.expansionLevel = 0 # The expansion level of this outline. 

127 self.expansionNode = None # The last node we expanded or contracted. 

128 self.nodeConflictList = [] # List of nodes with conflicting read-time data. 

129 self.nodeConflictFileName: Optional[str] = None # The fileName for c.nodeConflictList. 

130 self.user_dict = {} # Non-persistent dictionary for free use by scripts and plugins. 

131 #@+node:ekr.20120217070122.10467: *5* c.initEventIvars 

132 def initEventIvars(self): 

133 """Init ivars relating to gui events.""" 

134 self.configInited = False 

135 self.doubleClickFlag = False 

136 self.exists = True # Indicate that this class exists and has not been destroyed. 

137 self.in_qt_dialog = False # True: in a qt dialog. 

138 self.loading = False # True: we are loading a file: disables c.setChanged() 

139 self.promptingForClose = False # True: lock out additional closing dialogs. 

140 # 

141 # Flags for c.outerUpdate... 

142 self.enableRedrawFlag = True 

143 self.requestCloseWindow = False 

144 self.requestedFocusWidget = None 

145 self.requestLaterRedraw = False 

146 #@+node:ekr.20120217070122.10472: *5* c.initFileIvars 

147 def initFileIvars(self, fileName, relativeFileName): 

148 """Init file-related ivars of the commander.""" 

149 self.changed = False # True: the outline has changed since the last save. 

150 self.ignored_at_file_nodes: List["leoNodes.Position"] = [] # List of nodes for c.raise_error_dialogs. 

151 self.import_error_nodes: List["leoNodes.Position"] = [] # List of nodes for c.raise_error_dialogs. 

152 self.last_dir = None # The last used directory. 

153 self.mFileName: str = fileName or '' # Do _not_ use os_path_norm: it converts an empty path to '.' (!!) 

154 self.mRelativeFileName = relativeFileName or '' # 

155 self.openDirectory: Optional[str] = None # 

156 self.orphan_at_file_nodes: List["leoNodes.Position"] = [] # List of orphaned nodes for c.raise_error_dialogs. 

157 self.wrappedFileName: Optional[str] = None # The name of the wrapped file, for wrapper commanders. 

158 

159 #@+node:ekr.20120217070122.10469: *5* c.initOptionsIvars 

160 def initOptionsIvars(self): 

161 """Init Commander ivars corresponding to user options.""" 

162 self.fixed = False 

163 self.fixedWindowPosition = [] 

164 self.forceExecuteEntireBody = False 

165 self.focus_border_color = 'white' 

166 self.focus_border_width = 1 # pixels 

167 self.outlineHasInitialFocus = False 

168 self.page_width = 132 

169 self.sparse_find = True 

170 self.sparse_move = True 

171 self.sparse_spell = True 

172 self.stayInTreeAfterSelect = False 

173 self.tab_width = -4 

174 self.tangle_batch_flag = False 

175 self.target_language = "python" 

176 self.untangle_batch_flag = False 

177 self.vim_mode = False 

178 #@+node:ekr.20120217070122.10468: *5* c.initObjectIvars 

179 def initObjectIvars(self): 

180 # These ivars are set later by leoEditCommands.createEditCommanders 

181 self.abbrevCommands = None 

182 self.editCommands = None 

183 self.db = {} # May be set to a PickleShare instance later. 

184 self.bufferCommands = None 

185 self.chapterCommands = None 

186 self.controlCommands = None 

187 self.convertCommands = None 

188 self.debugCommands = None 

189 self.editFileCommands = None 

190 self.evalController = None 

191 self.gotoCommands = None 

192 self.helpCommands = None 

193 self.keyHandler = self.k = None 

194 self.keyHandlerCommands = None 

195 self.killBufferCommands = None 

196 self.leoCommands = None 

197 self.macroCommands = None 

198 self.miniBufferWidget = None 

199 self.printingController = None 

200 self.queryReplaceCommands = None 

201 self.rectangleCommands = None 

202 self.searchCommands = None 

203 self.spellCommands = None 

204 self.leoTestManager = None 

205 self.vimCommands = None 

206 #@+node:ekr.20120217070122.10470: *5* c.initObjects 

207 #@@nobeautify 

208 

209 def initObjects(self, gui): 

210 

211 c = self 

212 self.hiddenRootNode = leoNodes.VNode(context=c, gnx='hidden-root-vnode-gnx') 

213 self.hiddenRootNode.h = '<hidden root vnode>' 

214 # Create the gui frame. 

215 title = c.computeWindowTitle(c.mFileName) 

216 if not g.app.initing: 

217 g.doHook("before-create-leo-frame", c=c) 

218 self.frame = gui.createLeoFrame(c, title) 

219 assert self.frame 

220 assert self.frame.c == c 

221 from leo.core import leoHistory 

222 self.nodeHistory = leoHistory.NodeHistory(c) 

223 self.initConfigSettings() 

224 c.setWindowPosition() # Do this after initing settings. 

225 

226 # Break circular import dependencies by doing imports here. 

227 # All these imports take almost 3/4 sec in the leoBridge. 

228 

229 from leo.core import leoAtFile 

230 from leo.core import leoBeautify # So decorators are executed. 

231 assert leoBeautify # for pyflakes. 

232 from leo.core import leoChapters 

233 # User commands... 

234 from leo.commands import abbrevCommands 

235 from leo.commands import bufferCommands 

236 from leo.commands import checkerCommands # The import *is* required to define commands. 

237 assert checkerCommands # To suppress a pyflakes warning. 

238 from leo.commands import controlCommands 

239 from leo.commands import convertCommands 

240 from leo.commands import debugCommands 

241 from leo.commands import editCommands 

242 from leo.commands import editFileCommands 

243 from leo.commands import gotoCommands 

244 from leo.commands import helpCommands 

245 from leo.commands import keyCommands 

246 from leo.commands import killBufferCommands 

247 from leo.commands import rectangleCommands 

248 from leo.commands import spellCommands 

249 # Import files to execute @g.commander_command decorators 

250 from leo.core import leoCompare 

251 assert leoCompare 

252 from leo.core import leoDebugger 

253 assert leoDebugger 

254 from leo.commands import commanderEditCommands 

255 assert commanderEditCommands 

256 from leo.commands import commanderFileCommands 

257 assert commanderFileCommands 

258 from leo.commands import commanderHelpCommands 

259 assert commanderHelpCommands 

260 from leo.commands import commanderOutlineCommands 

261 assert commanderOutlineCommands 

262 # Other subcommanders. 

263 from leo.core import leoFind # Leo 4.11.1 

264 from leo.core import leoKeys 

265 from leo.core import leoFileCommands 

266 from leo.core import leoImport 

267 from leo.core import leoMarkup 

268 from leo.core import leoPersistence 

269 from leo.core import leoPrinting 

270 from leo.core import leoRst 

271 from leo.core import leoShadow 

272 from leo.core import leoUndo 

273 from leo.core import leoVim 

274 # Import commands.testCommands to define commands. 

275 import leo.commands.testCommands as testCommands 

276 assert testCommands # For pylint. 

277 # Define the subcommanders. 

278 self.keyHandler = self.k = leoKeys.KeyHandlerClass(c) 

279 self.chapterController = leoChapters.ChapterController(c) 

280 self.shadowController = leoShadow.ShadowController(c) 

281 self.fileCommands = leoFileCommands.FileCommands(c) 

282 self.findCommands = leoFind.LeoFind(c) 

283 self.atFileCommands = leoAtFile.AtFile(c) 

284 self.importCommands = leoImport.LeoImportCommands(c) 

285 self.markupCommands = leoMarkup.MarkupCommands(c) 

286 self.persistenceController = leoPersistence.PersistenceDataController(c) 

287 self.printingController = leoPrinting.PrintingController(c) 

288 self.rstCommands = leoRst.RstCommands(c) 

289 self.vimCommands = leoVim.VimCommands(c) 

290 # User commands 

291 self.abbrevCommands = abbrevCommands.AbbrevCommandsClass(c) 

292 self.bufferCommands = bufferCommands.BufferCommandsClass(c) 

293 self.controlCommands = controlCommands.ControlCommandsClass(c) 

294 self.convertCommands = convertCommands.ConvertCommandsClass(c) 

295 self.debugCommands = debugCommands.DebugCommandsClass(c) 

296 self.editCommands = editCommands.EditCommandsClass(c) 

297 self.editFileCommands = editFileCommands.EditFileCommandsClass(c) 

298 self.gotoCommands = gotoCommands.GoToCommands(c) 

299 self.helpCommands = helpCommands.HelpCommandsClass(c) 

300 self.keyHandlerCommands = keyCommands.KeyHandlerCommandsClass(c) 

301 self.killBufferCommands = killBufferCommands.KillBufferCommandsClass(c) 

302 self.rectangleCommands = rectangleCommands.RectangleCommandsClass(c) 

303 self.spellCommands = spellCommands.SpellCommandsClass(c) 

304 self.undoer = leoUndo.Undoer(c) 

305 # Create the list of subcommanders. 

306 self.subCommanders = [ 

307 self.abbrevCommands, 

308 self.atFileCommands, 

309 self.bufferCommands, 

310 self.chapterController, 

311 self.controlCommands, 

312 self.convertCommands, 

313 self.debugCommands, 

314 self.editCommands, 

315 self.editFileCommands, 

316 self.fileCommands, 

317 self.findCommands, 

318 self.gotoCommands, 

319 self.helpCommands, 

320 self.importCommands, 

321 self.keyHandler, 

322 self.keyHandlerCommands, 

323 self.killBufferCommands, 

324 self.persistenceController, 

325 self.printingController, 

326 self.rectangleCommands, 

327 self.rstCommands, 

328 self.shadowController, 

329 self.spellCommands, 

330 self.vimCommands, 

331 self.undoer, 

332 ] 

333 # Other objects 

334 # A list of other classes that have a reloadSettings method 

335 c.configurables = c.subCommanders[:] 

336 c.db = g.app.commander_cacher.get_wrapper(c) 

337 # #2485: Load the free_layout plugin in the proper context. 

338 # g.app.pluginsController.loadOnePlugin won't work here. 

339 try: 

340 g.app.pluginsController.loadingModuleNameStack.append('leo.plugins.free_layout') 

341 from leo.plugins import free_layout 

342 c.free_layout = free_layout.FreeLayoutController(c) 

343 finally: 

344 g.app.pluginsController.loadingModuleNameStack.pop() 

345 if hasattr(g.app.gui, 'styleSheetManagerClass'): 

346 self.styleSheetManager = g.app.gui.styleSheetManagerClass(c) 

347 self.subCommanders.append(self.styleSheetManager) 

348 else: 

349 self.styleSheetManager = None 

350 #@+node:ekr.20140815160132.18837: *5* c.initSettings 

351 def initSettings(self, previousSettings): 

352 """Init the settings *before* initing the objects.""" 

353 c = self 

354 from leo.core import leoConfig 

355 c.config = leoConfig.LocalConfigManager(c, previousSettings) 

356 g.app.config.setIvarsFromSettings(c) 

357 #@+node:ekr.20031218072017.2814: *4* c.__repr__ & __str__ 

358 def __repr__(self): 

359 return f"Commander {id(self)}: {repr(self.mFileName)}" 

360 

361 __str__ = __repr__ 

362 #@+node:ekr.20050920093543: *4* c.finishCreate & helpers 

363 def finishCreate(self): 

364 """ 

365 Finish creating the commander and all sub-objects. 

366 This is the last step in the startup process. 

367 """ 

368 c, k = self, self.k 

369 assert c.gui 

370 assert k 

371 t1 = time.process_time() 

372 c.frame.finishCreate() # Slightly slow. 

373 t2 = time.process_time() 

374 c.miniBufferWidget = c.frame.miniBufferWidget # Will be None for nullGui. 

375 # Only c.abbrevCommands needs a finishCreate method. 

376 c.abbrevCommands.finishCreate() 

377 # Finish other objects... 

378 c.createCommandNames() 

379 k.finishCreate() 

380 c.findCommands.finishCreate() 

381 if not c.gui.isNullGui: 

382 # #2485: register idle_focus_helper in the proper context. 

383 try: 

384 g.app.pluginsController.loadingModuleNameStack.append('leo.core.leoCommands') 

385 g.registerHandler('idle', c.idle_focus_helper) 

386 finally: 

387 g.app.pluginsController.loadingModuleNameStack.pop() 

388 if getattr(c.frame, 'menu', None): 

389 c.frame.menu.finishCreate() 

390 if getattr(c.frame, 'log', None): 

391 c.frame.log.finishCreate() 

392 c.undoer.clearUndoState() 

393 if c.vimCommands and c.vim_mode: 

394 c.vimCommands.finishCreate() # Menus must exist at this point. 

395 # Do not call chapterController.finishCreate here: 

396 # It must be called after the first real redraw. 

397 g.check_cmd_instance_dict(c, g) 

398 c.bodyWantsFocus() 

399 t3 = time.process_time() 

400 if 'speed' in g.app.debug: 

401 print('c.finishCreate') 

402 print( 

403 f" 1: {t2-t1:5.2f}\n" # 0.20 sec: qtGui.finishCreate. 

404 f" 2: {t3-t2:5.2f}\n" # 0.16 sec: everything else. 

405 f"total: {t3-t1:5.2f}" 

406 ) 

407 #@+node:ekr.20140815160132.18835: *5* c.createCommandNames 

408 def createCommandNames(self): 

409 """ 

410 Create all entries in c.commandsDict. 

411 Do *not* clear c.commandsDict here. 

412 """ 

413 for commandName, func in g.global_commands_dict.items(): 

414 self.k.registerCommand(commandName, func) 

415 #@+node:ekr.20051007143620: *5* c.printCommandsDict 

416 def printCommandsDict(self): 

417 c = self 

418 print('Commands...') 

419 for key in sorted(c.commandsDict): 

420 command = c.commandsDict.get(key) 

421 print(f"{key:30} = {command.__name__ if command else '<None>'}") 

422 print('') 

423 #@+node:ekr.20041130173135: *4* c.hash 

424 # This is a bad idea. 

425 

426 def hash(self): 

427 c = self 

428 if c.mFileName: 

429 return g.os_path_finalize(c.mFileName).lower() # #1341. 

430 return 0 

431 #@+node:ekr.20110509064011.14563: *4* c.idle_focus_helper & helpers 

432 idle_focus_count = 0 

433 

434 def idle_focus_helper(self, tag, keys): 

435 """An idle-tme handler that ensures that focus is *somewhere*.""" 

436 trace = 'focus' in g.app.debug 

437 trace_inactive_focus = False # Too disruptive for --trace-focus 

438 trace_in_dialog = False # Not useful enough for --trace-focus 

439 c = self 

440 assert tag == 'idle' 

441 if g.unitTesting: 

442 return 

443 if keys.get('c') != c: 

444 if trace: 

445 g.trace('no c') 

446 return 

447 self.idle_focus_count += 1 

448 if c.in_qt_dialog: 

449 if trace and trace_in_dialog: 

450 g.trace('in_qt_dialog') 

451 return 

452 w = g.app.gui.get_focus(at_idle=True) 

453 if g.app.gui.active: 

454 # Always call trace_idle_focus. 

455 self.trace_idle_focus(w) 

456 if w and self.is_unusual_focus(w): 

457 if trace: 

458 w_class = w and w.__class__.__name__ 

459 g.trace('***** unusual focus', w_class) 

460 # Fix bug 270: Leo's keyboard events doesn't work after "Insert" 

461 # on headline and Alt+Tab, Alt+Tab 

462 # Presumably, intricate details of Qt event handling are involved. 

463 # The focus was in the tree, so put the focus back in the tree. 

464 c.treeWantsFocusNow() 

465 # elif not w and active: 

466 # c.bodyWantsFocusNow() 

467 elif trace and trace_inactive_focus: 

468 w_class = w and w.__class__.__name__ 

469 count = c.idle_focus_count 

470 g.trace(f"{count} inactive focus: {w_class}") 

471 #@+node:ekr.20160427062131.1: *5* c.is_unusual_focus 

472 def is_unusual_focus(self, w): 

473 """Return True if w is not in an expected place.""" 

474 # 

475 # #270: Leo's keyboard events doesn't work after "Insert" 

476 # on headline and Alt+Tab, Alt+Tab 

477 # 

478 # #276: Focus lost...in Nav text input 

479 from leo.plugins import qt_frame 

480 return isinstance(w, qt_frame.QtTabBarWrapper) 

481 #@+node:ekr.20150403063658.1: *5* c.trace_idle_focus 

482 last_unusual_focus = None 

483 # last_no_focus = False 

484 

485 def trace_idle_focus(self, w): 

486 """Trace the focus for w, minimizing chatter.""" 

487 from leo.core.leoQt import QtWidgets 

488 from leo.plugins import qt_frame 

489 trace = 'focus' in g.app.debug 

490 trace_known = False 

491 c = self 

492 table = (QtWidgets.QWidget, qt_frame.LeoQTreeWidget,) 

493 count = c.idle_focus_count 

494 if w: 

495 w_class = w and w.__class__.__name__ 

496 c.last_no_focus = False 

497 if self.is_unusual_focus(w): 

498 if trace: 

499 g.trace(f"{count} unusual focus: {w_class}") 

500 else: 

501 c.last_unusual_focus = None 

502 if isinstance(w, table): 

503 if trace and trace_known: 

504 g.trace(f"{count} known focus: {w_class}") 

505 elif trace: 

506 g.trace(f"{count} unknown focus: {w_class}") 

507 else: 

508 if trace: 

509 g.trace(f"{count:3} no focus") 

510 #@+node:ekr.20081005065934.1: *4* c.initAfterLoad 

511 def initAfterLoad(self): 

512 """Provide an offical hook for late inits of the commander.""" 

513 pass 

514 #@+node:ekr.20090213065933.6: *4* c.initConfigSettings 

515 def initConfigSettings(self): 

516 """Init all cached commander config settings.""" 

517 c = self 

518 getBool = c.config.getBool 

519 getColor = c.config.getColor 

520 getData = c.config.getData 

521 getInt = c.config.getInt 

522 c.autoindent_in_nocolor = getBool('autoindent-in-nocolor-mode') 

523 c.collapse_nodes_after_move = getBool('collapse-nodes-after-move') 

524 c.collapse_on_lt_arrow = getBool('collapse-on-lt-arrow', default=True) 

525 c.contractVisitedNodes = getBool('contractVisitedNodes') 

526 c.fixedWindowPositionData = getData('fixedWindowPosition') 

527 c.focus_border_color = getColor('focus-border-color') or 'red' 

528 c.focus_border_command_state_color = getColor( 

529 'focus-border-command-state-color') or 'blue' 

530 c.focus_border_overwrite_state_color = getColor( 

531 'focus-border-overwrite-state-color') or 'green' 

532 c.focus_border_width = getInt('focus-border-width') or 1 # pixels 

533 c.forceExecuteEntireBody = getBool('force-execute-entire-body', default=False) 

534 c.make_node_conflicts_node = getBool('make-node-conflicts-node', default=True) 

535 c.outlineHasInitialFocus = getBool('outline-pane-has-initial-focus') 

536 c.page_width = getInt('page-width') or 132 

537 # c.putBitsFlag = getBool('put-expansion-bits-in-leo-files', default=True) 

538 c.sparse_move = getBool('sparse-move-outline-left') 

539 c.sparse_find = getBool('collapse-nodes-during-finds') 

540 c.sparce_spell = getBool('collapse-nodes-while-spelling') 

541 c.stayInTreeAfterSelect = getBool('stayInTreeAfterSelect') 

542 c.smart_tab = getBool('smart-tab') 

543 c.tab_width = getInt('tab-width') or -4 

544 c.verbose_check_outline = getBool('verbose-check-outline', default=False) 

545 c.vim_mode = getBool('vim-mode', default=False) 

546 c.write_script_file = getBool('write-script-file') 

547 #@+node:ekr.20090213065933.7: *4* c.setWindowPosition 

548 def setWindowPosition(self): 

549 c = self 

550 if c.fixedWindowPositionData: 

551 try: 

552 aList = [z.strip() for z in c.fixedWindowPositionData if z.strip()] 

553 w, h, l, t = aList 

554 c.fixedWindowPosition = int(w), int(h), int(l), int(t) # type:ignore 

555 except Exception: 

556 g.error('bad @data fixedWindowPosition', 

557 repr(self.fixedWindowPosition)) 

558 else: 

559 c.windowPosition = 500, 700, 50, 50 # width,height,left,top. 

560 #@+node:ekr.20210530065748.1: *3* @cmd c.execute-general-script 

561 @cmd('execute-general-script') 

562 def execute_general_script_command(self, event=None): 

563 """ 

564 Execute c.p and all its descendants as a script. 

565 

566 Create a temp file if c.p is not an @<file> node. 

567 

568 @data exec-script-commands associates commands with langauges. 

569 

570 @data exec-script-patterns provides patterns to create clickable 

571 links for error messages. 

572 

573 Set the cwd before calling the command. 

574 """ 

575 c, p, tag = self, self.p, 'execute-general-script' 

576 

577 def get_setting_for_language(setting: str): 

578 """ 

579 Return the setting from the given @data setting. 

580 The first colon ends each key. 

581 """ 

582 for s in c.config.getData(setting) or []: 

583 key, val = s.split(':', 1) 

584 if key.strip() == language: 

585 return val.strip() 

586 return None 

587 

588 # Get the language and extension. 

589 d = c.scanAllDirectives(p) 

590 language: str = d.get('language') 

591 if not language: 

592 print(f"{tag}: No language in effect at {p.h}") 

593 return 

594 ext = g.app.language_extension_dict.get(language) 

595 if not ext: 

596 print(f"{tag}: No extention for {language}") 

597 return 

598 # Get the command. 

599 command = get_setting_for_language('exec-script-commands') 

600 if not command: 

601 print(f"{tag}: No command for {language} in @data exec-script-commands") 

602 return 

603 # Get the optional pattern. 

604 regex = get_setting_for_language('exec-script-patterns') 

605 # Set the directory, if possible. 

606 if p.isAnyAtFileNode(): 

607 path = g.fullPath(c, p) 

608 directory = os.path.dirname(path) 

609 else: 

610 directory = None 

611 c.general_script_helper(command, ext, language, 

612 directory=directory, regex=regex, root=p) 

613 #@+node:vitalije.20190924191405.1: *3* @cmd execute-pytest 

614 @cmd('execute-pytest') 

615 def execute_pytest(self, event=None): 

616 """Using pytest, execute all @test nodes for p, p's parents and p's subtree.""" 

617 c = self 

618 

619 def it(p): 

620 for p1 in p.self_and_parents(): 

621 if p1.h.startswith('@test '): 

622 yield p1 

623 return 

624 for p1 in p.subtree(): 

625 if p1.h.startswith('@test '): 

626 yield p1 

627 

628 try: 

629 for p in it(c.p): 

630 self.execute_single_pytest(p) 

631 except ImportError: 

632 g.es('pytest needs to be installed') 

633 return 

634 

635 def execute_single_pytest(self, p): 

636 c = self 

637 from _pytest.config import get_config 

638 from _pytest.assertion.rewrite import rewrite_asserts 

639 import ast 

640 cfg = get_config() 

641 script = g.getScript(c, p, useSentinels=False) + ( 

642 '\n' 

643 'ls = dict(locals())\n' 

644 'failed = 0\n' 

645 'for x in ls:\n' 

646 ' if x.startswith("test_") and callable(ls[x]):\n' 

647 ' try:\n' 

648 ' ls[x]()\n' 

649 ' except AssertionError as e:\n' 

650 ' failed += 1\n' 

651 ' g.es(f"-------{p.h[6:].strip()}/{x} failed---------")\n' 

652 ' g.es(str(e))\n' 

653 'if failed == 0:\n' 

654 ' g.es("all tests passed")\n' 

655 'else:\n' 

656 ' g.es(f"failed:{failed} tests")\n') 

657 

658 fname = g.os_path_finalize_join(g.app.homeLeoDir, 'leoPytestScript.py') 

659 with open(fname, 'wt', encoding='utf8') as out: 

660 out.write(script) 

661 tree = ast.parse(script, filename=fname) 

662 # A mypy bug: the script can be str. 

663 rewrite_asserts(tree, script, config=cfg) # type:ignore 

664 co = compile(tree, fname, "exec", dont_inherit=True) 

665 sys.path.insert(0, '.') 

666 sys.path.insert(0, c.frame.openDirectory) 

667 try: 

668 exec(co, {'c': c, 'g': g, 'p': p}) 

669 except KeyboardInterrupt: 

670 g.es('interrupted') 

671 except Exception: 

672 g.handleScriptException(c, p, script, script) 

673 finally: 

674 del sys.path[:2] 

675 #@+node:ekr.20171123135625.4: *3* @cmd execute-script & public helpers 

676 @cmd('execute-script') 

677 def executeScript(self, event=None, 

678 args=None, p=None, script=None, useSelectedText=True, 

679 define_g=True, define_name='__main__', 

680 silent=False, namespace=None, raiseFlag=False, 

681 runPyflakes=True, 

682 ): 

683 """ 

684 Execute a *Leo* script, written in python. 

685 Keyword args: 

686 args=None Not None: set script_args in the execution environment. 

687 p=None Get the script from p.b, unless script is given. 

688 script=None None: use script in p.b or c.p.b 

689 useSelectedText=True False: use all the text in p.b or c.p.b. 

690 define_g=True True: define g for the script. 

691 define_name='__main__' Not None: define the name symbol. 

692 silent=False No longer used. 

693 namespace=None Not None: execute the script in this namespace. 

694 raiseFlag=False True: reraise any exceptions. 

695 runPyflakes=True True: run pyflakes if allowed by setting. 

696 """ 

697 c, script1 = self, script 

698 if runPyflakes: 

699 run_pyflakes = c.config.getBool('run-pyflakes-on-write', default=False) 

700 else: 

701 run_pyflakes = False 

702 if not script: 

703 if c.forceExecuteEntireBody: 

704 useSelectedText = False 

705 script = g.getScript(c, p or c.p, useSelectedText=useSelectedText) 

706 script_p = p or c.p # Only for error reporting below. 

707 # #532: check all scripts with pyflakes. 

708 if run_pyflakes and not g.unitTesting: 

709 from leo.commands import checkerCommands as cc 

710 prefix = ('c,g,p,script_gnx=None,None,None,None;' 

711 'assert c and g and p and script_gnx;\n') 

712 cc.PyflakesCommand(c).check_script(script_p, prefix + script) 

713 self.redirectScriptOutput() 

714 oldLog = g.app.log 

715 try: 

716 log = c.frame.log 

717 g.app.log = log 

718 if script.strip(): 

719 sys.path.insert(0, '.') # New in Leo 5.0 

720 sys.path.insert(0, c.frame.openDirectory) # per SegundoBob 

721 script += '\n' # Make sure we end the script properly. 

722 try: 

723 if not namespace or namespace.get('script_gnx') is None: 

724 namespace = namespace or {} 

725 namespace.update(script_gnx=script_p.gnx) 

726 # We *always* execute the script with p = c.p. 

727 c.executeScriptHelper(args, define_g, define_name, namespace, script) 

728 except KeyboardInterrupt: 

729 g.es('interrupted') 

730 except Exception: 

731 if raiseFlag: 

732 raise 

733 g.handleScriptException(c, script_p, script, script1) 

734 finally: 

735 del sys.path[0] 

736 del sys.path[0] 

737 else: 

738 tabName = log and hasattr(log, 'tabName') and log.tabName or 'Log' 

739 g.warning("no script selected", tabName=tabName) 

740 finally: 

741 g.app.log = oldLog 

742 self.unredirectScriptOutput() 

743 #@+node:ekr.20171123135625.5: *4* c.executeScriptHelper 

744 def executeScriptHelper(self, args, define_g, define_name, namespace, script): 

745 c = self 

746 if c.p: 

747 p = c.p.copy() # *Always* use c.p and pass c.p to script. 

748 c.setCurrentDirectoryFromContext(p) 

749 else: 

750 p = None 

751 d = {'c': c, 'g': g, 'input': g.input_, 'p': p} if define_g else {} 

752 if define_name: 

753 d['__name__'] = define_name 

754 d['script_args'] = args or [] 

755 d['script_gnx'] = g.app.scriptDict.get('script_gnx') 

756 if namespace: 

757 d.update(namespace) 

758 # A kludge: reset c.inCommand here to handle the case where we *never* return. 

759 # (This can happen when there are multiple event loops.) 

760 # This does not prevent zombie windows if the script puts up a dialog... 

761 try: 

762 c.inCommand = False 

763 g.inScript = g.app.inScript = True # g.inScript is a synonym for g.app.inScript. 

764 if c.write_script_file: 

765 scriptFile = self.writeScriptFile(script) 

766 exec(compile(script, scriptFile, 'exec'), d) 

767 else: 

768 exec(script, d) 

769 finally: 

770 g.inScript = g.app.inScript = False 

771 #@+node:ekr.20171123135625.6: *4* c.redirectScriptOutput 

772 def redirectScriptOutput(self): 

773 c = self 

774 if c.config.redirect_execute_script_output_to_log_pane: 

775 g.redirectStdout() # Redirect stdout 

776 g.redirectStderr() # Redirect stderr 

777 #@+node:ekr.20171123135625.7: *4* c.setCurrentDirectoryFromContext 

778 def setCurrentDirectoryFromContext(self, p): 

779 c = self 

780 aList = g.get_directives_dict_list(p) 

781 path = c.scanAtPathDirectives(aList) 

782 curDir = g.os_path_abspath(os.getcwd()) 

783 if path and path != curDir: 

784 try: 

785 os.chdir(path) 

786 except Exception: 

787 pass 

788 #@+node:ekr.20171123135625.8: *4* c.unredirectScriptOutput 

789 def unredirectScriptOutput(self): 

790 c = self 

791 if c.exists and c.config.redirect_execute_script_output_to_log_pane: 

792 g.restoreStderr() 

793 g.restoreStdout() 

794 #@+node:ekr.20080514131122.12: *3* @cmd recolor 

795 @cmd('recolor') 

796 def recolorCommand(self, event=None): 

797 """Force a full recolor.""" 

798 c = self 

799 wrapper = c.frame.body.wrapper 

800 # Setting all text appears to be the only way. 

801 i, j = wrapper.getSelectionRange() 

802 ins = wrapper.getInsertPoint() 

803 wrapper.setAllText(c.p.b) 

804 wrapper.setSelectionRange(i, j, insert=ins) 

805 #@+node:ekr.20171124100654.1: *3* c.API 

806 # These methods are a fundamental, unchanging, part of Leo's API. 

807 #@+node:ekr.20091001141621.6061: *4* c.Generators 

808 #@+node:ekr.20091001141621.6043: *5* c.all_nodes & all_unique_nodes 

809 def all_nodes(self): 

810 """A generator returning all vnodes in the outline, in outline order.""" 

811 c = self 

812 for p in c.all_positions(): 

813 yield p.v 

814 

815 def all_unique_nodes(self): 

816 """A generator returning each vnode of the outline.""" 

817 c = self 

818 for p in c.all_unique_positions(copy=False): 

819 yield p.v 

820 

821 # Compatibility with old code... 

822 

823 all_vnodes_iter = all_nodes 

824 all_unique_vnodes_iter = all_unique_nodes 

825 #@+node:ekr.20091001141621.6044: *5* c.all_positions 

826 def all_positions(self, copy=True): 

827 """A generator return all positions of the outline, in outline order.""" 

828 c = self 

829 p = c.rootPosition() 

830 while p: 

831 yield p.copy() if copy else p 

832 p.moveToThreadNext() 

833 

834 # Compatibility with old code... 

835 

836 all_positions_iter = all_positions 

837 allNodes_iter = all_positions 

838 #@+node:ekr.20191014093239.1: *5* c.all_positions_for_v 

839 def all_positions_for_v(self, v, stack=None): 

840 """ 

841 Generates all positions p in this outline where p.v is v. 

842 

843 Should be called with stack=None. 

844 

845 The generated positions are not necessarily in outline order. 

846 

847 By Виталије Милошевић (Vitalije Milosevic). 

848 """ 

849 c = self 

850 

851 if stack is None: 

852 stack = [] 

853 

854 if not isinstance(v, leoNodes.VNode): 

855 g.es_print(f"not a VNode: {v!r}") 

856 return # Stop the generator. 

857 

858 def allinds(v, target_v): 

859 """Yield all indices i such that v.children[i] == target_v.""" 

860 for i, x in enumerate(v.children): 

861 if x is target_v: 

862 yield i 

863 

864 def stack2pos(stack): 

865 """Convert the stack to a position.""" 

866 v, i = stack[-1] 

867 return leoNodes.Position(v, i, stack[:-1]) 

868 

869 for v2 in set(v.parents): 

870 for i in allinds(v2, v): 

871 stack.insert(0, (v, i)) 

872 if v2 is c.hiddenRootNode: 

873 yield stack2pos(stack) 

874 else: 

875 yield from c.all_positions_for_v(v2, stack) 

876 stack.pop(0) 

877 #@+node:ekr.20161120121226.1: *5* c.all_roots 

878 def all_roots(self, copy=True, predicate=None): 

879 """ 

880 A generator yielding *all* the root positions in the outline that 

881 satisfy the given predicate. p.isAnyAtFileNode is the default 

882 predicate. 

883 """ 

884 c = self 

885 if predicate is None: 

886 

887 # pylint: disable=function-redefined 

888 

889 def predicate(p): 

890 return p.isAnyAtFileNode() 

891 

892 p = c.rootPosition() 

893 while p: 

894 if predicate(p): 

895 yield p.copy() # 2017/02/19 

896 p.moveToNodeAfterTree() 

897 else: 

898 p.moveToThreadNext() 

899 #@+node:ekr.20091001141621.6062: *5* c.all_unique_positions 

900 def all_unique_positions(self, copy=True): 

901 """ 

902 A generator return all positions of the outline, in outline order. 

903 Returns only the first position for each vnode. 

904 """ 

905 c = self 

906 p = c.rootPosition() 

907 seen = set() 

908 while p: 

909 if p.v in seen: 

910 p.moveToNodeAfterTree() 

911 else: 

912 seen.add(p.v) 

913 yield p.copy() if copy else p 

914 p.moveToThreadNext() 

915 

916 # Compatibility with old code... 

917 

918 all_positions_with_unique_vnodes_iter = all_unique_positions 

919 #@+node:ekr.20161120125322.1: *5* c.all_unique_roots 

920 def all_unique_roots(self, copy=True, predicate=None): 

921 """ 

922 A generator yielding all unique root positions in the outline that 

923 satisfy the given predicate. p.isAnyAtFileNode is the default 

924 predicate. 

925 """ 

926 c = self 

927 if predicate is None: 

928 

929 # pylint: disable=function-redefined 

930 

931 def predicate(p): 

932 return p.isAnyAtFileNode() 

933 

934 seen = set() 

935 p = c.rootPosition() 

936 while p: 

937 if p.v not in seen and predicate(p): 

938 seen.add(p.v) 

939 yield p.copy() if copy else p 

940 p.moveToNodeAfterTree() 

941 else: 

942 p.moveToThreadNext() 

943 #@+node:ekr.20150316175921.5: *5* c.safe_all_positions 

944 def safe_all_positions(self, copy=True): 

945 """ 

946 A generator returning all positions of the outline. This generator does 

947 *not* assume that vnodes are never their own ancestors. 

948 """ 

949 c = self 

950 p = c.rootPosition() # Make one copy. 

951 while p: 

952 yield p.copy() if copy else p 

953 p.safeMoveToThreadNext() 

954 #@+node:ekr.20060906211747: *4* c.Getters 

955 #@+node:ekr.20040803140033: *5* c.currentPosition 

956 def currentPosition(self): 

957 """ 

958 Return a copy of the presently selected position or a new null 

959 position. So c.p.copy() is never necessary. 

960 """ 

961 c = self 

962 if hasattr(c, '_currentPosition') and getattr(c, '_currentPosition'): 

963 # *Always* return a copy. 

964 return c._currentPosition.copy() 

965 return c.rootPosition() 

966 

967 # For compatibiility with old scripts... 

968 

969 currentVnode = currentPosition 

970 #@+node:ekr.20190506060937.1: *5* c.dumpExpanded 

971 @cmd('dump-expanded') 

972 def dump_expanded(self, event): 

973 """Print all non-empty v.expandedPositions lists.""" 

974 c = event.get('c') 

975 if not c: 

976 return 

977 g.es_print('dump-expanded...') 

978 for p in c.all_positions(): 

979 if p.v.expandedPositions: 

980 indent = ' ' * p.level() 

981 print(f"{indent}{p.h}") 

982 g.printObj(p.v.expandedPositions, indent=indent) 

983 #@+node:ekr.20040306220230.1: *5* c.edit_widget 

984 def edit_widget(self, p): 

985 c = self 

986 return p and c.frame.tree.edit_widget(p) 

987 #@+node:ekr.20031218072017.2986: *5* c.fileName & relativeFileName & shortFileName 

988 # Compatibility with scripts 

989 

990 def fileName(self): 

991 s = self.mFileName or "" 

992 if g.isWindows: 

993 s = s.replace('\\', '/') 

994 return s 

995 

996 def relativeFileName(self): 

997 return self.mRelativeFileName or self.mFileName 

998 

999 def shortFileName(self): 

1000 return g.shortFileName(self.mFileName) 

1001 

1002 shortFilename = shortFileName 

1003 #@+node:ekr.20070615070925.1: *5* c.firstVisible 

1004 def firstVisible(self): 

1005 """Move to the first visible node of the present chapter or hoist.""" 

1006 c, p = self, self.p 

1007 while 1: 

1008 back = p.visBack(c) 

1009 if back and back.isVisible(c): 

1010 p = back 

1011 else: break 

1012 return p 

1013 #@+node:ekr.20171123135625.29: *5* c.getBodyLines 

1014 def getBodyLines(self): 

1015 """ 

1016 Return (head, lines, tail, oldSel, oldYview). 

1017 

1018 - head: string containg all the lines before the selected text (or the 

1019 text before the insert point if no selection) 

1020 - lines: list of lines containing the selected text 

1021 (or the line containing the insert point if no selection) 

1022 - after: string containing all lines after the selected text 

1023 (or the text after the insert point if no selection) 

1024 - oldSel: tuple containing the old selection range, or None. 

1025 - oldYview: int containing the old y-scroll value, or None. 

1026 """ 

1027 c = self 

1028 body = c.frame.body 

1029 w = body.wrapper 

1030 oldVview = w.getYScrollPosition() 

1031 # Note: lines is the entire line containing the insert point if no selection. 

1032 head, s, tail = body.getSelectionLines() 

1033 lines = g.splitLines(s) # Retain the newlines of each line. 

1034 # Expand the selection. 

1035 i = len(head) 

1036 j = len(head) + len(s) 

1037 oldSel = i, j 

1038 return head, lines, tail, oldSel, oldVview # string,list,string,tuple,int. 

1039 #@+node:ekr.20150417073117.1: *5* c.getTabWidth 

1040 def getTabWidth(self, p): 

1041 """Return the tab width in effect at p.""" 

1042 c = self 

1043 val = g.scanAllAtTabWidthDirectives(c, p) 

1044 return val 

1045 #@+node:ekr.20040803112200: *5* c.is...Position 

1046 #@+node:ekr.20040803155551: *6* c.currentPositionIsRootPosition 

1047 def currentPositionIsRootPosition(self): 

1048 """Return True if the current position is the root position. 

1049 

1050 This method is called during idle time, so not generating positions 

1051 here fixes a major leak. 

1052 """ 

1053 c = self 

1054 root = c.rootPosition() 

1055 return c._currentPosition and root and c._currentPosition == root 

1056 # return ( 

1057 # c._currentPosition and c._rootPosition and 

1058 # c._currentPosition == c._rootPosition) 

1059 #@+node:ekr.20040803160656: *6* c.currentPositionHasNext 

1060 def currentPositionHasNext(self): 

1061 """Return True if the current position is the root position. 

1062 

1063 This method is called during idle time, so not generating positions 

1064 here fixes a major leak. 

1065 """ 

1066 c = self 

1067 current = c._currentPosition 

1068 return current and current.hasNext() 

1069 #@+node:ekr.20040803112450: *6* c.isCurrentPosition 

1070 def isCurrentPosition(self, p): 

1071 c = self 

1072 if p is None or c._currentPosition is None: 

1073 return False 

1074 return p == c._currentPosition 

1075 #@+node:ekr.20040803112450.1: *6* c.isRootPosition 

1076 def isRootPosition(self, p): 

1077 c = self 

1078 root = c.rootPosition() 

1079 return p and root and p == root # 2011/03/03 

1080 #@+node:ekr.20031218072017.2987: *5* c.isChanged 

1081 def isChanged(self): 

1082 return self.changed 

1083 #@+node:ekr.20210901104900.1: *5* c.lastPosition 

1084 def lastPosition(self): 

1085 c = self 

1086 p = c.rootPosition() 

1087 while p.hasNext(): 

1088 p.moveToNext() 

1089 while p.hasThreadNext(): 

1090 p.moveToThreadNext() 

1091 return p 

1092 #@+node:ekr.20140106215321.16676: *5* c.lastTopLevel 

1093 def lastTopLevel(self): 

1094 """Return the last top-level position in the outline.""" 

1095 c = self 

1096 p = c.rootPosition() 

1097 while p.hasNext(): 

1098 p.moveToNext() 

1099 return p 

1100 #@+node:ekr.20031218072017.4146: *5* c.lastVisible 

1101 def lastVisible(self): 

1102 """Move to the last visible node of the present chapter or hoist.""" 

1103 c, p = self, self.p 

1104 while 1: 

1105 next = p.visNext(c) 

1106 if next and next.isVisible(c): 

1107 p = next 

1108 else: break 

1109 return p 

1110 #@+node:ekr.20040307104131.3: *5* c.positionExists 

1111 def positionExists(self, p, root=None, trace=False): 

1112 """Return True if a position exists in c's tree""" 

1113 if not p or not p.v: 

1114 return False 

1115 

1116 rstack = root.stack + [(root.v, root._childIndex)] if root else [] 

1117 pstack = p.stack + [(p.v, p._childIndex)] 

1118 

1119 if len(rstack) > len(pstack): 

1120 return False 

1121 

1122 par = self.hiddenRootNode 

1123 for j, x in enumerate(pstack): 

1124 if j < len(rstack) and x != rstack[j]: 

1125 return False 

1126 v, i = x 

1127 if i >= len(par.children) or v is not par.children[i]: 

1128 return False 

1129 par = v 

1130 return True 

1131 #@+node:ekr.20160427153457.1: *6* c.dumpPosition 

1132 def dumpPosition(self, p): 

1133 """Dump position p and it's ancestors.""" 

1134 g.trace('=====', p.h, p._childIndex) 

1135 for i, data in enumerate(p.stack): 

1136 v, childIndex = data 

1137 print(f"{i} {childIndex} {v._headString}") 

1138 #@+node:ekr.20040803140033.2: *5* c.rootPosition 

1139 _rootCount = 0 

1140 

1141 def rootPosition(self): 

1142 """Return the root position. 

1143 

1144 Root position is the first position in the document. Other 

1145 top level positions are siblings of this node. 

1146 """ 

1147 c = self 

1148 # 2011/02/25: Compute the position directly. 

1149 if c.hiddenRootNode.children: 

1150 v = c.hiddenRootNode.children[0] 

1151 return leoNodes.Position(v, childIndex=0, stack=None) 

1152 return None 

1153 

1154 # For compatibiility with old scripts... 

1155 

1156 rootVnode = rootPosition 

1157 findRootPosition = rootPosition 

1158 #@+node:ekr.20131017174814.17480: *5* c.shouldBeExpanded 

1159 def shouldBeExpanded(self, p): 

1160 """Return True if the node at position p should be expanded.""" 

1161 c, v = self, p.v 

1162 if not p.hasChildren(): 

1163 return False 

1164 # Always clear non-existent positions. 

1165 v.expandedPositions = [z for z in v.expandedPositions if c.positionExists(z)] 

1166 if not p.isCloned(): 

1167 # Do not call p.isExpanded here! It calls this method. 

1168 return p.v.isExpanded() 

1169 if p.isAncestorOf(c.p): 

1170 return True 

1171 for p2 in v.expandedPositions: 

1172 if p == p2: 

1173 return True 

1174 return False 

1175 #@+node:ekr.20070609122713: *5* c.visLimit 

1176 def visLimit(self): 

1177 """ 

1178 Return the topmost visible node. 

1179 This is affected by chapters and hoists. 

1180 """ 

1181 c = self 

1182 cc = c.chapterController 

1183 if c.hoistStack: 

1184 bunch = c.hoistStack[-1] 

1185 p = bunch.p 

1186 limitIsVisible = not cc or not p.h.startswith('@chapter') 

1187 return p, limitIsVisible 

1188 return None, None 

1189 #@+node:tbrown.20091206142842.10296: *5* c.vnode2allPositions 

1190 def vnode2allPositions(self, v): 

1191 """Given a VNode v, find all valid positions p such that p.v = v. 

1192 

1193 Not really all, just all for each of v's distinct immediate parents. 

1194 """ 

1195 c = self 

1196 context = v.context # v's commander. 

1197 assert c == context 

1198 positions = [] 

1199 for immediate in v.parents: 

1200 if v in immediate.children: 

1201 n = immediate.children.index(v) 

1202 else: 

1203 continue 

1204 stack = [(v, n)] 

1205 while immediate.parents: 

1206 parent = immediate.parents[0] 

1207 if immediate in parent.children: 

1208 n = parent.children.index(immediate) 

1209 else: 

1210 break 

1211 stack.insert(0, (immediate, n),) 

1212 immediate = parent 

1213 else: 

1214 v, n = stack.pop() 

1215 p = leoNodes.Position(v, n, stack) 

1216 positions.append(p) 

1217 return positions 

1218 #@+node:ekr.20090107113956.1: *5* c.vnode2position 

1219 def vnode2position(self, v): 

1220 """Given a VNode v, construct a valid position p such that p.v = v. 

1221 """ 

1222 c = self 

1223 context = v.context # v's commander. 

1224 assert c == context 

1225 stack: List[Tuple[int, Tuple["leoNodes.VNode", int]]] = [] 

1226 while v.parents: 

1227 parent = v.parents[0] 

1228 if v in parent.children: 

1229 n = parent.children.index(v) 

1230 else: 

1231 return None 

1232 stack.insert(0, (v, n),) 

1233 v = parent 

1234 # v.parents includes the hidden root node. 

1235 if not stack: 

1236 # a VNode not in the tree 

1237 return None 

1238 v, n = stack.pop() 

1239 p = leoNodes.Position(v, n, stack) # type:ignore 

1240 return p 

1241 #@+node:ekr.20090130135126.1: *4* c.Properties 

1242 def __get_p(self): 

1243 c = self 

1244 return c.currentPosition() 

1245 

1246 p = property( 

1247 __get_p, # No setter. 

1248 doc="commander current position property") 

1249 #@+node:ekr.20060906211747.1: *4* c.Setters 

1250 #@+node:ekr.20040315032503: *5* c.appendStringToBody 

1251 def appendStringToBody(self, p, s): 

1252 

1253 if s: 

1254 p.b = p.b + g.toUnicode(s) 

1255 #@+node:ekr.20031218072017.2984: *5* c.clearAllMarked 

1256 def clearAllMarked(self): 

1257 c = self 

1258 for p in c.all_unique_positions(copy=False): 

1259 p.v.clearMarked() 

1260 #@+node:ekr.20031218072017.2985: *5* c.clearAllVisited 

1261 def clearAllVisited(self): 

1262 c = self 

1263 for p in c.all_unique_positions(copy=False): 

1264 p.v.clearVisited() 

1265 p.v.clearWriteBit() 

1266 #@+node:ekr.20191215044636.1: *5* c.clearChanged 

1267 def clearChanged(self): 

1268 """clear the marker that indicates that the .leo file has been changed.""" 

1269 c = self 

1270 if not c.frame: 

1271 return 

1272 c.changed = False 

1273 if c.loading: 

1274 return # don't update while loading. 

1275 # Clear all dirty bits _before_ setting the caption. 

1276 for v in c.all_unique_nodes(): 

1277 v.clearDirty() 

1278 c.changed = False 

1279 # Do nothing for null frames. 

1280 assert c.gui 

1281 if c.gui.guiName() == 'nullGui': 

1282 return 

1283 if not c.frame.top: 

1284 return 

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

1286 if master: 

1287 master.setChanged(c, changed=False) # LeoTabbedTopLevel.setChanged. 

1288 s = c.frame.getTitle() 

1289 if len(s) > 2 and s[0:2] == "* ": 

1290 c.frame.setTitle(s[2:]) 

1291 #@+node:ekr.20060906211138: *5* c.clearMarked 

1292 def clearMarked(self, p): 

1293 c = self 

1294 p.v.clearMarked() 

1295 g.doHook("clear-mark", c=c, p=p) 

1296 #@+node:ekr.20040305223522: *5* c.setBodyString 

1297 def setBodyString(self, p, s): 

1298 """ 

1299 This is equivalent to p.b = s. 

1300 

1301 Warning: This method may call c.recolor() or c.redraw(). 

1302 """ 

1303 c, v = self, p.v 

1304 if not c or not v: 

1305 return 

1306 s = g.toUnicode(s) 

1307 current = c.p 

1308 # 1/22/05: Major change: the previous test was: 'if p == current:' 

1309 # This worked because commands work on the presently selected node. 

1310 # But setRecentFiles may change a _clone_ of the selected node! 

1311 if current and p.v == current.v: 

1312 w = c.frame.body.wrapper 

1313 w.setAllText(s) 

1314 v.setSelection(0, 0) 

1315 c.recolor() 

1316 # Keep the body text in the VNode up-to-date. 

1317 if v.b != s: 

1318 v.setBodyString(s) 

1319 v.setSelection(0, 0) 

1320 p.setDirty() 

1321 if not c.isChanged(): 

1322 c.setChanged() 

1323 c.redraw_after_icons_changed() 

1324 #@+node:ekr.20031218072017.2989: *5* c.setChanged 

1325 def setChanged(self): 

1326 """Set the marker that indicates that the .leo file has been changed.""" 

1327 c = self 

1328 if not c.frame: 

1329 return 

1330 c.changed = True 

1331 if c.loading: 

1332 return # don't update while loading. 

1333 # Do nothing for null frames. 

1334 assert c.gui 

1335 if c.gui.guiName() == 'nullGui': 

1336 return 

1337 if not c.frame.top: 

1338 return 

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

1340 if master: 

1341 master.setChanged(c, changed=True) 

1342 # LeoTabbedTopLevel.setChanged. 

1343 s = c.frame.getTitle() 

1344 if len(s) > 2 and s[0] != '*': 

1345 c.frame.setTitle("* " + s) 

1346 #@+node:ekr.20040803140033.1: *5* c.setCurrentPosition 

1347 _currentCount = 0 

1348 

1349 def setCurrentPosition(self, p): 

1350 """ 

1351 Set the presently selected position. For internal use only. 

1352 Client code should use c.selectPosition instead. 

1353 """ 

1354 c = self 

1355 if not p: 

1356 g.trace('===== no p', g.callers()) 

1357 return 

1358 if c.positionExists(p): 

1359 if c._currentPosition and p == c._currentPosition: 

1360 pass # We have already made a copy. 

1361 else: # Make a copy _now_ 

1362 c._currentPosition = p.copy() 

1363 else: 

1364 # Don't kill unit tests for this nkind of problem. 

1365 c._currentPosition = c.rootPosition() 

1366 g.trace('Invalid position', repr(p)) 

1367 g.trace(g.callers()) 

1368 

1369 # For compatibiility with old scripts. 

1370 

1371 setCurrentVnode = setCurrentPosition 

1372 #@+node:ekr.20040305223225: *5* c.setHeadString 

1373 def setHeadString(self, p, s): 

1374 """ 

1375 Set the p's headline and the corresponding tree widget to s. 

1376 

1377 This is used in by unit tests to restore the outline. 

1378 """ 

1379 c = self 

1380 p.initHeadString(s) 

1381 p.setDirty() 

1382 # Change the actual tree widget so 

1383 # A later call to c.endEditing or c.redraw will use s. 

1384 c.frame.tree.setHeadline(p, s) 

1385 #@+node:ekr.20060109164136: *5* c.setLog 

1386 def setLog(self): 

1387 c = self 

1388 if c.exists: 

1389 try: 

1390 # c.frame or c.frame.log may not exist. 

1391 g.app.setLog(c.frame.log) 

1392 except AttributeError: 

1393 pass 

1394 #@+node:ekr.20060906211138.1: *5* c.setMarked (calls hook) 

1395 def setMarked(self, p): 

1396 c = self 

1397 p.setMarked() 

1398 p.setDirty() # Defensive programming. 

1399 g.doHook("set-mark", c=c, p=p) 

1400 #@+node:ekr.20040803140033.3: *5* c.setRootPosition (A do-nothing) 

1401 def setRootPosition(self, unused_p=None): 

1402 """Set c._rootPosition.""" 

1403 # 2011/03/03: No longer used. 

1404 #@+node:ekr.20060906131836: *5* c.setRootVnode (A do-nothing) 

1405 def setRootVnode(self, v): 

1406 pass 

1407 # c = self 

1408 # # 2011/02/25: c.setRootPosition needs no arguments. 

1409 # c.setRootPosition() 

1410 #@+node:ekr.20040311173238: *5* c.topPosition & c.setTopPosition 

1411 def topPosition(self): 

1412 """Return the root position.""" 

1413 c = self 

1414 if c._topPosition: 

1415 return c._topPosition.copy() 

1416 return None 

1417 

1418 def setTopPosition(self, p): 

1419 """Set the root positioin.""" 

1420 c = self 

1421 if p: 

1422 c._topPosition = p.copy() 

1423 else: 

1424 c._topPosition = None 

1425 

1426 # Define these for compatibiility with old scripts... 

1427 

1428 topVnode = topPosition 

1429 setTopVnode = setTopPosition 

1430 #@+node:ekr.20171124081419.1: *3* c.Check Outline... 

1431 #@+node:ekr.20141024211256.22: *4* c.checkGnxs 

1432 def checkGnxs(self): 

1433 """ 

1434 Check the consistency of all gnx's. 

1435 Reallocate gnx's for duplicates or empty gnx's. 

1436 Return the number of structure_errors found. 

1437 """ 

1438 c = self 

1439 # Keys are gnx's; values are sets of vnodes with that gnx. 

1440 d: Dict[str, Set["leoNodes.VNode"]] = {} 

1441 ni = g.app.nodeIndices 

1442 t1 = time.time() 

1443 

1444 def new_gnx(v): 

1445 """Set v.fileIndex.""" 

1446 v.fileIndex = ni.getNewIndex(v) 

1447 

1448 count, gnx_errors = 0, 0 

1449 for p in c.safe_all_positions(copy=False): 

1450 count += 1 

1451 v = p.v 

1452 gnx = v.fileIndex 

1453 if gnx: # gnx must be a string. 

1454 aSet: Set["leoNodes.VNode"] = d.get(gnx, set()) 

1455 aSet.add(v) 

1456 d[gnx] = aSet 

1457 else: 

1458 gnx_errors += 1 

1459 new_gnx(v) 

1460 g.es_print(f"empty v.fileIndex: {v} new: {p.v.gnx!r}", color='red') 

1461 for gnx in sorted(d.keys()): 

1462 aList = list(d.get(gnx)) 

1463 if len(aList) != 1: 

1464 print('\nc.checkGnxs...') 

1465 g.es_print(f"multiple vnodes with gnx: {gnx!r}", color='red') 

1466 for v in aList: 

1467 gnx_errors += 1 

1468 g.es_print(f"id(v): {id(v)} gnx: {v.fileIndex} {v.h}", color='red') 

1469 new_gnx(v) 

1470 ok = not gnx_errors and not g.app.structure_errors 

1471 t2 = time.time() 

1472 if not ok: 

1473 g.es_print( 

1474 f"check-outline ERROR! {c.shortFileName()} " 

1475 f"{count} nodes, " 

1476 f"{gnx_errors} gnx errors, " 

1477 f"{g.app.structure_errors} " 

1478 f"structure errors", 

1479 color='red' 

1480 ) 

1481 elif c.verbose_check_outline and not g.unitTesting: 

1482 print( 

1483 f"check-outline OK: {t2 - t1:4.2f} sec. " 

1484 f"{c.shortFileName()} {count} nodes") 

1485 return g.app.structure_errors 

1486 #@+node:ekr.20150318131947.7: *4* c.checkLinks & helpers 

1487 def checkLinks(self): 

1488 """Check the consistency of all links in the outline.""" 

1489 c = self 

1490 t1 = time.time() 

1491 count, errors = 0, 0 

1492 for p in c.safe_all_positions(): 

1493 count += 1 

1494 # try: 

1495 if not c.checkThreadLinks(p): 

1496 errors += 1 

1497 break 

1498 if not c.checkSiblings(p): 

1499 errors += 1 

1500 break 

1501 if not c.checkParentAndChildren(p): 

1502 errors += 1 

1503 break 

1504 # except AssertionError: 

1505 # errors += 1 

1506 # junk, value, junk = sys.exc_info() 

1507 # g.error("test failed at position %s\n%s" % (repr(p), value)) 

1508 t2 = time.time() 

1509 g.es_print( 

1510 f"check-links: {t2 - t1:4.2f} sec. " 

1511 f"{c.shortFileName()} {count} nodes", color='blue') 

1512 return errors 

1513 #@+node:ekr.20040314035615.2: *5* c.checkParentAndChildren 

1514 def checkParentAndChildren(self, p): 

1515 """Check consistency of parent and child data structures.""" 

1516 c = self 

1517 

1518 def _assert(condition): 

1519 return g._assert(condition, show_callers=False) 

1520 

1521 def dump(p): 

1522 if p and p.v: 

1523 p.v.dump() 

1524 elif p: 

1525 print('<no p.v>') 

1526 else: 

1527 print('<no p>') 

1528 if g.unitTesting: 

1529 assert False, g.callers() 

1530 

1531 if p.hasParent(): 

1532 n = p.childIndex() 

1533 if not _assert(p == p.parent().moveToNthChild(n)): 

1534 g.trace(f"p != parent().moveToNthChild({n})") 

1535 dump(p) 

1536 dump(p.parent()) 

1537 return False 

1538 if p.level() > 0 and not _assert(p.v.parents): 

1539 g.trace("no parents") 

1540 dump(p) 

1541 return False 

1542 for child in p.children(): 

1543 if not c.checkParentAndChildren(child): 

1544 return False 

1545 if not _assert(p == child.parent()): 

1546 g.trace("p != child.parent()") 

1547 dump(p) 

1548 dump(child.parent()) 

1549 return False 

1550 if p.hasNext(): 

1551 if not _assert(p.next().parent() == p.parent()): 

1552 g.trace("p.next().parent() != p.parent()") 

1553 dump(p.next().parent()) 

1554 dump(p.parent()) 

1555 return False 

1556 if p.hasBack(): 

1557 if not _assert(p.back().parent() == p.parent()): 

1558 g.trace("p.back().parent() != parent()") 

1559 dump(p.back().parent()) 

1560 dump(p.parent()) 

1561 return False 

1562 # Check consistency of parent and children arrays. 

1563 # Every nodes gets visited, so a strong test need only check consistency 

1564 # between p and its parent, not between p and its children. 

1565 parent_v = p._parentVnode() 

1566 n = p.childIndex() 

1567 if not _assert(parent_v.children[n] == p.v): 

1568 g.trace("parent_v.children[n] != p.v") 

1569 parent_v.dump() 

1570 p.v.dump() 

1571 return False 

1572 return True 

1573 #@+node:ekr.20040314035615.1: *5* c.checkSiblings 

1574 def checkSiblings(self, p): 

1575 """Check the consistency of next and back links.""" 

1576 back = p.back() 

1577 next = p.next() 

1578 if back: 

1579 if not g._assert(p == back.next()): 

1580 g.trace( 

1581 f"p!=p.back().next()\n" 

1582 f" back: {back}\n" 

1583 f"back.next: {back.next()}") 

1584 return False 

1585 if next: 

1586 if not g._assert(p == next.back()): 

1587 g.trace( 

1588 f"p!=p.next().back\n" 

1589 f" next: {next}\n" 

1590 f"next.back: {next.back()}") 

1591 return False 

1592 return True 

1593 #@+node:ekr.20040314035615: *5* c.checkThreadLinks 

1594 def checkThreadLinks(self, p): 

1595 """Check consistency of threadNext & threadBack links.""" 

1596 threadBack = p.threadBack() 

1597 threadNext = p.threadNext() 

1598 if threadBack: 

1599 if not g._assert(p == threadBack.threadNext()): 

1600 g.trace("p!=p.threadBack().threadNext()") 

1601 return False 

1602 if threadNext: 

1603 if not g._assert(p == threadNext.threadBack()): 

1604 g.trace("p!=p.threadNext().threadBack()") 

1605 return False 

1606 return True 

1607 #@+node:ekr.20031218072017.1760: *4* c.checkMoveWithParentWithWarning & c.checkDrag 

1608 #@+node:ekr.20070910105044: *5* c.checkMoveWithParentWithWarning 

1609 def checkMoveWithParentWithWarning(self, root, parent, warningFlag): 

1610 """ 

1611 Return False if root or any of root's descendents is a clone of parent 

1612 or any of parents ancestors. 

1613 """ 

1614 c = self 

1615 message = "Illegal move or drag: no clone may contain a clone of itself" 

1616 clonedVnodes = {} 

1617 for ancestor in parent.self_and_parents(copy=False): 

1618 if ancestor.isCloned(): 

1619 v = ancestor.v 

1620 clonedVnodes[v] = v 

1621 if not clonedVnodes: 

1622 return True 

1623 for p in root.self_and_subtree(copy=False): 

1624 if p.isCloned() and clonedVnodes.get(p.v): 

1625 if not g.unitTesting and warningFlag: 

1626 c.alert(message) 

1627 return False 

1628 return True 

1629 #@+node:ekr.20070910105044.1: *5* c.checkDrag 

1630 def checkDrag(self, root, target): 

1631 """Return False if target is any descendant of root.""" 

1632 c = self 

1633 message = "Can not drag a node into its descendant tree." 

1634 for z in root.subtree(): 

1635 if z == target: 

1636 if not g.unitTesting: 

1637 c.alert(message) 

1638 return False 

1639 return True 

1640 #@+node:ekr.20031218072017.2072: *4* c.checkOutline 

1641 def checkOutline(self, event=None, check_links=False): 

1642 """ 

1643 Check for errors in the outline. 

1644 Return the count of serious structure errors. 

1645 """ 

1646 # The check-outline command sets check_links = True. 

1647 c = self 

1648 g.app.structure_errors = 0 

1649 structure_errors = c.checkGnxs() 

1650 if check_links and not structure_errors: 

1651 structure_errors += c.checkLinks() 

1652 return structure_errors 

1653 #@+node:ekr.20031218072017.1765: *4* c.validateOutline 

1654 # Makes sure all nodes are valid. 

1655 

1656 def validateOutline(self, event=None): 

1657 c = self 

1658 if not g.app.validate_outline: 

1659 return True 

1660 root = c.rootPosition() 

1661 parent = None 

1662 if root: 

1663 return root.validateOutlineWithParent(parent) 

1664 return True 

1665 #@+node:ekr.20040723094220: *3* c.Check Python code 

1666 # This code is no longer used by any Leo command, 

1667 # but it will be retained for use of scripts. 

1668 #@+node:ekr.20040723094220.1: *4* c.checkAllPythonCode 

1669 def checkAllPythonCode(self, event=None, ignoreAtIgnore=True): 

1670 """Check all nodes in the selected tree for syntax and tab errors.""" 

1671 c = self 

1672 count = 0 

1673 result = "ok" 

1674 for p in c.all_unique_positions(): 

1675 count += 1 

1676 if not g.unitTesting: 

1677 #@+<< print dots >> 

1678 #@+node:ekr.20040723094220.2: *5* << print dots >> 

1679 if count % 100 == 0: 

1680 g.es('', '.', newline=False) 

1681 if count % 2000 == 0: 

1682 g.enl() 

1683 #@-<< print dots >> 

1684 if g.scanForAtLanguage(c, p) == "python": 

1685 if not g.scanForAtSettings(p) and ( 

1686 not ignoreAtIgnore or not g.scanForAtIgnore(c, p) 

1687 ): 

1688 try: 

1689 c.checkPythonNode(p) 

1690 except(SyntaxError, tokenize.TokenError, tabnanny.NannyNag): 

1691 result = "error" # Continue to check. 

1692 except Exception: 

1693 return "surprise" # abort 

1694 if result != 'ok': 

1695 g.pr(f"Syntax error in {p.h}") 

1696 return result # End the unit test: it has failed. 

1697 if not g.unitTesting: 

1698 g.blue("check complete") 

1699 return result 

1700 #@+node:ekr.20040723094220.3: *4* c.checkPythonCode 

1701 def checkPythonCode(self, 

1702 event=None, 

1703 ignoreAtIgnore=True, 

1704 checkOnSave=False 

1705 ): 

1706 """Check the selected tree for syntax and tab errors.""" 

1707 c = self 

1708 count = 0 

1709 result = "ok" 

1710 if not g.unitTesting: 

1711 g.es("checking Python code ") 

1712 for p in c.p.self_and_subtree(): 

1713 count += 1 

1714 if not g.unitTesting and not checkOnSave: 

1715 #@+<< print dots >> 

1716 #@+node:ekr.20040723094220.4: *5* << print dots >> 

1717 if count % 100 == 0: 

1718 g.es('', '.', newline=False) 

1719 if count % 2000 == 0: 

1720 g.enl() 

1721 #@-<< print dots >> 

1722 if g.scanForAtLanguage(c, p) == "python": 

1723 if not ignoreAtIgnore or not g.scanForAtIgnore(c, p): 

1724 try: 

1725 c.checkPythonNode(p) 

1726 except(SyntaxError, tokenize.TokenError, tabnanny.NannyNag): 

1727 result = "error" # Continue to check. 

1728 except Exception: 

1729 return "surprise" # abort 

1730 if not g.unitTesting: 

1731 g.blue("check complete") 

1732 # We _can_ return a result for unit tests because we aren't using doCommand. 

1733 return result 

1734 #@+node:ekr.20040723094220.5: *4* c.checkPythonNode 

1735 def checkPythonNode(self, p): 

1736 c, h = self, p.h 

1737 # Call getScript to ignore directives and section references. 

1738 body = g.getScript(c, p.copy()) 

1739 if not body: 

1740 return 

1741 try: 

1742 fn = f"<node: {p.h}>" 

1743 compile(body + '\n', fn, 'exec') 

1744 c.tabNannyNode(p, h, body) 

1745 except SyntaxError: 

1746 if g.unitTesting: 

1747 raise 

1748 g.warning(f"Syntax error in: {h}") 

1749 g.es_exception(full=False, color="black") 

1750 except Exception: 

1751 g.es_print('unexpected exception') 

1752 g.es_exception() 

1753 raise 

1754 #@+node:ekr.20040723094220.6: *4* c.tabNannyNode 

1755 # This code is based on tabnanny.check. 

1756 

1757 def tabNannyNode(self, p, headline, body): 

1758 """Check indentation using tabnanny.""" 

1759 try: 

1760 readline = g.ReadLinesClass(body).next 

1761 tabnanny.process_tokens(tokenize.generate_tokens(readline)) 

1762 except IndentationError: 

1763 if g.unitTesting: 

1764 raise 

1765 junk1, msg, junk2 = sys.exc_info() 

1766 g.warning("IndentationError in", headline) 

1767 g.es('', msg) 

1768 except tokenize.TokenError: 

1769 if g.unitTesting: 

1770 raise 

1771 junk1, msg, junk2 = sys.exc_info() 

1772 g.warning("TokenError in", headline) 

1773 g.es('', msg) 

1774 except tabnanny.NannyNag: 

1775 if g.unitTesting: 

1776 raise 

1777 junk1, nag, junk2 = sys.exc_info() 

1778 badline = nag.get_lineno() 

1779 line = nag.get_line() 

1780 message = nag.get_msg() 

1781 g.warning("indentation error in", headline, "line", badline) 

1782 g.es(message) 

1783 line2 = repr(str(line))[1:-1] 

1784 g.es("offending line:\n", line2) 

1785 except Exception: 

1786 g.trace("unexpected exception") 

1787 g.es_exception() 

1788 raise 

1789 #@+node:ekr.20171123200644.1: *3* c.Convenience methods 

1790 #@+node:ekr.20171123135625.39: *4* c.getTime 

1791 def getTime(self, body=True): 

1792 c = self 

1793 default_format = "%m/%d/%Y %H:%M:%S" # E.g., 1/30/2003 8:31:55 

1794 # Try to get the format string from settings. 

1795 if body: 

1796 format = c.config.getString("body-time-format-string") 

1797 gmt = c.config.getBool("body-gmt-time") 

1798 else: 

1799 format = c.config.getString("headline-time-format-string") 

1800 gmt = c.config.getBool("headline-gmt-time") 

1801 if format is None: 

1802 format = default_format 

1803 try: 

1804 # import time 

1805 if gmt: 

1806 s = time.strftime(format, time.gmtime()) 

1807 else: 

1808 s = time.strftime(format, time.localtime()) 

1809 except(ImportError, NameError): 

1810 g.warning("time.strftime not available on this platform") 

1811 return "" 

1812 except Exception: 

1813 g.es_exception() # Probably a bad format string in leoSettings.leo. 

1814 s = time.strftime(default_format, time.gmtime()) 

1815 return s 

1816 #@+node:ekr.20171123135625.10: *4* c.goToLineNumber & goToScriptLineNumber 

1817 def goToLineNumber(self, n): 

1818 """ 

1819 Go to line n (zero-based) of a script. 

1820 A convenience method called from g.handleScriptException. 

1821 """ 

1822 c = self 

1823 c.gotoCommands.find_file_line(n) 

1824 

1825 def goToScriptLineNumber(self, n, p): 

1826 """ 

1827 Go to line n (zero-based) of a script. 

1828 A convenience method called from g.handleScriptException. 

1829 """ 

1830 c = self 

1831 c.gotoCommands.find_script_line(n, p) 

1832 #@+node:ekr.20090103070824.9: *4* c.setFileTimeStamp 

1833 def setFileTimeStamp(self, fn): 

1834 """Update the timestamp for fn..""" 

1835 # c = self 

1836 if g.app.externalFilesController: 

1837 g.app.externalFilesController.set_time(fn) 

1838 #@+node:ekr.20031218072017.3000: *4* c.updateSyntaxColorer 

1839 def updateSyntaxColorer(self, v): 

1840 self.frame.body.updateSyntaxColorer(v) 

1841 #@+node:ekr.20180503110307.1: *4* c.interactive* 

1842 #@+node:ekr.20180504075937.1: *5* c.interactive 

1843 def interactive(self, callback, event, prompts): 

1844 #@+<< c.interactive docstring >> 

1845 #@+node:ekr.20180503131222.1: *6* << c.interactive docstring >> 

1846 """ 

1847 c.interactive: Prompt for up to three arguments from the minibuffer. 

1848 

1849 The number of prompts determines the number of arguments. 

1850 

1851 Use the @command decorator to define commands. Examples: 

1852 

1853 @g.command('i3') 

1854 def i3_command(event): 

1855 c = event.get('c') 

1856 if not c: return 

1857 

1858 def callback(args, c, event): 

1859 g.trace(args) 

1860 c.bodyWantsFocus() 

1861 

1862 c.interactive(callback, event, 

1863 prompts=['Arg1: ', ' Arg2: ', ' Arg3: ']) 

1864 """ 

1865 #@-<< c.interactive docstring >> 

1866 # 

1867 # This pathetic code should be generalized, 

1868 # but it's not as easy as one might imagine. 

1869 c = self 

1870 d = {1: c.interactive1, 2: c.interactive2, 3: c.interactive3,} 

1871 f = d.get(len(prompts)) 

1872 if f: 

1873 f(callback, event, prompts) 

1874 else: 

1875 g.trace('At most 3 arguments are supported.') 

1876 #@+node:ekr.20180503111213.1: *5* c.interactive1 

1877 def interactive1(self, callback, event, prompts): 

1878 

1879 c, k = self, self.k 

1880 prompt = prompts[0] 

1881 

1882 def state1(event): 

1883 callback(args=[k.arg], c=c, event=event) 

1884 k.clearState() 

1885 k.resetLabel() 

1886 k.showStateAndMode() 

1887 

1888 k.setLabelBlue(prompt) 

1889 k.get1Arg(event, handler=state1) 

1890 #@+node:ekr.20180503111249.1: *5* c.interactive2 

1891 def interactive2(self, callback, event, prompts): 

1892 

1893 c, d, k = self, {}, self.k 

1894 prompt1, prompt2 = prompts 

1895 

1896 def state1(event): 

1897 d['arg1'] = k.arg 

1898 k.extendLabel(prompt2, select=False, protect=True) 

1899 k.getNextArg(handler=state2) 

1900 

1901 def state2(event): 

1902 callback(args=[d.get('arg1'), k.arg], c=c, event=event) 

1903 k.clearState() 

1904 k.resetLabel() 

1905 k.showStateAndMode() 

1906 

1907 k.setLabelBlue(prompt1) 

1908 k.get1Arg(event, handler=state1) 

1909 #@+node:ekr.20180503111249.2: *5* c.interactive3 

1910 def interactive3(self, callback, event, prompts): 

1911 

1912 c, d, k = self, {}, self.k 

1913 prompt1, prompt2, prompt3 = prompts 

1914 

1915 def state1(event): 

1916 d['arg1'] = k.arg 

1917 k.extendLabel(prompt2, select=False, protect=True) 

1918 k.getNextArg(handler=state2) 

1919 

1920 def state2(event): 

1921 d['arg2'] = k.arg 

1922 k.extendLabel(prompt3, select=False, protect=True) 

1923 k.get1Arg(event, handler=state3) 

1924 # Restart. 

1925 

1926 def state3(event): 

1927 args = [d.get('arg1'), d.get('arg2'), k.arg] 

1928 callback(args=args, c=c, event=event) 

1929 k.clearState() 

1930 k.resetLabel() 

1931 k.showStateAndMode() 

1932 

1933 k.setLabelBlue(prompt1) 

1934 k.get1Arg(event, handler=state1) 

1935 #@+node:ekr.20080901124540.1: *3* c.Directive scanning 

1936 # These are all new in Leo 4.5.1. 

1937 #@+node:ekr.20171123135625.33: *4* c.getLanguageAtCursor 

1938 def getLanguageAtCursor(self, p, language): 

1939 """ 

1940 Return the language in effect at the present insert point. 

1941 Use the language argument as a default if no @language directive seen. 

1942 """ 

1943 c = self 

1944 tag = '@language' 

1945 w = c.frame.body.wrapper 

1946 ins = w.getInsertPoint() 

1947 n = 0 

1948 for s in g.splitLines(p.b): 

1949 if g.match_word(s, 0, tag): 

1950 i = g.skip_ws(s, len(tag)) 

1951 j = g.skip_id(s, i) 

1952 language = s[i:j] 

1953 if n <= ins < n + len(s): 

1954 break 

1955 else: 

1956 n += len(s) 

1957 return language 

1958 #@+node:ekr.20081006100835.1: *4* c.getNodePath & c.getNodeFileName 

1959 def getNodePath(self, p): 

1960 """Return the path in effect at node p.""" 

1961 c = self 

1962 aList = g.get_directives_dict_list(p) 

1963 path = c.scanAtPathDirectives(aList) 

1964 return path 

1965 

1966 def getNodeFileName(self, p): 

1967 """ 

1968 Return the full file name at node p, 

1969 including effects of all @path directives. 

1970 Return '' if p is no kind of @file node. 

1971 """ 

1972 c = self 

1973 for p in p.self_and_parents(copy=False): 

1974 name = p.anyAtFileNodeName() 

1975 if name: 

1976 return g.fullPath(c, p) # #1914. 

1977 return '' 

1978 #@+node:ekr.20171123135625.32: *4* c.hasAmbiguousLanguage 

1979 def hasAmbiguousLanguage(self, p): 

1980 """Return True if p.b contains different @language directives.""" 

1981 # c = self 

1982 languages, tag = set(), '@language' 

1983 for s in g.splitLines(p.b): 

1984 if g.match_word(s, 0, tag): 

1985 i = g.skip_ws(s, len(tag)) 

1986 j = g.skip_id(s, i) 

1987 word = s[i:j] 

1988 languages.add(word) 

1989 return len(list(languages)) > 1 

1990 #@+node:ekr.20080827175609.39: *4* c.scanAllDirectives 

1991 #@@nobeautify 

1992 

1993 def scanAllDirectives(self, p): 

1994 """ 

1995 Scan p and ancestors for directives. 

1996 

1997 Returns a dict containing the results, including defaults. 

1998 """ 

1999 c = self 

2000 p = p or c.p 

2001 # Defaults... 

2002 default_language = g.getLanguageFromAncestorAtFileNode(p) or c.target_language or 'python' 

2003 default_delims = g.set_delims_from_language(default_language) 

2004 wrap = c.config.getBool("body-pane-wraps") 

2005 table = ( # type:ignore 

2006 ('encoding', None, g.scanAtEncodingDirectives), 

2007 ('lang-dict', {}, g.scanAtCommentAndAtLanguageDirectives), 

2008 ('lineending', None, g.scanAtLineendingDirectives), 

2009 ('pagewidth', c.page_width, g.scanAtPagewidthDirectives), 

2010 ('path', None, c.scanAtPathDirectives), 

2011 ('tabwidth', c.tab_width, g.scanAtTabwidthDirectives), 

2012 ('wrap', wrap, g.scanAtWrapDirectives), 

2013 ) 

2014 # Set d by scanning all directives. 

2015 aList = g.get_directives_dict_list(p) 

2016 d = {} 

2017 for key, default, func in table: 

2018 val = func(aList) # type:ignore 

2019 d[key] = default if val is None else val 

2020 # Post process: do *not* set commander ivars. 

2021 lang_dict = d.get('lang-dict') 

2022 d = { 

2023 "delims": lang_dict.get('delims') or default_delims, 

2024 "comment": lang_dict.get('comment'), # Leo 6.4: New. 

2025 "encoding": d.get('encoding'), 

2026 # Note: at.scanAllDirectives does not use the defaults for "language". 

2027 "language": lang_dict.get('language') or default_language, 

2028 "lang-dict": lang_dict, # Leo 6.4: New. 

2029 "lineending": d.get('lineending'), 

2030 "pagewidth": d.get('pagewidth'), 

2031 "path": d.get('path'), # Redundant: or g.getBaseDirectory(c), 

2032 "tabwidth": d.get('tabwidth'), 

2033 "wrap": d.get('wrap'), 

2034 } 

2035 return d 

2036 #@+node:ekr.20080828103146.15: *4* c.scanAtPathDirectives 

2037 def scanAtPathDirectives(self, aList): 

2038 """ 

2039 Scan aList for @path directives. 

2040 Return a reasonable default if no @path directive is found. 

2041 """ 

2042 c = self 

2043 c.scanAtPathDirectivesCount += 1 # An important statistic. 

2044 # Step 1: Compute the starting path. 

2045 # The correct fallback directory is the absolute path to the base. 

2046 if c.openDirectory: # Bug fix: 2008/9/18 

2047 base = c.openDirectory 

2048 else: 

2049 base = g.app.config.relative_path_base_directory 

2050 if base and base == "!": 

2051 base = g.app.loadDir 

2052 elif base and base == ".": 

2053 base = c.openDirectory 

2054 base = c.expand_path_expression(base) # #1341. 

2055 base = g.os_path_expanduser(base) # #1889. 

2056 absbase = g.os_path_finalize_join(g.app.loadDir, base) # #1341. 

2057 # Step 2: look for @path directives. 

2058 paths = [] 

2059 for d in aList: 

2060 # Look for @path directives. 

2061 path = d.get('path') 

2062 warning = d.get('@path_in_body') 

2063 if path is not None: # retain empty paths for warnings. 

2064 # Convert "path" or <path> to path. 

2065 path = g.stripPathCruft(path) 

2066 if path and not warning: 

2067 path = c.expand_path_expression(path) # #1341. 

2068 path = g.os_path_expanduser(path) # #1889. 

2069 paths.append(path) 

2070 # We will silently ignore empty @path directives. 

2071 # Add absbase and reverse the list. 

2072 paths.append(absbase) 

2073 paths.reverse() 

2074 # Step 3: Compute the full, effective, absolute path. 

2075 path = g.os_path_finalize_join(*paths) # #1341. 

2076 return path or g.getBaseDirectory(c) 

2077 # 2010/10/22: A useful default. 

2078 #@+node:ekr.20171123201514.1: *3* c.Executing commands & scripts 

2079 #@+node:ekr.20110605040658.17005: *4* c.check_event 

2080 def check_event(self, event): 

2081 """Check an event object.""" 

2082 # c = self 

2083 from leo.core import leoGui 

2084 

2085 if not event: 

2086 return 

2087 stroke = event.stroke 

2088 got = event.char 

2089 if g.unitTesting: 

2090 return 

2091 if stroke and (stroke.find('Alt+') > -1 or stroke.find('Ctrl+') > -1): 

2092 # Alas, Alt and Ctrl bindings must *retain* the char field, 

2093 # so there is no way to know what char field to expect. 

2094 expected = event.char 

2095 else: 

2096 # disable the test. 

2097 # We will use the (weird) key value for, say, Ctrl-s, 

2098 # if there is no binding for Ctrl-s. 

2099 expected = event.char 

2100 if not isinstance(event, leoGui.LeoKeyEvent): 

2101 if g.app.gui.guiName() not in ('browser', 'console', 'curses'): # #1839. 

2102 g.trace(f"not leo event: {event!r}, callers: {g.callers(8)}") 

2103 if expected != got: 

2104 g.trace(f"stroke: {stroke!r}, expected char: {expected!r}, got: {got!r}") 

2105 #@+node:ekr.20031218072017.2817: *4* c.doCommand 

2106 command_count = 0 

2107 

2108 def doCommand(self, command_func, command_name, event): 

2109 """ 

2110 Execute the given command function, invoking hooks and catching exceptions. 

2111 

2112 The code assumes that the "command1" hook has completely handled the 

2113 command func if g.doHook("command1") returns False. This provides a 

2114 simple mechanism for overriding commands. 

2115 """ 

2116 c, p = self, self.p 

2117 c.setLog() 

2118 self.command_count += 1 

2119 # New in Leo 6.2. Set command_function and command_name ivars. 

2120 self.command_function = command_func 

2121 self.command_name = command_name 

2122 # The presence of this message disables all commands. 

2123 if c.disableCommandsMessage: 

2124 g.blue(c.disableCommandsMessage) 

2125 return None 

2126 if c.exists and c.inCommand and not g.unitTesting: 

2127 g.app.commandInterruptFlag = True # For sc.make_slide_show_command. 

2128 # 1912: This message is annoying and unhelpful. 

2129 # g.error('ignoring command: already executing a command.') 

2130 return None 

2131 g.app.commandInterruptFlag = False 

2132 # #2256: Update the list of recent commands. 

2133 if len(c.recent_commands_list) > 99: 

2134 c.recent_commands_list.pop() 

2135 c.recent_commands_list.insert(0, command_name) 

2136 if not g.doHook("command1", c=c, p=p, label=command_name): 

2137 try: 

2138 c.inCommand = True 

2139 try: 

2140 return_value = command_func(event) 

2141 except Exception: 

2142 g.es_exception() 

2143 return_value = None 

2144 if c and c.exists: # Be careful: the command could destroy c. 

2145 c.inCommand = False 

2146 ## c.k.funcReturn = return_value 

2147 except Exception: 

2148 c.inCommand = False 

2149 if g.unitTesting: 

2150 raise 

2151 g.es_print("exception executing command") 

2152 g.es_exception(c=c) 

2153 if c and c.exists: 

2154 if c.requestCloseWindow: 

2155 c.requestCloseWindow = False 

2156 g.app.closeLeoWindow(c.frame) 

2157 else: 

2158 c.outerUpdate() 

2159 # Be careful: the command could destroy c. 

2160 if c and c.exists: 

2161 p = c.p 

2162 g.doHook("command2", c=c, p=p, label=command_name) 

2163 return return_value 

2164 #@+node:ekr.20200522075411.1: *4* c.doCommandByName 

2165 def doCommandByName(self, command_name, event): 

2166 """ 

2167 Execute one command, given the name of the command. 

2168 

2169 The caller must do any required keystroke-only tasks. 

2170 

2171 Return the result, if any, of the command. 

2172 """ 

2173 c = self 

2174 # Get the command's function. 

2175 command_func = c.commandsDict.get(command_name.replace('&', '')) 

2176 if not command_func: 

2177 message = f"no command function for {command_name!r}" 

2178 if g.unitTesting or g.app.inBridge: 

2179 raise AttributeError(message) 

2180 g.es_print(message, color='red') 

2181 g.trace(g.callers()) 

2182 return None 

2183 # Invoke the function. 

2184 val = c.doCommand(command_func, command_name, event) 

2185 if c.exists: 

2186 c.frame.updateStatusLine() 

2187 return val 

2188 #@+node:ekr.20200526074132.1: *4* c.executeMinibufferCommand 

2189 def executeMinibufferCommand(self, commandName): 

2190 """Call c.doCommandByName, creating the required event.""" 

2191 c = self 

2192 event = g.app.gui.create_key_event(c) 

2193 return c.doCommandByName(commandName, event) 

2194 #@+node:ekr.20210305133229.1: *4* c.general_script_helper & helpers 

2195 #@@nobeautify 

2196 

2197 def general_script_helper(self, command, ext, language, root, directory=None, regex=None): 

2198 """ 

2199 The official helper for the execute-general-script command. 

2200 

2201 c: The Commander of the outline. 

2202 command: The os command to execute the script. 

2203 directory: Optional: Change to this directory before executing command. 

2204 ext: The file extention for the tempory file. 

2205 language: The language name. 

2206 regex: Optional regular expression describing error messages. 

2207 If present, group(1) should evaluate to a line number. 

2208 May be a compiled regex expression or a string. 

2209 root: The root of the tree containing the script, 

2210 The script may contain section references and @others. 

2211 

2212 Other features: 

2213 

2214 - Create a temporary external file if `not root.isAnyAtFileNode()`. 

2215 - Compute the final command as follows. 

2216 1. If command contains <FILE>, replace <FILE> with the full path. 

2217 2. If command contains <NO-FILE>, just remove <NO-FILE>. 

2218 This allows, for example, `go run .` to work as expected. 

2219 3. Append the full path to the command. 

2220 """ 

2221 c, log = self, self.frame.log 

2222 #@+others # Define helper functions 

2223 #@+node:ekr.20210529142153.1: *5* function: put_line 

2224 def put_line(s): 

2225 """ 

2226 Put the line, creating a clickable link if the regex matches. 

2227 """ 

2228 if not regex: 

2229 g.es_print(s) 

2230 return 

2231 # Get the line number. 

2232 m = regex.match(s) 

2233 if not m: 

2234 g.es_print(s) 

2235 return 

2236 # If present, the regex should define two groups. 

2237 try: 

2238 s1 = m.group(1) 

2239 s2 = m.group(2) 

2240 except IndexError: 

2241 g.es_print(f"Regex {regex.pattern()} must define two groups") 

2242 return 

2243 if s1.isdigit(): 

2244 n = int(s1) 

2245 fn = s2 

2246 elif s2.isdigit(): 

2247 n = int(s2) 

2248 fn = s1 

2249 else: 

2250 # No line number. 

2251 g.es_print(s) 

2252 return 

2253 s = s.replace(root_path, root.h) 

2254 # Print to the console. 

2255 print(s) 

2256 # Find the node and offset corresponding to line n. 

2257 p, n2 = find_line(fn, n) 

2258 # Create the link. 

2259 unl = p.get_UNL() 

2260 if unl: 

2261 log.put(s + '\n', nodeLink=f"{unl}::{n2}") # local line. 

2262 else: 

2263 log.put(s + '\n') 

2264 #@+node:ekr.20210529164957.1: *5* function: find_line 

2265 def find_line(path, n): 

2266 """ 

2267 Return the node corresponding to line n of external file given by path. 

2268 """ 

2269 if path == root_path: 

2270 p, offset, found = c.gotoCommands.find_file_line(n, root) 

2271 else: 

2272 # Find an @<file> node with the given path. 

2273 found = False 

2274 for p in c.all_positions(): 

2275 if p.isAnyAtFileNode(): 

2276 norm_path = os.path.normpath(g.fullPath(c, p)) 

2277 if path == norm_path: 

2278 p, offset, found = c.gotoCommands.find_file_line(n, p) 

2279 break 

2280 if found: 

2281 return p, offset 

2282 return root, n 

2283 #@-others 

2284 # Compile and check the regex. 

2285 if regex: 

2286 if isinstance(regex, str): 

2287 try: 

2288 regex = re.compile(regex) 

2289 except Exception: 

2290 g.trace(f"Bad regex: {regex!s}") 

2291 return None 

2292 # Get the script. 

2293 script = g.getScript(c, root, 

2294 useSelectedText=False, 

2295 forcePythonSentinels=False, # language=='python', 

2296 useSentinels=True, 

2297 ) 

2298 # Create a temp file if root is not an @<file> node. 

2299 use_temp = not root.isAnyAtFileNode() 

2300 if use_temp: 

2301 fd, root_path = tempfile.mkstemp(suffix=ext, prefix="") 

2302 with os.fdopen(fd, 'w') as f: 

2303 f.write(script) 

2304 else: 

2305 root_path = g.fullPath(c, root) 

2306 # Compute the final command. 

2307 if '<FILE>' in command: 

2308 final_command = command.replace('<FILE>', root_path) 

2309 elif '<NO-FILE>' in command: 

2310 final_command = command.replace('<NO-FILE>', '').replace(root_path, '') 

2311 else: 

2312 final_command = f"{command} {root_path}" 

2313 # Change directory. 

2314 old_dir = os.path.abspath(os.path.curdir) 

2315 if not directory: 

2316 directory = os.path.dirname(root_path) 

2317 os.chdir(directory) 

2318 # Execute the final command. 

2319 try: 

2320 proc = subprocess.Popen(final_command, 

2321 shell=True, 

2322 stdout=subprocess.PIPE, 

2323 stderr=subprocess.PIPE) 

2324 out, err = proc.communicate() 

2325 for s in g.splitLines(g.toUnicode(out)): 

2326 print(s.rstrip()) 

2327 print('') 

2328 for s in g.splitLines(g.toUnicode(err)): 

2329 put_line(s.rstrip()) 

2330 finally: 

2331 if use_temp: 

2332 os.remove(root_path) 

2333 os.chdir(old_dir) 

2334 #@+node:ekr.20200523135601.1: *4* c.insertCharFromEvent 

2335 def insertCharFromEvent(self, event): 

2336 """ 

2337 Handle the character given by event, ignoring various special keys: 

2338 - getArg state: k.getArg. 

2339 - Tree: onCanvasKey or onHeadlineKey. 

2340 - Body: ec.selfInsertCommand 

2341 - Log: log_w.insert 

2342 """ 

2343 trace = all(z in g.app.debug for z in ('keys', 'verbose')) 

2344 c, k, w = self, self.k, event.widget 

2345 name = c.widget_name(w) 

2346 stroke = event.stroke 

2347 if trace: 

2348 g.trace('stroke', stroke, 'plain:', k.isPlainKey(stroke), 'widget', name) 

2349 if not stroke: 

2350 return 

2351 # 

2352 # Part 1: Very late special cases. 

2353 # 

2354 # #1448 

2355 if stroke.isNumPadKey() and k.state.kind == 'getArg': 

2356 stroke.removeNumPadModifier() 

2357 k.getArg(event, stroke=stroke) 

2358 return 

2359 # Handle all unbound characters in command mode. 

2360 if k.unboundKeyAction == 'command': 

2361 w = g.app.gui.get_focus(c) 

2362 if w and g.app.gui.widget_name(w).lower().startswith('canvas'): 

2363 c.onCanvasKey(event) 

2364 return 

2365 # 

2366 # Part 2: Filter out keys that should never be inserted by default. 

2367 # 

2368 # Ignore unbound F-keys. 

2369 if stroke.isFKey(): 

2370 return 

2371 # Ignore unbound Alt/Ctrl keys. 

2372 if stroke.isAltCtrl(): 

2373 if not k.enable_alt_ctrl_bindings: 

2374 return 

2375 if k.ignore_unbound_non_ascii_keys: 

2376 return 

2377 # #868 

2378 if stroke.isPlainNumPad(): 

2379 stroke.removeNumPadModifier() 

2380 event.stroke = stroke 

2381 # #868 

2382 if stroke.isNumPadKey(): 

2383 return 

2384 # Ignore unbound non-ascii character. 

2385 if k.ignore_unbound_non_ascii_keys and not stroke.isPlainKey(): 

2386 return 

2387 # Never insert escape or insert characters. 

2388 if 'Escape' in stroke.s or 'Insert' in stroke.s: 

2389 return 

2390 # 

2391 # Part 3: Handle the event depending on the pane and state. 

2392 # 

2393 # Handle events in the body pane. 

2394 if name.startswith('body'): 

2395 action = k.unboundKeyAction 

2396 if action in ('insert', 'overwrite'): 

2397 c.editCommands.selfInsertCommand(event, action=action) 

2398 c.frame.updateStatusLine() 

2399 return 

2400 # 

2401 # Handle events in headlines. 

2402 if name.startswith('head'): 

2403 c.frame.tree.onHeadlineKey(event) 

2404 return 

2405 # 

2406 # Handle events in the background tree (not headlines). 

2407 if name.startswith('canvas'): 

2408 if event.char: 

2409 k.searchTree(event.char) 

2410 # Not exactly right, but it seems to be good enough. 

2411 elif not stroke: 

2412 c.onCanvasKey(event) 

2413 return 

2414 # 

2415 # Ignore all events outside the log pane. 

2416 if not name.startswith('log'): 

2417 return 

2418 # 

2419 # Make sure we can insert into w. 

2420 log_w = event.widget 

2421 if not hasattr(log_w, 'supportsHighLevelInterface'): 

2422 return 

2423 # 

2424 # Send the event to the text widget, not the LeoLog instance. 

2425 i = log_w.getInsertPoint() 

2426 s = stroke.toGuiChar() 

2427 log_w.insert(i, s) 

2428 #@+node:ekr.20131016084446.16724: *4* c.setComplexCommand 

2429 def setComplexCommand(self, commandName): 

2430 """Make commandName the command to be executed by repeat-complex-command.""" 

2431 c = self 

2432 c.k.mb_history.insert(0, commandName) 

2433 #@+node:bobjack.20080509080123.2: *4* c.universalCallback & minibufferCallback 

2434 def universalCallback(self, source_c, function): 

2435 """Create a universal command callback. 

2436 

2437 Create and return a callback that wraps a function with an rClick 

2438 signature in a callback which adapts standard minibufer command 

2439 callbacks to a compatible format. 

2440 

2441 This also serves to allow rClick callback functions to handle 

2442 minibuffer commands from sources other than rClick menus so allowing 

2443 a single function to handle calls from all sources. 

2444 

2445 A function wrapped in this wrapper can handle rclick generator 

2446 and invocation commands and commands typed in the minibuffer. 

2447 

2448 It will also be able to handle commands from the minibuffer even 

2449 if rclick is not installed. 

2450 """ 

2451 

2452 def minibufferCallback(event, function=function): 

2453 # Avoid a pylint complaint. 

2454 if hasattr(self, 'theContextMenuController'): 

2455 cm = getattr(self, 'theContextMenuController') 

2456 keywords = cm.mb_keywords 

2457 else: 

2458 cm = keywords = None 

2459 if not keywords: 

2460 # If rClick is not loaded or no keywords dict was provided 

2461 # then the command must have been issued in a minibuffer 

2462 # context. 

2463 keywords = {'c': self, 'rc_phase': 'minibuffer'} 

2464 keywords['mb_event'] = event 

2465 retval = None 

2466 try: 

2467 retval = function(keywords) 

2468 finally: 

2469 if cm: 

2470 # Even if there is an error: 

2471 # clear mb_keywords prior to next command and 

2472 # ensure mb_retval from last command is wiped 

2473 cm.mb_keywords = None 

2474 cm.mb_retval = retval 

2475 

2476 minibufferCallback.__doc__ = function.__doc__ # For g.getDocStringForFunction 

2477 minibufferCallback.source_c = source_c # For GetArgs.command_source 

2478 return minibufferCallback 

2479 

2480 # fix bobjack's spelling error. 

2481 universallCallback = universalCallback 

2482 #@+node:ekr.20070115135502: *4* c.writeScriptFile (changed: does not expand expressions) 

2483 def writeScriptFile(self, script): 

2484 

2485 # Get the path to the file. 

2486 c = self 

2487 path = c.config.getString('script-file-path') 

2488 if path: 

2489 isAbsPath = os.path.isabs(path) 

2490 driveSpec, path = os.path.splitdrive(path) 

2491 parts = path.split('/') 

2492 # xxx bad idea, loadDir is often read only! 

2493 path = g.app.loadDir 

2494 if isAbsPath: 

2495 # make the first element absolute 

2496 parts[0] = driveSpec + os.sep + parts[0] 

2497 allParts = [path] + parts 

2498 path = g.os_path_finalize_join(*allParts) # #1431 

2499 else: 

2500 path = g.os_path_finalize_join(g.app.homeLeoDir, 'scriptFile.py') # #1431 

2501 # 

2502 # Write the file. 

2503 try: 

2504 with open(path, encoding='utf-8', mode='w') as f: 

2505 f.write(script) 

2506 except Exception: 

2507 g.es_exception() 

2508 g.es(f"Failed to write script to {path}") 

2509 # g.es("Check your configuration of script_file_path, currently %s" % 

2510 # c.config.getString('script-file-path')) 

2511 path = None 

2512 return path 

2513 #@+node:ekr.20190921130036.1: *3* c.expand_path_expression 

2514 def expand_path_expression(self, s): 

2515 """Expand all {{anExpression}} in c's context.""" 

2516 c = self 

2517 if not s: 

2518 return '' 

2519 s = g.toUnicode(s) 

2520 # find and replace repeated path expressions 

2521 previ, aList = 0, [] 

2522 while previ < len(s): 

2523 i = s.find('{{', previ) 

2524 j = s.find('}}', previ) 

2525 if -1 < i < j: 

2526 # Add anything from previous index up to '{{' 

2527 if previ < i: 

2528 aList.append(s[previ:i]) 

2529 # Get expression and find substitute 

2530 exp = s[i + 2 : j].strip() 

2531 if exp: 

2532 try: 

2533 s2 = c.replace_path_expression(exp) 

2534 aList.append(s2) 

2535 except Exception: 

2536 g.es(f"Exception evaluating {{{{{exp}}}}} in {s.strip()}") 

2537 g.es_exception(full=True, c=c) 

2538 # Prepare to search again after the last '}}' 

2539 previ = j + 2 

2540 else: 

2541 # Add trailing fragment (fragile in case of mismatched '{{'/'}}') 

2542 aList.append(s[previ:]) 

2543 break 

2544 val = ''.join(aList) 

2545 if g.isWindows: 

2546 val = val.replace('\\', '/') 

2547 return val 

2548 #@+node:ekr.20190921130036.2: *4* c.replace_path_expression 

2549 replace_errors: List[str] = [] 

2550 

2551 def replace_path_expression(self, expr): 

2552 """ local function to replace a single path expression.""" 

2553 c = self 

2554 d = { 

2555 'c': c, 

2556 'g': g, 

2557 # 'getString': c.config.getString, 

2558 'p': c.p, 

2559 'os': os, 

2560 'sep': os.sep, 

2561 'sys': sys, 

2562 } 

2563 # #1338: Don't report errors when called by g.getUrlFromNode. 

2564 try: 

2565 # pylint: disable=eval-used 

2566 path = eval(expr, d) 

2567 return g.toUnicode(path, encoding='utf-8') 

2568 except Exception as e: 

2569 message = ( 

2570 f"{c.shortFileName()}: {c.p.h}\n" 

2571 f"expression: {expr!s}\n" 

2572 f" error: {e!s}") 

2573 if message not in self.replace_errors: 

2574 self.replace_errors.append(message) 

2575 g.trace(message) 

2576 return expr 

2577 #@+node:ekr.20171124101444.1: *3* c.File 

2578 #@+node:ekr.20200305104646.1: *4* c.archivedPositionToPosition (new) 

2579 def archivedPositionToPosition(self, s): 

2580 """Convert an archived position (a string) to a position.""" 

2581 c = self 

2582 s = g.toUnicode(s) 

2583 aList = s.split(',') 

2584 try: 

2585 aList = [int(z) for z in aList] 

2586 except Exception: 

2587 aList = None 

2588 if not aList: 

2589 return None 

2590 p = c.rootPosition() 

2591 level = 0 

2592 while level < len(aList): 

2593 i = aList[level] 

2594 while i > 0: 

2595 if p.hasNext(): 

2596 p.moveToNext() 

2597 i -= 1 

2598 else: 

2599 return None 

2600 level += 1 

2601 if level < len(aList): 

2602 p.moveToFirstChild() 

2603 return p 

2604 #@+node:ekr.20150422080541.1: *4* c.backup 

2605 def backup(self, fileName=None, prefix=None, silent=False, useTimeStamp=True): 

2606 """ 

2607 Back up given fileName or c.fileName(). 

2608 If useTimeStamp is True, append a timestamp to the filename. 

2609 """ 

2610 c = self 

2611 fn = fileName or c.fileName() 

2612 if not fn: 

2613 return None 

2614 theDir, base = g.os_path_split(fn) 

2615 if useTimeStamp: 

2616 if base.endswith('.leo'): 

2617 base = base[:-4] 

2618 stamp = time.strftime("%Y%m%d-%H%M%S") 

2619 branch = prefix + '-' if prefix else '' 

2620 fn = f"{branch}{base}-{stamp}.leo" 

2621 path = g.os_path_finalize_join(theDir, fn) 

2622 else: 

2623 path = fn 

2624 if path: 

2625 # pylint: disable=no-member 

2626 # Defined in commanderFileCommands.py. 

2627 c.saveTo(fileName=path, silent=silent) 

2628 # Issues saved message. 

2629 # g.es('in', theDir) 

2630 return path 

2631 #@+node:ekr.20180210092235.1: *4* c.backup_helper 

2632 def backup_helper(self, 

2633 base_dir=None, 

2634 env_key='LEO_BACKUP', 

2635 sub_dir=None, 

2636 use_git_prefix=True, 

2637 ): 

2638 """ 

2639 A helper for scripts that back up a .leo file. 

2640 Use os.environ[env_key] as the base_dir only if base_dir is not given. 

2641 Backup to base_dir or join(base_dir, sub_dir). 

2642 """ 

2643 c = self 

2644 old_cwd = os.getcwd() 

2645 join = g.os_path_finalize_join 

2646 if not base_dir: 

2647 if env_key: 

2648 try: 

2649 base_dir = os.environ[env_key] 

2650 except KeyError: 

2651 print(f"No environment var: {env_key}") 

2652 base_dir = None 

2653 if base_dir and g.os_path_exists(base_dir): 

2654 if use_git_prefix: 

2655 git_branch, junk = g.gitInfo() 

2656 else: 

2657 git_branch = None 

2658 theDir, fn = g.os_path_split(c.fileName()) 

2659 backup_dir = join(base_dir, sub_dir) if sub_dir else base_dir 

2660 path = join(backup_dir, fn) 

2661 if g.os_path_exists(backup_dir): 

2662 written_fn = c.backup( 

2663 path, 

2664 prefix=git_branch, 

2665 silent=True, 

2666 useTimeStamp=True, 

2667 ) 

2668 g.es_print(f"wrote: {written_fn}") 

2669 else: 

2670 g.es_print(f"backup_dir not found: {backup_dir!r}") 

2671 else: 

2672 g.es_print(f"base_dir not found: {base_dir!r}") 

2673 os.chdir(old_cwd) 

2674 #@+node:ekr.20090103070824.11: *4* c.checkFileTimeStamp 

2675 def checkFileTimeStamp(self, fn): 

2676 """ 

2677 Return True if the file given by fn has not been changed 

2678 since Leo read it or if the user agrees to overwrite it. 

2679 """ 

2680 c = self 

2681 if g.app.externalFilesController: 

2682 return g.app.externalFilesController.check_overwrite(c, fn) 

2683 return True 

2684 #@+node:ekr.20090212054250.9: *4* c.createNodeFromExternalFile 

2685 def createNodeFromExternalFile(self, fn): 

2686 """ 

2687 Read the file into a node. 

2688 Return None, indicating that c.open should set focus. 

2689 """ 

2690 c = self 

2691 s, e = g.readFileIntoString(fn) 

2692 if s is None: 

2693 return 

2694 head, ext = g.os_path_splitext(fn) 

2695 if ext.startswith('.'): 

2696 ext = ext[1:] 

2697 language = g.app.extension_dict.get(ext) 

2698 if language: 

2699 prefix = f"@color\n@language {language}\n\n" 

2700 else: 

2701 prefix = '@killcolor\n\n' 

2702 # pylint: disable=no-member 

2703 # Defined in commanderOutlineCommands.py 

2704 p2 = c.insertHeadline(op_name='Open File', as_child=False) 

2705 p2.h = f"@edit {fn}" 

2706 p2.b = prefix + s 

2707 w = c.frame.body.wrapper 

2708 if w: 

2709 w.setInsertPoint(0) 

2710 c.redraw() 

2711 c.recolor() 

2712 #@+node:ekr.20110530124245.18248: *4* c.looksLikeDerivedFile 

2713 def looksLikeDerivedFile(self, fn): 

2714 """ 

2715 Return True if fn names a file that looks like an 

2716 external file written by Leo. 

2717 """ 

2718 # c = self 

2719 try: 

2720 with open(fn, 'rb') as f: # 2020/11/14: Allow unicode characters! 

2721 b = f.read() 

2722 s = g.toUnicode(b) 

2723 return s.find('@+leo-ver=') > -1 

2724 except Exception: 

2725 g.es_exception() 

2726 return False 

2727 #@+node:ekr.20031218072017.2925: *4* c.markAllAtFileNodesDirty 

2728 def markAllAtFileNodesDirty(self, event=None): 

2729 """Mark all @file nodes as changed.""" 

2730 c = self 

2731 c.endEditing() 

2732 p = c.rootPosition() 

2733 while p: 

2734 if p.isAtFileNode(): 

2735 p.setDirty() 

2736 c.setChanged() 

2737 p.moveToNodeAfterTree() 

2738 else: 

2739 p.moveToThreadNext() 

2740 c.redraw_after_icons_changed() 

2741 #@+node:ekr.20031218072017.2926: *4* c.markAtFileNodesDirty 

2742 def markAtFileNodesDirty(self, event=None): 

2743 """Mark all @file nodes in the selected tree as changed.""" 

2744 c = self 

2745 p = c.p 

2746 if not p: 

2747 return 

2748 c.endEditing() 

2749 after = p.nodeAfterTree() 

2750 while p and p != after: 

2751 if p.isAtFileNode(): 

2752 p.setDirty() 

2753 c.setChanged() 

2754 p.moveToNodeAfterTree() 

2755 else: 

2756 p.moveToThreadNext() 

2757 c.redraw_after_icons_changed() 

2758 #@+node:ekr.20031218072017.2823: *4* c.openWith 

2759 def openWith(self, event=None, d=None): 

2760 """ 

2761 This is *not* a command. 

2762 

2763 Handles the items in the Open With... menu. 

2764 

2765 See ExternalFilesController.open_with for details about d. 

2766 """ 

2767 c = self 

2768 if d and g.app.externalFilesController: 

2769 # Select an ancestor @<file> node if possible. 

2770 if not d.get('p'): 

2771 d['p'] = None 

2772 p = c.p 

2773 while p: 

2774 if p.isAnyAtFileNode(): 

2775 d['p'] = p 

2776 break 

2777 p.moveToParent() 

2778 g.app.externalFilesController.open_with(c, d) 

2779 elif not d: 

2780 g.trace('can not happen: no d', g.callers()) 

2781 #@+node:ekr.20140717074441.17770: *4* c.recreateGnxDict 

2782 def recreateGnxDict(self): 

2783 """Recreate the gnx dict prior to refreshing nodes from disk.""" 

2784 c, d = self, {} 

2785 for v in c.all_unique_nodes(): 

2786 gnxString = v.fileIndex 

2787 if isinstance(gnxString, str): 

2788 d[gnxString] = v 

2789 if 'gnx' in g.app.debug: 

2790 g.trace(c.shortFileName(), gnxString, v) 

2791 else: 

2792 g.internalError(f"no gnx for vnode: {v}") 

2793 c.fileCommands.gnxDict = d 

2794 #@+node:ekr.20180508111544.1: *3* c.Git 

2795 #@+node:ekr.20180510104805.1: *4* c.diff_file 

2796 def diff_file(self, fn, rev1='HEAD', rev2=''): 

2797 """ 

2798 Create an outline describing the git diffs for all files changed 

2799 between rev1 and rev2. 

2800 """ 

2801 from leo.commands import editFileCommands as efc 

2802 x = efc.GitDiffController(c=self) 

2803 x.diff_file(fn=fn, rev1=rev1, rev2=rev2) 

2804 #@+node:ekr.20180508110755.1: *4* c.diff_two_revs 

2805 def diff_two_revs(self, directory=None, rev1='', rev2=''): 

2806 """ 

2807 Create an outline describing the git diffs for all files changed 

2808 between rev1 and rev2. 

2809 """ 

2810 from leo.commands import editFileCommands as efc 

2811 efc.GitDiffController(c=self).diff_two_revs(rev1=rev1, rev2=rev2) 

2812 #@+node:ekr.20180510103923.1: *4* c.diff_two_branches 

2813 def diff_two_branches(self, branch1, branch2, fn): 

2814 """ 

2815 Create an outline describing the git diffs for all files changed 

2816 between rev1 and rev2. 

2817 """ 

2818 from leo.commands import editFileCommands as efc 

2819 efc.GitDiffController(c=self).diff_two_branches( 

2820 branch1=branch1, branch2=branch2, fn=fn) 

2821 #@+node:ekr.20180510105125.1: *4* c.git_diff 

2822 def git_diff(self, rev1='HEAD', rev2=''): 

2823 

2824 from leo.commands import editFileCommands as efc 

2825 efc.GitDiffController(c=self).git_diff(rev1, rev2) 

2826 #@+node:ekr.20171124100534.1: *3* c.Gui 

2827 #@+node:ekr.20111217154130.10286: *4* c.Dialogs & messages 

2828 #@+node:ekr.20110510052422.14618: *5* c.alert 

2829 def alert(self, message): 

2830 c = self 

2831 # The unit tests just tests the args. 

2832 if not g.unitTesting: 

2833 g.es(message) 

2834 g.app.gui.alert(c, message) 

2835 #@+node:ekr.20111217154130.10284: *5* c.init_error_dialogs 

2836 def init_error_dialogs(self): 

2837 c = self 

2838 c.import_error_nodes = [] 

2839 c.ignored_at_file_nodes = [] 

2840 c.orphan_at_file_nodes = [] 

2841 #@+node:ekr.20171123135805.1: *5* c.notValidInBatchMode 

2842 def notValidInBatchMode(self, commandName): 

2843 g.es('the', commandName, "command is not valid in batch mode") 

2844 #@+node:ekr.20110530082209.18250: *5* c.putHelpFor 

2845 def putHelpFor(self, s, short_title=''): 

2846 """Helper for various help commands.""" 

2847 c = self 

2848 g.app.gui.put_help(c, s, short_title) 

2849 #@+node:ekr.20111217154130.10285: *5* c.raise_error_dialogs 

2850 warnings_dict: Dict[str, bool] = {} 

2851 

2852 def raise_error_dialogs(self, kind='read'): 

2853 """Warn about read/write failures.""" 

2854 c = self 

2855 use_dialogs = False 

2856 if g.unitTesting: 

2857 c.init_error_dialogs() 

2858 return 

2859 # Issue one or two dialogs or messages. 

2860 saved_body = c.rootPosition().b # Save the root's body. The dialog destroys it! 

2861 if c.import_error_nodes or c.ignored_at_file_nodes or c.orphan_at_file_nodes: 

2862 g.app.gui.dismiss_splash_screen() 

2863 else: 

2864 # #1007: Exit now, so we don't have to restore c.rootPosition().b. 

2865 c.init_error_dialogs() 

2866 return 

2867 if c.import_error_nodes: 

2868 files = '\n'.join(sorted(set(c.import_error_nodes))) # type:ignore 

2869 if files not in self.warnings_dict: 

2870 self.warnings_dict[files] = True 

2871 import_message1 = 'The following were not imported properly.' 

2872 import_message2 = f"Inserted @ignore in...\n{files}" 

2873 g.es_print(import_message1, color='red') 

2874 g.es_print(import_message2) 

2875 if use_dialogs: 

2876 import_dialog_message = f"{import_message1}\n{import_message2}" 

2877 g.app.gui.runAskOkDialog(c, 

2878 message=import_dialog_message, title='Import errors') 

2879 if c.ignored_at_file_nodes: 

2880 files = '\n'.join(sorted(set(c.ignored_at_file_nodes))) # type:ignore 

2881 if files not in self.warnings_dict: 

2882 self.warnings_dict[files] = True 

2883 kind_s = 'read' if kind == 'read' else 'written' 

2884 ignored_message = f"The following were not {kind_s} because they contain @ignore:" 

2885 kind = 'read' if kind.startswith('read') else 'written' 

2886 g.es_print(ignored_message, color='red') 

2887 g.es_print(files) 

2888 if use_dialogs: 

2889 ignored_dialog_message = f"{ignored_message}\n{files}" 

2890 g.app.gui.runAskOkDialog(c, 

2891 message=ignored_dialog_message, title=f"Not {kind.capitalize()}") 

2892 # #1050: always raise a dialog for orphan @<file> nodes. 

2893 if c.orphan_at_file_nodes: 

2894 message = '\n'.join([ 

2895 'The following were not written because of errors:\n', 

2896 '\n'.join(sorted(set(c.orphan_at_file_nodes))), # type:ignore 

2897 '', 

2898 'Warning: changes to these files will be lost\n' 

2899 'unless you can save the files successfully.' 

2900 ]) 

2901 g.app.gui.runAskOkDialog(c, message=message, title='Not Written') 

2902 # Mark all the nodes dirty. 

2903 for z in c.all_unique_positions(): 

2904 if z.isOrphan(): 

2905 z.setDirty() 

2906 z.clearOrphan() 

2907 c.setChanged() 

2908 c.redraw() 

2909 # Restore the root position's body. 

2910 c.rootPosition().v.b = saved_body # #1007: just set v.b. 

2911 c.init_error_dialogs() 

2912 #@+node:ekr.20150710083827.1: *5* c.syntaxErrorDialog 

2913 def syntaxErrorDialog(self): 

2914 """Warn about syntax errors in files.""" 

2915 c = self 

2916 if g.app.syntax_error_files and c.config.getBool( 

2917 'syntax-error-popup', default=False): 

2918 aList = sorted(set(g.app.syntax_error_files)) 

2919 g.app.syntax_error_files = [] 

2920 list_s = '\n'.join(aList) 

2921 g.app.gui.runAskOkDialog( 

2922 c, 

2923 title='Python Errors', 

2924 message=f"Python errors in:\n\n{list_s}", 

2925 text="Ok", 

2926 ) 

2927 #@+node:ekr.20031218072017.2945: *4* c.Dragging 

2928 #@+node:ekr.20031218072017.2947: *5* c.dragToNthChildOf 

2929 def dragToNthChildOf(self, p, parent, n): 

2930 c, p, u = self, self.p, self.undoer 

2931 if not c.checkDrag(p, parent): 

2932 return 

2933 if not c.checkMoveWithParentWithWarning(p, parent, True): 

2934 return 

2935 c.endEditing() 

2936 undoData = u.beforeMoveNode(p) 

2937 p.setDirty() 

2938 p.moveToNthChildOf(parent, n) 

2939 p.setDirty() 

2940 c.setChanged() 

2941 u.afterMoveNode(p, 'Drag', undoData) 

2942 c.redraw(p) 

2943 c.updateSyntaxColorer(p) # Dragging can change syntax coloring. 

2944 #@+node:ekr.20031218072017.2353: *5* c.dragAfter 

2945 def dragAfter(self, p, after): 

2946 c, p, u = self, self.p, self.undoer 

2947 if not c.checkDrag(p, after): 

2948 return 

2949 if not c.checkMoveWithParentWithWarning(p, after.parent(), True): 

2950 return 

2951 c.endEditing() 

2952 undoData = u.beforeMoveNode(p) 

2953 p.setDirty() 

2954 p.moveAfter(after) 

2955 p.setDirty() 

2956 c.setChanged() 

2957 u.afterMoveNode(p, 'Drag', undoData) 

2958 c.redraw(p) 

2959 c.updateSyntaxColorer(p) # Dragging can change syntax coloring. 

2960 #@+node:ekr.20031218072017.2946: *5* c.dragCloneToNthChildOf 

2961 def dragCloneToNthChildOf(self, p, parent, n): 

2962 c = self 

2963 u = c.undoer 

2964 undoType = 'Clone Drag' 

2965 current = c.p 

2966 clone = p.clone() # Creates clone & dependents, does not set undo. 

2967 if ( 

2968 not c.checkDrag(p, parent) or 

2969 not c.checkMoveWithParentWithWarning(clone, parent, True) 

2970 ): 

2971 clone.doDelete(newNode=p) # Destroys clone and makes p the current node. 

2972 c.selectPosition(p) # Also sets root position. 

2973 return 

2974 c.endEditing() 

2975 undoData = u.beforeInsertNode(current) 

2976 clone.setDirty() 

2977 clone.moveToNthChildOf(parent, n) 

2978 clone.setDirty() 

2979 c.setChanged() 

2980 u.afterInsertNode(clone, undoType, undoData) 

2981 c.redraw(clone) 

2982 c.updateSyntaxColorer(clone) # Dragging can change syntax coloring. 

2983 #@+node:ekr.20031218072017.2948: *5* c.dragCloneAfter 

2984 def dragCloneAfter(self, p, after): 

2985 c = self 

2986 u = c.undoer 

2987 undoType = 'Clone Drag' 

2988 current = c.p 

2989 clone = p.clone() # Creates clone. Does not set undo. 

2990 if c.checkDrag( 

2991 p, after) and c.checkMoveWithParentWithWarning(clone, after.parent(), True): 

2992 c.endEditing() 

2993 undoData = u.beforeInsertNode(current) 

2994 clone.setDirty() 

2995 clone.moveAfter(after) 

2996 clone.v.setDirty() 

2997 c.setChanged() 

2998 u.afterInsertNode(clone, undoType, undoData) 

2999 p = clone 

3000 else: 

3001 clone.doDelete(newNode=p) 

3002 c.redraw(p) 

3003 c.updateSyntaxColorer(clone) # Dragging can change syntax coloring. 

3004 #@+node:ekr.20031218072017.2949: *4* c.Drawing 

3005 #@+node:ekr.20080514131122.8: *5* c.bringToFront 

3006 def bringToFront(self, c2=None): 

3007 c = self 

3008 c2 = c2 or c 

3009 g.app.gui.ensure_commander_visible(c2) 

3010 

3011 BringToFront = bringToFront # Compatibility with old scripts 

3012 #@+node:ekr.20040803072955.143: *5* c.expandAllAncestors 

3013 def expandAllAncestors(self, p): 

3014 """ 

3015 Expand all ancestors without redrawing. 

3016 Return a flag telling whether a redraw is needed. 

3017 """ 

3018 # c = self 

3019 redraw_flag = False 

3020 for p in p.parents(): 

3021 if not p.v.isExpanded(): 

3022 p.v.expand() 

3023 p.expand() 

3024 redraw_flag = True 

3025 elif p.isExpanded(): 

3026 p.v.expand() 

3027 else: 

3028 p.expand() 

3029 redraw_flag = True 

3030 return redraw_flag 

3031 #@+node:ekr.20080514131122.20: *5* c.outerUpdate 

3032 def outerUpdate(self): 

3033 """Handle delayed focus requests and modified events.""" 

3034 c = self 

3035 if not c.exists or not c.k: 

3036 return 

3037 # New in Leo 5.6: Delayed redraws are useful in utility methods. 

3038 if c.requestLaterRedraw: 

3039 if c.enableRedrawFlag: 

3040 c.requestLaterRedraw = False 

3041 if 'drawing' in g.app.debug and not g.unitTesting: 

3042 g.trace('\nDELAYED REDRAW') 

3043 time.sleep(1.0) 

3044 c.redraw() 

3045 # Delayed focus requests will always be useful. 

3046 if c.requestedFocusWidget: 

3047 w = c.requestedFocusWidget 

3048 if 'focus' in g.app.debug and not g.unitTesting: 

3049 if hasattr(w, 'objectName'): 

3050 name = w.objectName() 

3051 else: 

3052 name = w.__class__.__name__ 

3053 g.trace('DELAYED FOCUS', name) 

3054 c.set_focus(w) 

3055 c.requestedFocusWidget = None 

3056 table = ( 

3057 ("childrenModified", g.childrenModifiedSet), 

3058 ("contentModified", g.contentModifiedSet), 

3059 ) 

3060 for kind, mods in table: 

3061 if mods: 

3062 g.doHook(kind, c=c, nodes=mods) 

3063 mods.clear() 

3064 #@+node:ekr.20080514131122.13: *5* c.recolor 

3065 def recolor(self, p=None): 

3066 # Support QScintillaColorizer.colorize. 

3067 c = self 

3068 colorizer = c.frame.body.colorizer 

3069 if colorizer and hasattr(colorizer, 'colorize'): 

3070 colorizer.colorize(p or c.p) 

3071 

3072 recolor_now = recolor 

3073 #@+node:ekr.20080514131122.14: *5* c.redrawing... 

3074 #@+node:ekr.20170808014610.1: *6* c.enable/disable_redraw 

3075 def disable_redraw(self): 

3076 """Disable all redrawing until enabled.""" 

3077 c = self 

3078 c.enableRedrawFlag = False 

3079 

3080 def enable_redraw(self): 

3081 c = self 

3082 c.enableRedrawFlag = True 

3083 #@+node:ekr.20090110073010.1: *6* c.redraw 

3084 @cmd('redraw') 

3085 def redraw_command(self, event): 

3086 c = event.get('c') 

3087 if c: 

3088 c.redraw() 

3089 

3090 def redraw(self, p=None): 

3091 """ 

3092 Redraw the screen immediately. 

3093 If p is given, set c.p to p. 

3094 """ 

3095 c = self 

3096 # New in Leo 5.6: clear the redraw request. 

3097 c.requestLaterRedraw = False 

3098 if not p: 

3099 p = c.p or c.rootPosition() 

3100 if not p: 

3101 return 

3102 c.expandAllAncestors(p) 

3103 if p: 

3104 # Fix bug https://bugs.launchpad.net/leo-editor/+bug/1183855 

3105 # This looks redundant, but it is probably the only safe fix. 

3106 c.frame.tree.select(p) 

3107 # tree.redraw will change the position if p is a hoisted @chapter node. 

3108 p2 = c.frame.tree.redraw(p) 

3109 # Be careful. NullTree.redraw returns None. 

3110 # #503: NullTree.redraw(p) now returns p. 

3111 c.selectPosition(p2 or p) 

3112 # Do not call treeFocusHelper here. 

3113 # c.treeFocusHelper() 

3114 # Clear the redraw request, again. 

3115 c.requestLaterRedraw = False 

3116 

3117 # Compatibility with old scripts 

3118 

3119 force_redraw = redraw 

3120 redraw_now = redraw 

3121 #@+node:ekr.20090110073010.3: *6* c.redraw_afer_icons_changed 

3122 def redraw_after_icons_changed(self): 

3123 """Update the icon for the presently selected node""" 

3124 c = self 

3125 if c.enableRedrawFlag: 

3126 c.frame.tree.redraw_after_icons_changed() 

3127 # Do not call treeFocusHelper here. 

3128 # c.treeFocusHelper() 

3129 else: 

3130 c.requestLaterRedraw = True 

3131 #@+node:ekr.20090110131802.2: *6* c.redraw_after_contract 

3132 def redraw_after_contract(self, p=None): 

3133 c = self 

3134 if c.enableRedrawFlag: 

3135 if p: 

3136 c.setCurrentPosition(p) 

3137 else: 

3138 p = c.currentPosition() 

3139 c.frame.tree.redraw_after_contract(p) 

3140 c.treeFocusHelper() 

3141 else: 

3142 c.requestLaterRedraw = True 

3143 #@+node:ekr.20090112065525.1: *6* c.redraw_after_expand 

3144 def redraw_after_expand(self, p): 

3145 c = self 

3146 if c.enableRedrawFlag: 

3147 if p: 

3148 c.setCurrentPosition(p) 

3149 else: 

3150 p = c.currentPosition() 

3151 c.frame.tree.redraw_after_expand(p) 

3152 c.treeFocusHelper() 

3153 else: 

3154 c.requestLaterRedraw = True 

3155 #@+node:ekr.20090110073010.2: *6* c.redraw_after_head_changed 

3156 def redraw_after_head_changed(self): 

3157 """ 

3158 Redraw the screen (if needed) when editing ends. 

3159 This may be a do-nothing for some gui's. 

3160 """ 

3161 c = self 

3162 if c.enableRedrawFlag: 

3163 self.frame.tree.redraw_after_head_changed() 

3164 else: 

3165 c.requestLaterRedraw = True 

3166 #@+node:ekr.20090110073010.4: *6* c.redraw_after_select 

3167 def redraw_after_select(self, p): 

3168 """Redraw the screen after node p has been selected.""" 

3169 c = self 

3170 if c.enableRedrawFlag: 

3171 flag = c.expandAllAncestors(p) 

3172 if flag: 

3173 c.frame.tree.redraw_after_select(p) 

3174 # This is the same as c.frame.tree.full_redraw(). 

3175 else: 

3176 c.requestLaterRedraw = True 

3177 #@+node:ekr.20170908081918.1: *6* c.redraw_later 

3178 def redraw_later(self): 

3179 """ 

3180 Ensure that c.redraw() will be called eventually. 

3181 

3182 c.outerUpdate will call c.redraw() only if no other code calls c.redraw(). 

3183 """ 

3184 c = self 

3185 c.requestLaterRedraw = True 

3186 if 'drawing' in g.app.debug: 

3187 # g.trace('\n' + g.callers(8)) 

3188 g.trace(g.callers()) 

3189 #@+node:ekr.20080514131122.17: *5* c.widget_name 

3190 def widget_name(self, widget): 

3191 # c = self 

3192 return g.app.gui.widget_name(widget) if g.app.gui else '<no widget>' 

3193 #@+node:ekr.20171124101045.1: *4* c.Events 

3194 #@+node:ekr.20060923202156: *5* c.onCanvasKey 

3195 def onCanvasKey(self, event): 

3196 """ 

3197 Navigate to the next headline starting with ch = event.char. 

3198 If ch is uppercase, search all headlines; otherwise search only visible headlines. 

3199 This is modelled on Windows explorer. 

3200 """ 

3201 if not event or not event.char or not event.char.isalnum(): 

3202 return 

3203 c, p = self, self.p 

3204 p1 = p.copy() 

3205 invisible = c.config.getBool('invisible-outline-navigation') 

3206 ch = event.char if event else '' 

3207 allFlag = ch.isupper() and invisible # all is a global (!?) 

3208 if not invisible: 

3209 ch = ch.lower() 

3210 found = False 

3211 extend = self.navQuickKey() 

3212 attempts = (True, False) if extend else (False,) 

3213 for extend2 in attempts: 

3214 p = p1.copy() 

3215 while 1: 

3216 if allFlag: 

3217 p.moveToThreadNext() 

3218 else: 

3219 p.moveToVisNext(c) 

3220 if not p: 

3221 p = c.rootPosition() 

3222 if p == p1: # Never try to match the same position. 

3223 found = False 

3224 break 

3225 newPrefix = c.navHelper(p, ch, extend2) 

3226 if newPrefix: 

3227 found = True 

3228 break 

3229 if found: 

3230 break 

3231 if found: 

3232 c.selectPosition(p) 

3233 c.redraw_after_select(p) 

3234 c.navTime = time.time() 

3235 c.navPrefix = newPrefix 

3236 else: 

3237 c.navTime = None 

3238 c.navPrefix = '' 

3239 c.treeWantsFocus() 

3240 #@+node:ekr.20061002095711.1: *6* c.navQuickKey 

3241 def navQuickKey(self) -> bool: 

3242 """ 

3243 Return true if there are two quick outline navigation keys 

3244 in quick succession. 

3245 

3246 Returns False if @float outline_nav_extend_delay setting is 0.0 or unspecified. 

3247 """ 

3248 c = self 

3249 deltaTime = c.config.getFloat('outline-nav-extend-delay') 

3250 if deltaTime in (None, 0.0): 

3251 return False 

3252 if c.navTime is None: 

3253 return False # mypy. 

3254 return time.time() - c.navTime < deltaTime 

3255 #@+node:ekr.20061002095711: *6* c.navHelper 

3256 def navHelper(self, p, ch, extend): 

3257 c = self 

3258 h = p.h.lower() 

3259 if extend: 

3260 prefix = c.navPrefix + ch 

3261 return h.startswith(prefix.lower()) and prefix 

3262 if h.startswith(ch): 

3263 return ch 

3264 # New feature: search for first non-blank character after @x for common x. 

3265 if ch != '@' and h.startswith('@'): 

3266 for s in ('button', 'command', 'file', 'thin', 'asis', 'nosent',): 

3267 prefix = '@' + s 

3268 if h.startswith('@' + s): 

3269 while 1: 

3270 n = len(prefix) 

3271 ch2 = h[n] if n < len(h) else '' 

3272 if ch2.isspace(): 

3273 prefix = prefix + ch2 

3274 else: break 

3275 if len(prefix) < len(h) and h.startswith(prefix + ch.lower()): 

3276 return prefix + ch 

3277 return '' 

3278 #@+node:ekr.20031218072017.2909: *4* c.Expand/contract 

3279 #@+node:ekr.20171124091426.1: *5* c.contractAllHeadlines 

3280 def contractAllHeadlines(self, event=None): 

3281 """Contract all nodes in the outline.""" 

3282 c = self 

3283 for v in c.all_nodes(): 

3284 v.contract() 

3285 if c.hoistStack: 

3286 # #2380: Handle hoists properly. 

3287 bunch = c.hoistStack[-1] 

3288 p = bunch.p 

3289 else: 

3290 # Select the topmost ancestor of the presently selected node. 

3291 p = c.p 

3292 while p and p.hasParent(): 

3293 p.moveToParent() 

3294 c.selectPosition(p) # #2380: Don't redraw here. 

3295 c.expansionLevel = 1 # Reset expansion level. 

3296 #@+node:ekr.20031218072017.2910: *5* c.contractSubtree 

3297 def contractSubtree(self, p): 

3298 for p in p.subtree(): 

3299 p.contract() 

3300 #@+node:ekr.20031218072017.2911: *5* c.expandSubtree 

3301 def expandSubtree(self, p): 

3302 # c = self 

3303 last = p.lastNode() 

3304 p = p.copy() 

3305 while p and p != last: 

3306 p.expand() 

3307 p = p.moveToThreadNext() 

3308 #@+node:ekr.20031218072017.2912: *5* c.expandToLevel 

3309 def expandToLevel(self, level): 

3310 

3311 c = self 

3312 n = c.p.level() 

3313 old_expansion_level = c.expansionLevel 

3314 max_level = 0 

3315 for p in c.p.self_and_subtree(copy=False): 

3316 if p.level() - n + 1 < level: 

3317 p.expand() 

3318 max_level = max(max_level, p.level() - n + 1) 

3319 else: 

3320 p.contract() 

3321 c.expansionNode = c.p.copy() 

3322 c.expansionLevel = max_level + 1 

3323 if c.expansionLevel != old_expansion_level: 

3324 c.redraw() 

3325 # It's always useful to announce the level. 

3326 # c.k.setLabelBlue('level: %s' % (max_level+1)) 

3327 # g.es('level', max_level + 1) 

3328 c.frame.putStatusLine(f"level: {max_level + 1}") 

3329 # bg='red', fg='red') 

3330 #@+node:ekr.20141028061518.23: *4* c.Focus 

3331 #@+node:ekr.20080514131122.9: *5* c.get/request/set_focus 

3332 def get_focus(self): 

3333 c = self 

3334 w = g.app.gui and g.app.gui.get_focus(c) 

3335 if 'focus' in g.app.debug: 

3336 name = w.objectName() if hasattr(w, 'objectName') else w.__class__.__name__ 

3337 g.trace('(c)', name) 

3338 # g.trace('\n(c)', w.__class__.__name__) 

3339 # g.trace(g.callers(6)) 

3340 return w 

3341 

3342 def get_requested_focus(self): 

3343 c = self 

3344 return c.requestedFocusWidget 

3345 

3346 def request_focus(self, w): 

3347 c = self 

3348 if w and g.app.gui: 

3349 if 'focus' in g.app.debug: 

3350 # g.trace('\n(c)', repr(w)) 

3351 name = w.objectName( 

3352 ) if hasattr(w, 'objectName') else w.__class__.__name__ 

3353 g.trace('(c)', name) 

3354 c.requestedFocusWidget = w 

3355 

3356 def set_focus(self, w): 

3357 trace = 'focus' in g.app.debug 

3358 c = self 

3359 if w and g.app.gui: 

3360 if trace: 

3361 name = w.objectName( 

3362 ) if hasattr(w, 'objectName') else w.__class__.__name__ 

3363 g.trace('(c)', name) 

3364 g.app.gui.set_focus(c, w) 

3365 else: 

3366 if trace: 

3367 g.trace('(c) no w') 

3368 c.requestedFocusWidget = None 

3369 #@+node:ekr.20080514131122.10: *5* c.invalidateFocus (do nothing) 

3370 def invalidateFocus(self): 

3371 """Indicate that the focus is in an invalid location, or is unknown.""" 

3372 # c = self 

3373 # c.requestedFocusWidget = None 

3374 pass 

3375 #@+node:ekr.20080514131122.16: *5* c.traceFocus (not used) 

3376 def traceFocus(self, w): 

3377 c = self 

3378 if 'focus' in g.app.debug: 

3379 c.trace_focus_count += 1 

3380 g.pr(f"{c.trace_focus_count:4d}", c.widget_name(w), g.callers(8)) 

3381 #@+node:ekr.20070226121510: *5* c.xFocusHelper & initialFocusHelper 

3382 def treeFocusHelper(self): 

3383 c = self 

3384 if c.stayInTreeAfterSelect: 

3385 c.treeWantsFocus() 

3386 else: 

3387 c.bodyWantsFocus() 

3388 

3389 def initialFocusHelper(self): 

3390 c = self 

3391 if c.outlineHasInitialFocus: 

3392 c.treeWantsFocus() 

3393 else: 

3394 c.bodyWantsFocus() 

3395 #@+node:ekr.20080514131122.18: *5* c.xWantsFocus 

3396 def bodyWantsFocus(self): 

3397 c = self 

3398 body = c.frame.body 

3399 c.request_focus(body and body.wrapper) 

3400 

3401 def logWantsFocus(self): 

3402 c = self 

3403 log = c.frame.log 

3404 c.request_focus(log and log.logCtrl) 

3405 

3406 def minibufferWantsFocus(self): 

3407 c = self 

3408 c.request_focus(c.miniBufferWidget) 

3409 

3410 def treeWantsFocus(self): 

3411 c = self 

3412 tree = c.frame.tree 

3413 c.request_focus(tree and tree.canvas) 

3414 

3415 def widgetWantsFocus(self, w): 

3416 c = self 

3417 c.request_focus(w) 

3418 #@+node:ekr.20080514131122.19: *5* c.xWantsFocusNow 

3419 # widgetWantsFocusNow does an automatic update. 

3420 

3421 def widgetWantsFocusNow(self, w): 

3422 c = self 

3423 if w: 

3424 c.set_focus(w) 

3425 c.requestedFocusWidget = None 

3426 

3427 # New in 4.9: all FocusNow methods now *do* call c.outerUpdate(). 

3428 

3429 def bodyWantsFocusNow(self): 

3430 c, body = self, self.frame.body 

3431 c.widgetWantsFocusNow(body and body.wrapper) 

3432 

3433 def logWantsFocusNow(self): 

3434 c, log = self, self.frame.log 

3435 c.widgetWantsFocusNow(log and log.logCtrl) 

3436 

3437 def minibufferWantsFocusNow(self): 

3438 c = self 

3439 c.widgetWantsFocusNow(c.miniBufferWidget) 

3440 

3441 def treeWantsFocusNow(self): 

3442 c, tree = self, self.frame.tree 

3443 c.widgetWantsFocusNow(tree and tree.canvas) 

3444 #@+node:ekr.20031218072017.2955: *4* c.Menus 

3445 #@+node:ekr.20080610085158.2: *5* c.add_command 

3446 def add_command(self, menu, **keys): 

3447 c = self 

3448 command = keys.get('command') 

3449 if command: 

3450 # Command is always either: 

3451 # one of two callbacks defined in createMenuEntries or 

3452 # recentFilesCallback, defined in createRecentFilesMenuItems. 

3453 

3454 def add_commandCallback(c=c, command=command): 

3455 val = command() 

3456 # Careful: func may destroy c. 

3457 if c.exists: 

3458 c.outerUpdate() 

3459 return val 

3460 

3461 keys['command'] = add_commandCallback 

3462 menu.add_command(**keys) 

3463 else: 

3464 g.trace('can not happen: no "command" arg') 

3465 #@+node:ekr.20171123203044.1: *5* c.Menu Enablers 

3466 #@+node:ekr.20040131170659: *6* c.canClone 

3467 def canClone(self): 

3468 c = self 

3469 if c.hoistStack: 

3470 current = c.p 

3471 bunch = c.hoistStack[-1] 

3472 return current != bunch.p 

3473 return True 

3474 #@+node:ekr.20031218072017.2956: *6* c.canContractAllHeadlines 

3475 def canContractAllHeadlines(self): 

3476 """Contract all nodes in the tree.""" 

3477 c = self 

3478 for p in c.all_positions(): # was c.all_unique_positions() 

3479 if p.isExpanded(): 

3480 return True 

3481 return False 

3482 #@+node:ekr.20031218072017.2957: *6* c.canContractAllSubheads 

3483 def canContractAllSubheads(self): 

3484 current = self.p 

3485 for p in current.subtree(): 

3486 if p != current and p.isExpanded(): 

3487 return True 

3488 return False 

3489 #@+node:ekr.20031218072017.2958: *6* c.canContractParent 

3490 def canContractParent(self) -> bool: 

3491 c = self 

3492 return c.p.parent() 

3493 #@+node:ekr.20031218072017.2959: *6* c.canContractSubheads 

3494 def canContractSubheads(self): 

3495 current = self.p 

3496 for child in current.children(): 

3497 if child.isExpanded(): 

3498 return True 

3499 return False 

3500 #@+node:ekr.20031218072017.2960: *6* c.canCutOutline & canDeleteHeadline 

3501 def canDeleteHeadline(self): 

3502 c, p = self, self.p 

3503 if c.hoistStack: 

3504 bunch = c.hoistStack[0] 

3505 if p == bunch.p: 

3506 return False 

3507 return p.hasParent() or p.hasThreadBack() or p.hasNext() 

3508 

3509 canCutOutline = canDeleteHeadline 

3510 #@+node:ekr.20031218072017.2961: *6* c.canDemote 

3511 def canDemote(self) -> bool: 

3512 c = self 

3513 return c.p.hasNext() 

3514 #@+node:ekr.20031218072017.2962: *6* c.canExpandAllHeadlines 

3515 def canExpandAllHeadlines(self): 

3516 """Return True if the Expand All Nodes menu item should be enabled.""" 

3517 c = self 

3518 for p in c.all_positions(): # was c.all_unique_positions() 

3519 if not p.isExpanded(): 

3520 return True 

3521 return False 

3522 #@+node:ekr.20031218072017.2963: *6* c.canExpandAllSubheads 

3523 def canExpandAllSubheads(self): 

3524 c = self 

3525 for p in c.p.subtree(): 

3526 if not p.isExpanded(): 

3527 return True 

3528 return False 

3529 #@+node:ekr.20031218072017.2964: *6* c.canExpandSubheads 

3530 def canExpandSubheads(self): 

3531 current = self.p 

3532 for p in current.children(): 

3533 if p != current and not p.isExpanded(): 

3534 return True 

3535 return False 

3536 #@+node:ekr.20031218072017.2287: *6* c.canExtract, canExtractSection & canExtractSectionNames 

3537 def canExtract(self) -> bool: 

3538 c = self 

3539 w = c.frame.body.wrapper 

3540 return w and w.hasSelection() 

3541 

3542 canExtractSectionNames = canExtract 

3543 

3544 def canExtractSection(self): 

3545 c = self 

3546 w = c.frame.body.wrapper 

3547 if not w: 

3548 return False 

3549 s = w.getSelectedText() 

3550 if not s: 

3551 return False 

3552 line = g.get_line(s, 0) 

3553 i1 = line.find("<<") 

3554 j1 = line.find(">>") 

3555 i2 = line.find("@<") 

3556 j2 = line.find("@>") 

3557 return -1 < i1 < j1 or -1 < i2 < j2 

3558 #@+node:ekr.20031218072017.2965: *6* c.canFindMatchingBracket 

3559 #@@nobeautify 

3560 

3561 def canFindMatchingBracket(self): 

3562 c = self 

3563 brackets = "()[]{}" 

3564 w = c.frame.body.wrapper 

3565 s = w.getAllText() 

3566 ins = w.getInsertPoint() 

3567 c1 = s[ins] if 0 <= ins < len(s) else '' 

3568 c2 = s[ins-1] if 0 <= ins-1 < len(s) else '' 

3569 val = (c1 and c1 in brackets) or (c2 and c2 in brackets) 

3570 return bool(val) 

3571 #@+node:ekr.20040303165342: *6* c.canHoist & canDehoist 

3572 def canDehoist(self): 

3573 """ 

3574 Return True if do-hoist should be enabled in a menu. 

3575 Should not be used in any other context. 

3576 """ 

3577 c = self 

3578 return bool(c.hoistStack) 

3579 

3580 def canHoist(self): 

3581 # This is called at idle time, so minimizing positions is crucial! 

3582 """ 

3583 Return True if hoist should be enabled in a menu. 

3584 Should not be used in any other context. 

3585 """ 

3586 return True 

3587 #@+node:ekr.20031218072017.2970: *6* c.canMoveOutlineDown 

3588 def canMoveOutlineDown(self) -> bool: 

3589 c, p = self, self.p 

3590 return p and p.visNext(c) 

3591 #@+node:ekr.20031218072017.2971: *6* c.canMoveOutlineLeft 

3592 def canMoveOutlineLeft(self) -> bool: 

3593 c, p = self, self.p 

3594 if c.hoistStack: 

3595 bunch = c.hoistStack[-1] 

3596 if p and p.hasParent(): 

3597 p.moveToParent() 

3598 return p != bunch.p and bunch.p.isAncestorOf(p) 

3599 return False 

3600 return p and p.hasParent() 

3601 #@+node:ekr.20031218072017.2972: *6* c.canMoveOutlineRight 

3602 def canMoveOutlineRight(self) -> bool: 

3603 c, p = self, self.p 

3604 if c.hoistStack: 

3605 bunch = c.hoistStack[-1] 

3606 return p and p.hasBack() and p != bunch.p 

3607 return p and p.hasBack() 

3608 #@+node:ekr.20031218072017.2973: *6* c.canMoveOutlineUp 

3609 def canMoveOutlineUp(self): 

3610 c, current = self, self.p 

3611 visBack = current and current.visBack(c) 

3612 if not visBack: 

3613 return False 

3614 if visBack.visBack(c): 

3615 return True 

3616 if c.hoistStack: 

3617 limit, limitIsVisible = c.visLimit() 

3618 if limitIsVisible: # A hoist 

3619 return current != limit 

3620 # A chapter. 

3621 return current != limit.firstChild() 

3622 return current != c.rootPosition() 

3623 #@+node:ekr.20031218072017.2974: *6* c.canPasteOutline 

3624 def canPasteOutline(self, s=None): 

3625 # c = self 

3626 if not s: 

3627 s = g.app.gui.getTextFromClipboard() 

3628 if s and g.match(s, 0, g.app.prolog_prefix_string): 

3629 return True 

3630 return False 

3631 #@+node:ekr.20031218072017.2975: *6* c.canPromote 

3632 def canPromote(self) -> bool: 

3633 p = self.p 

3634 return p and p.hasChildren() 

3635 #@+node:ekr.20031218072017.2977: *6* c.canSelect.... 

3636 def canSelectThreadBack(self): 

3637 p = self.p 

3638 return p.hasThreadBack() 

3639 

3640 def canSelectThreadNext(self): 

3641 p = self.p 

3642 return p.hasThreadNext() 

3643 

3644 def canSelectVisBack(self): 

3645 c, p = self, self.p 

3646 return p.visBack(c) 

3647 

3648 def canSelectVisNext(self): 

3649 c, p = self, self.p 

3650 return p.visNext(c) 

3651 #@+node:ekr.20031218072017.2978: *6* c.canShiftBodyLeft/Right 

3652 def canShiftBodyLeft(self) -> bool: 

3653 c = self 

3654 w = c.frame.body.wrapper 

3655 return w and w.getAllText() 

3656 

3657 canShiftBodyRight = canShiftBodyLeft 

3658 #@+node:ekr.20031218072017.2979: *6* c.canSortChildren, canSortSiblings 

3659 def canSortChildren(self) -> bool: 

3660 p = self.p 

3661 return p and p.hasChildren() 

3662 

3663 def canSortSiblings(self) -> bool: 

3664 p = self.p 

3665 return p and (p.hasNext() or p.hasBack()) 

3666 #@+node:ekr.20031218072017.2980: *6* c.canUndo & canRedo 

3667 def canUndo(self) -> bool: 

3668 c = self 

3669 return c.undoer.canUndo() 

3670 

3671 def canRedo(self) -> bool: 

3672 c = self 

3673 return c.undoer.canRedo() 

3674 #@+node:ekr.20031218072017.2981: *6* c.canUnmarkAll 

3675 def canUnmarkAll(self): 

3676 c = self 

3677 for p in c.all_unique_positions(): 

3678 if p.isMarked(): 

3679 return True 

3680 return False 

3681 #@+node:ekr.20040323172420: *6* Slow routines: no longer used 

3682 #@+node:ekr.20031218072017.2966: *7* c.canGoToNextDirtyHeadline (slow) 

3683 def canGoToNextDirtyHeadline(self): 

3684 c, current = self, self.p 

3685 for p in c.all_unique_positions(): 

3686 if p != current and p.isDirty(): 

3687 return True 

3688 return False 

3689 #@+node:ekr.20031218072017.2967: *7* c.canGoToNextMarkedHeadline (slow) 

3690 def canGoToNextMarkedHeadline(self): 

3691 c, current = self, self.p 

3692 for p in c.all_unique_positions(): 

3693 if p != current and p.isMarked(): 

3694 return True 

3695 return False 

3696 #@+node:ekr.20031218072017.2968: *7* c.canMarkChangedHeadline (slow) 

3697 def canMarkChangedHeadlines(self): 

3698 c = self 

3699 for p in c.all_unique_positions(): 

3700 if p.isDirty(): 

3701 return True 

3702 return False 

3703 #@+node:ekr.20031218072017.2969: *7* c.canMarkChangedRoots 

3704 def canMarkChangedRoots(self): 

3705 c = self 

3706 for p in c.all_unique_positions(): 

3707 if p.isDirty() and p.isAnyAtFileNode(): 

3708 return True 

3709 return False 

3710 #@+node:ekr.20031218072017.2990: *4* c.Selecting 

3711 #@+node:ekr.20031218072017.2992: *5* c.endEditing 

3712 def endEditing(self): 

3713 """End the editing of a headline.""" 

3714 c = self 

3715 p = c.p 

3716 if p: 

3717 c.frame.tree.endEditLabel() 

3718 #@+node:ville.20090525205736.12325: *5* c.getSelectedPositions 

3719 def getSelectedPositions(self): 

3720 """ Get list (PosList) of currently selected positions 

3721 

3722 So far only makes sense on qt gui (which supports multiselection) 

3723 """ 

3724 c = self 

3725 return c.frame.tree.getSelectedPositions() 

3726 #@+node:ekr.20031218072017.2991: *5* c.redrawAndEdit 

3727 def redrawAndEdit(self, p, selectAll=False, selection=None, keepMinibuffer=False): 

3728 """Redraw the screen and edit p's headline.""" 

3729 c, k = self, self.k 

3730 c.redraw(p) # This *must* be done now. 

3731 if p: 

3732 # This should request focus. 

3733 c.frame.tree.editLabel(p, selectAll=selectAll, selection=selection) 

3734 if k and not keepMinibuffer: 

3735 # Setting the input state has no effect on focus. 

3736 if selectAll: 

3737 k.setInputState('insert') 

3738 else: 

3739 k.setDefaultInputState() 

3740 # This *does* affect focus. 

3741 k.showStateAndMode() 

3742 else: 

3743 g.trace('** no p') 

3744 # Update the focus immediately. 

3745 if not keepMinibuffer: 

3746 c.outerUpdate() 

3747 #@+node:ekr.20031218072017.2997: *5* c.selectPosition (trace of unexpected de-hoists) 

3748 def selectPosition(self, p, **kwargs): 

3749 """ 

3750 Select a new position, redrawing the screen *only* if we must 

3751 change chapters. 

3752 """ 

3753 trace = False # For # 2167. 

3754 if kwargs: 

3755 print('c.selectPosition: all keyword args are ignored', g.callers()) 

3756 c = self 

3757 cc = c.chapterController 

3758 if not p: 

3759 if not g.app.batchMode: # A serious error. 

3760 g.trace('Warning: no p', g.callers()) 

3761 return 

3762 if cc and not cc.selectChapterLockout: 

3763 cc.selectChapterForPosition(p) 

3764 # Calls c.redraw only if the chapter changes. 

3765 # De-hoist as necessary to make p visible. 

3766 if c.hoistStack: 

3767 while c.hoistStack: 

3768 bunch = c.hoistStack[-1] 

3769 if c.positionExists(p, bunch.p): 

3770 break 

3771 if trace: 

3772 # #2167: Give detailed trace. 

3773 print('') 

3774 print('pop hoist stack! callers:', g.callers()) 

3775 g.printObj(c.hoistStack, tag='c.hoistStack before pop') 

3776 print('Recent keystrokes') 

3777 for i, data in enumerate(reversed(g.app.lossage)): 

3778 print(f"{i:>2} {data!r}") 

3779 print('Recently-executed commands...') 

3780 for i, command in enumerate(reversed(c.recent_commands_list)): 

3781 print(f"{i:>2} {command}") 

3782 c.hoistStack.pop() 

3783 c.frame.tree.select(p) 

3784 c.setCurrentPosition(p) 

3785 # Do *not* test whether the position exists! 

3786 # We may be in the midst of an undo. 

3787 

3788 # Compatibility, but confusing. 

3789 

3790 selectVnode = selectPosition 

3791 #@+node:ekr.20080503055349.1: *5* c.setPositionAfterSort 

3792 def setPositionAfterSort(self, sortChildren): 

3793 """ 

3794 Return the position to be selected after a sort. 

3795 """ 

3796 c = self 

3797 p = c.p 

3798 p_v = p.v 

3799 parent = p.parent() 

3800 parent_v = p._parentVnode() 

3801 if sortChildren: 

3802 return parent or c.rootPosition() 

3803 if parent: 

3804 p = parent.firstChild() 

3805 else: 

3806 p = leoNodes.Position(parent_v.children[0]) 

3807 while p and p.v != p_v: 

3808 p.moveToNext() 

3809 p = p or parent 

3810 return p 

3811 #@+node:ekr.20070226113916: *5* c.treeSelectHelper 

3812 def treeSelectHelper(self, p): 

3813 c = self 

3814 if not p: 

3815 p = c.p 

3816 if p: 

3817 # Do not call expandAllAncestors here. 

3818 c.selectPosition(p) 

3819 c.redraw_after_select(p) 

3820 c.treeFocusHelper() 

3821 # This is essential. 

3822 #@+node:ekr.20130823083943.12559: *3* c.recursiveImport 

3823 def recursiveImport(self, dir_, kind, 

3824 add_context=None, # Override setting only if True/False 

3825 add_file_context=None, # Override setting only if True/False 

3826 add_path=True, 

3827 recursive=True, 

3828 safe_at_file=True, 

3829 theTypes=None, 

3830 # force_at_others=False, # tag:no-longer-used 

3831 ignore_pattern=None, 

3832 verbose=True, # legacy value. 

3833 ): 

3834 #@+<< docstring >> 

3835 #@+node:ekr.20130823083943.12614: *4* << docstring >> 

3836 """ 

3837 Recursively import all python files in a directory and clean the results. 

3838 

3839 Parameters:: 

3840 dir_ The root directory or file to import. 

3841 kind One of ('@clean','@edit','@file','@nosent'). 

3842 add_path=True True: add a full @path directive to @<file> nodes. 

3843 recursive=True True: recurse into subdirectories. 

3844 safe_at_file=True True: produce @@file nodes instead of @file nodes. 

3845 theTypes=None A list of file extensions to import. 

3846 None is equivalent to ['.py'] 

3847 

3848 This method cleans imported files as follows: 

3849 

3850 - Replace backslashes with forward slashes in headlines. 

3851 - Remove empty nodes. 

3852 - Add @path directives that reduce the needed path specifiers in descendant nodes. 

3853 - Add @file to nodes or replace @file with @@file. 

3854 """ 

3855 #@-<< docstring >> 

3856 c = self 

3857 if g.os_path_exists(dir_): 

3858 # Import all files in dir_ after c.p. 

3859 try: 

3860 from leo.core import leoImport 

3861 cc = leoImport.RecursiveImportController(c, kind, 

3862 add_context=add_context, 

3863 add_file_context=add_file_context, 

3864 add_path=add_path, 

3865 ignore_pattern=ignore_pattern, 

3866 recursive=recursive, 

3867 safe_at_file=safe_at_file, 

3868 theTypes=['.py'] if not theTypes else theTypes, 

3869 verbose=verbose, 

3870 ) 

3871 cc.run(dir_) 

3872 finally: 

3873 c.redraw() 

3874 else: 

3875 g.es_print(f"Does not exist: {dir_}") 

3876 #@+node:ekr.20171124084149.1: *3* c.Scripting utils 

3877 #@+node:ekr.20160201072634.1: *4* c.cloneFindByPredicate 

3878 def cloneFindByPredicate(self, 

3879 generator, # The generator used to traverse the tree. 

3880 predicate, # A function of one argument p, returning True 

3881 # if p should be included in the results. 

3882 failMsg=None, # Failure message. Default is no message. 

3883 flatten=False, # True: Put all matches at the top level. 

3884 iconPath=None, # Full path to icon to attach to all matches. 

3885 undoType=None, # The undo name, shown in the Edit:Undo menu. 

3886 # The default is 'clone-find-predicate' 

3887 ): 

3888 """ 

3889 Traverse the tree given using the generator, cloning all positions for 

3890 which predicate(p) is True. Undoably move all clones to a new node, created 

3891 as the last top-level node. Returns the newly-created node. Arguments: 

3892 

3893 generator, The generator used to traverse the tree. 

3894 predicate, A function of one argument p returning true if p should be included. 

3895 failMsg=None, Message given if nothing found. Default is no message. 

3896 flatten=False, True: Move all node to be parents of the root node. 

3897 iconPath=None, Full path to icon to attach to all matches. 

3898 undo_type=None, The undo/redo name shown in the Edit:Undo menu. 

3899 The default is 'clone-find-predicate' 

3900 """ 

3901 c = self 

3902 u, undoType = c.undoer, undoType or 'clone-find-predicate' 

3903 clones, root, seen = [], None, set() 

3904 for p in generator(): 

3905 if predicate(p) and p.v not in seen: 

3906 c.setCloneFindByPredicateIcon(iconPath, p) 

3907 if flatten: 

3908 seen.add(p.v) 

3909 else: 

3910 for p2 in p.self_and_subtree(copy=False): 

3911 seen.add(p2.v) 

3912 clones.append(p.copy()) 

3913 if clones: 

3914 undoData = u.beforeInsertNode(c.p) 

3915 root = c.createCloneFindPredicateRoot(flatten, undoType) 

3916 for p in clones: 

3917 # Create the clone directly as a child of found. 

3918 p2 = p.copy() 

3919 n = root.numberOfChildren() 

3920 p2._linkCopiedAsNthChild(root, n) 

3921 u.afterInsertNode(root, undoType, undoData) 

3922 c.selectPosition(root) 

3923 c.setChanged() 

3924 c.contractAllHeadlines() 

3925 root.expand() 

3926 elif failMsg: 

3927 g.es(failMsg, color='red') 

3928 return root 

3929 #@+node:ekr.20160304054950.1: *5* c.setCloneFindByPredicateIcon 

3930 def setCloneFindByPredicateIcon(self, iconPath, p): 

3931 """Attach an icon to p.v.u.""" 

3932 if iconPath and g.os_path_exists(iconPath) and not g.os_path_isdir(iconPath): 

3933 aList = p.v.u.get('icons', []) 

3934 for d in aList: 

3935 if d.get('file') == iconPath: 

3936 break 

3937 else: 

3938 aList.append({ 

3939 'type': 'file', 

3940 'file': iconPath, 

3941 'on': 'VNode', 

3942 # 'relPath': iconPath, 

3943 'where': 'beforeHeadline', 

3944 'xoffset': 2, 'xpad': 1, 

3945 'yoffset': 0, 

3946 

3947 }) 

3948 p.v.u['icons'] = aList 

3949 elif iconPath: 

3950 g.trace('bad icon path', iconPath) 

3951 #@+node:ekr.20160201075438.1: *5* c.createCloneFindPredicateRoot 

3952 def createCloneFindPredicateRoot(self, flatten, undoType): 

3953 """Create a root node for clone-find-predicate.""" 

3954 c = self 

3955 root = c.lastTopLevel().insertAfter() 

3956 root.h = undoType + (' (flattened)' if flatten else '') 

3957 return root 

3958 #@+node:peckj.20131023115434.10114: *4* c.createNodeHierarchy 

3959 def createNodeHierarchy(self, heads, parent=None, forcecreate=False): 

3960 """ Create the proper hierarchy of nodes with headlines defined in 

3961 'heads' under 'parent' 

3962 

3963 params: 

3964 parent - parent node to start from. Set to None for top-level nodes 

3965 heads - list of headlines in order to create, i.e. ['foo','bar','baz'] 

3966 will create: 

3967 parent 

3968 -foo 

3969 --bar 

3970 ---baz 

3971 forcecreate - If False (default), will not create nodes unless they don't exist 

3972 If True, will create nodes regardless of existing nodes 

3973 returns the final position ('baz' in the above example) 

3974 """ 

3975 u = self.undoer 

3976 undoType = 'Create Node Hierarchy' 

3977 undoType2 = 'Insert Node In Hierarchy' 

3978 u_node = parent or self.rootPosition() 

3979 undoData = u.beforeChangeGroup(u_node, undoType) 

3980 changed_node = False 

3981 for idx, head in enumerate(heads): 

3982 if parent is None and idx == 0: # if parent = None, create top level node for first head 

3983 if not forcecreate: 

3984 for pos in self.all_positions(): 

3985 if pos.h == head: 

3986 parent = pos 

3987 break 

3988 if parent is None or forcecreate: 

3989 u_d = u.beforeInsertNode(u_node) 

3990 n = self.rootPosition().insertAfter() 

3991 n.h = head 

3992 u.afterInsertNode(n, undoType2, u_d) 

3993 parent = n 

3994 else: # else, simply create child nodes each round 

3995 if not forcecreate: 

3996 for ch in parent.children(): 

3997 if ch.h == head: 

3998 parent = ch 

3999 changed_node = True 

4000 break 

4001 if parent.h != head or not changed_node or forcecreate: 

4002 u_d = u.beforeInsertNode(parent) 

4003 n = parent.insertAsLastChild() 

4004 n.h = head 

4005 u.afterInsertNode(n, undoType2, u_d) 

4006 parent = n 

4007 changed_node = False 

4008 u.afterChangeGroup(parent, undoType, undoData) 

4009 return parent # actually the last created/found position 

4010 #@+node:ekr.20100802121531.5804: *4* c.deletePositionsInList 

4011 def deletePositionsInList(self, aList): 

4012 """ 

4013 Delete all vnodes corresponding to the positions in aList. 

4014  

4015 Set c.p if the old position no longer exists. 

4016 

4017 See "Theory of operation of c.deletePositionsInList" in LeoDocs.leo. 

4018 """ 

4019 # New implementation by Vitalije 2020-03-17 17:29 

4020 c = self 

4021 # Ensure all positions are valid. 

4022 aList = [p for p in aList if c.positionExists(p)] 

4023 if not aList: 

4024 return [] 

4025 

4026 def p2link(p): 

4027 parent_v = p.stack[-1][0] if p.stack else c.hiddenRootNode 

4028 return p._childIndex, parent_v 

4029 

4030 links_to_be_cut = sorted(set(map(p2link, aList)), key=lambda x: -x[0]) 

4031 undodata = [] 

4032 for i, v in links_to_be_cut: 

4033 ch = v.children.pop(i) 

4034 ch.parents.remove(v) 

4035 undodata.append((v.gnx, i, ch.gnx)) 

4036 if not c.positionExists(c.p): 

4037 c.selectPosition(c.rootPosition()) 

4038 return undodata 

4039 

4040 #@+node:vitalije.20200318161844.1: *4* c.undoableDeletePositions 

4041 def undoableDeletePositions(self, aList): 

4042 """ 

4043 Deletes all vnodes corresponding to the positions in aList, 

4044 and make changes undoable. 

4045 """ 

4046 c = self 

4047 u = c.undoer 

4048 data = c.deletePositionsInList(aList) 

4049 gnx2v = c.fileCommands.gnxDict 

4050 def undo(): 

4051 for pgnx, i, chgnx in reversed(u.getBead(u.bead).data): 

4052 v = gnx2v[pgnx] 

4053 ch = gnx2v[chgnx] 

4054 v.children.insert(i, ch) 

4055 ch.parents.append(v) 

4056 if not c.positionExists(c.p): 

4057 c.setCurrentPosition(c.rootPosition()) 

4058 def redo(): 

4059 for pgnx, i, chgnx in u.getBead(u.bead + 1).data: 

4060 v = gnx2v[pgnx] 

4061 ch = v.children.pop(i) 

4062 ch.parents.remove(v) 

4063 if not c.positionExists(c.p): 

4064 c.setCurrentPosition(c.rootPosition()) 

4065 u.pushBead(g.Bunch( 

4066 data=data, 

4067 undoType='delete nodes', 

4068 undoHelper=undo, 

4069 redoHelper=redo, 

4070 )) 

4071 #@+node:ekr.20091211111443.6265: *4* c.doBatchOperations & helpers 

4072 def doBatchOperations(self, aList=None): 

4073 # Validate aList and create the parents dict 

4074 if aList is None: 

4075 aList = [] 

4076 ok, d = self.checkBatchOperationsList(aList) 

4077 if not ok: 

4078 g.error('do-batch-operations: invalid list argument') 

4079 return 

4080 for v in list(d.keys()): 

4081 aList2 = d.get(v, []) 

4082 if aList2: 

4083 aList.sort() 

4084 #@+node:ekr.20091211111443.6266: *5* c.checkBatchOperationsList 

4085 def checkBatchOperationsList(self, aList): 

4086 ok = True 

4087 d: Dict["leoNodes.VNode", List[Any]] = {} 

4088 for z in aList: 

4089 try: 

4090 op, p, n = z 

4091 ok = (op in ('insert', 'delete') and 

4092 isinstance(p, leoNodes.position) and isinstance(n, int)) 

4093 if ok: 

4094 aList2 = d.get(p.v, []) 

4095 data = n, op 

4096 aList2.append(data) 

4097 d[p.v] = aList2 

4098 except ValueError: 

4099 ok = False 

4100 if not ok: 

4101 break 

4102 return ok, d 

4103 #@+node:ekr.20091002083910.6106: *4* c.find_b & find_h (PosList) 

4104 #@+<< PosList doc >> 

4105 #@+node:bob.20101215134608.5898: *5* << PosList doc >> 

4106 #@@language rest 

4107 #@+at 

4108 # List of positions 

4109 # 

4110 # Functions find_h() and find_b() both return an instance of PosList. 

4111 # 

4112 # Methods filter_h() and filter_b() refine a PosList. 

4113 # 

4114 # Method children() generates a new PosList by descending one level from 

4115 # all the nodes in a PosList. 

4116 # 

4117 # A chain of PosList method calls must begin with find_h() or find_b(). 

4118 # The rest of the chain can be any combination of filter_h(), 

4119 # filter_b(), and children(). For example: 

4120 # 

4121 # pl = c.find_h('@file.*py').children().filter_h('class.*').filter_b('import (.*)') 

4122 # 

4123 # For each position, pos, in the PosList returned, find_h() and 

4124 # filter_h() set attribute pos.mo to the match object (see Python 

4125 # Regular Expression documentation) for the pattern match. 

4126 # 

4127 # Caution: The pattern given to find_h() or filter_h() must match zero 

4128 # or more characters at the beginning of the headline. 

4129 # 

4130 # For each position, pos, the postlist returned, find_b() and filter_b() 

4131 # set attribute pos.matchiter to an iterator that will return a match 

4132 # object for each of the non-overlapping matches of the pattern in the 

4133 # body of the node. 

4134 #@-<< PosList doc >> 

4135 #@+node:ville.20090311190405.70: *5* c.find_h 

4136 def find_h(self, regex, flags=re.IGNORECASE): 

4137 """ Return list (a PosList) of all nodes where zero or more characters at 

4138 the beginning of the headline match regex 

4139 """ 

4140 c = self 

4141 pat = re.compile(regex, flags) 

4142 res = leoNodes.PosList() 

4143 for p in c.all_positions(): 

4144 m = re.match(pat, p.h) 

4145 if m: 

4146 pc = p.copy() 

4147 pc.mo = m 

4148 res.append(pc) 

4149 return res 

4150 #@+node:ville.20090311200059.1: *5* c.find_b 

4151 def find_b(self, regex, flags=re.IGNORECASE | re.MULTILINE): 

4152 """ Return list (a PosList) of all nodes whose body matches regex 

4153 one or more times. 

4154 

4155 """ 

4156 c = self 

4157 pat = re.compile(regex, flags) 

4158 res = leoNodes.PosList() 

4159 for p in c.all_positions(): 

4160 m = re.finditer(pat, p.b) 

4161 t1, t2 = itertools.tee(m, 2) 

4162 try: 

4163 t1.__next__() 

4164 except StopIteration: 

4165 continue 

4166 pc = p.copy() 

4167 pc.matchiter = t2 

4168 res.append(pc) 

4169 return res 

4170 #@+node:ekr.20171124155725.1: *3* c.Settings 

4171 #@+node:ekr.20171114114908.1: *4* c.registerReloadSettings 

4172 def registerReloadSettings(self, obj): 

4173 """Enter object into c.configurables.""" 

4174 c = self 

4175 if obj not in c.configurables: 

4176 c.configurables.append(obj) 

4177 #@+node:ekr.20170221040621.1: *4* c.reloadConfigurableSettings 

4178 def reloadConfigurableSettings(self): 

4179 """ 

4180 Call all reloadSettings method in c.subcommanders, c.configurables and 

4181 other known classes. 

4182 """ 

4183 c = self 

4184 table = [ 

4185 g.app.gui, 

4186 g.app.pluginsController, 

4187 c.k.autoCompleter, 

4188 c.frame, c.frame.body, c.frame.log, c.frame.tree, 

4189 c.frame.body.colorizer, 

4190 getattr(c.frame.body.colorizer, 'highlighter', None), 

4191 ] 

4192 for obj in table: 

4193 if obj: 

4194 c.registerReloadSettings(obj) 

4195 # Useful now that instances add themselves to c.configurables. 

4196 c.configurables = list(set(c.configurables)) 

4197 c.configurables.sort(key=lambda obj: obj.__class__.__name__.lower()) 

4198 for obj in c.configurables: 

4199 func = getattr(obj, 'reloadSettings', None) 

4200 if func: 

4201 # pylint: disable=not-callable 

4202 try: 

4203 func() 

4204 except Exception: 

4205 g.es_exception() 

4206 c.configurables.remove(obj) 

4207 #@-others 

4208#@-others 

4209#@@language python 

4210#@@tabwidth -4 

4211#@@pagewidth 70 

4212#@-leo