Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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

2#@+leo-ver=5-thin 

3#@+node:ekr.20031218072017.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 

62 # Set only by the ipython plugin. 

63 # The order of these calls does not matter. 

64 c.initCommandIvars() 

65 c.initDebugIvars() 

66 c.initDocumentIvars() 

67 c.initEventIvars() 

68 c.initFileIvars(fileName, relativeFileName) 

69 c.initOptionsIvars() 

70 c.initObjectIvars() 

71 c.initSettings(previousSettings) 

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

73 # Initialize all subsidiary objects, including subcommanders. 

74 c.initObjects(self.gui) 

75 assert c.frame 

76 assert c.frame.c 

77 # Complete the init! 

78 t2 = time.process_time() 

79 c.finishCreate() # Slightly slow. 

80 t3 = time.process_time() 

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

82 print('c.__init__') 

83 print( 

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

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

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

87 ) 

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

89 def computeWindowTitle(self, fileName): 

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

91 if fileName: 

92 title = g.computeWindowTitle(fileName) 

93 else: 

94 s = "untitled" 

95 n = g.app.numberOfUntitledWindows 

96 if n > 0: 

97 s += str(n) 

98 title = g.computeWindowTitle(s) 

99 g.app.numberOfUntitledWindows = n + 1 

100 return title 

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

102 def initCommandIvars(self): 

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

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

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

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

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

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

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

110 # For hoist/dehoist commands. 

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

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

113 # For outline navigation. 

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

115 self.navTime: Optional[float] = None 

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

117 self.sqlite_connection = None 

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

119 def initDebugIvars(self): 

120 """Init Commander debugging ivars.""" 

121 self.command_count = 0 

122 self.scanAtPathDirectivesCount = 0 

123 self.trace_focus_count = 0 

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

125 def initDocumentIvars(self): 

126 """Init per-document ivars.""" 

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

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

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

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

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

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

133 def initEventIvars(self): 

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

135 self.configInited = False 

136 self.doubleClickFlag = False 

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

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

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

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

141 # 

142 # Flags for c.outerUpdate... 

143 self.enableRedrawFlag = True 

144 self.requestCloseWindow = False 

145 self.requestedFocusWidget = None 

146 self.requestLaterRedraw = False 

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

148 def initFileIvars(self, fileName, relativeFileName): 

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

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

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

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

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

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

155 self.mRelativeFileName = relativeFileName or '' # 

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

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

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

159 

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

161 def initOptionsIvars(self): 

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

163 self.fixed = False 

164 self.fixedWindowPosition = [] 

165 self.forceExecuteEntireBody = False 

166 self.focus_border_color = 'white' 

167 self.focus_border_width = 1 # pixels 

168 self.outlineHasInitialFocus = False 

169 self.page_width = 132 

170 self.sparse_find = True 

171 self.sparse_move = True 

172 self.sparse_spell = True 

173 self.stayInTreeAfterSelect = False 

174 self.tab_width = -4 

175 self.tangle_batch_flag = False 

176 self.target_language = "python" 

177 self.untangle_batch_flag = False 

178 self.vim_mode = False 

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

180 def initObjectIvars(self): 

181 # These ivars are set later by leoEditCommands.createEditCommanders 

182 self.abbrevCommands = None 

183 self.editCommands = None 

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

185 self.bufferCommands = None 

186 self.chapterCommands = None 

187 self.controlCommands = None 

188 self.convertCommands = None 

189 self.debugCommands = None 

190 self.editFileCommands = None 

191 self.evalController = None 

192 self.gotoCommands = None 

193 self.helpCommands = None 

194 self.keyHandler = self.k = None 

195 self.keyHandlerCommands = None 

196 self.killBufferCommands = None 

197 self.leoCommands = None 

198 self.macroCommands = None 

199 self.miniBufferWidget = None 

200 self.printingController = None 

201 self.queryReplaceCommands = None 

202 self.rectangleCommands = None 

203 self.searchCommands = None 

204 self.spellCommands = None 

205 self.leoTestManager = None 

206 self.vimCommands = None 

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

208 #@@nobeautify 

209 

210 def initObjects(self, gui): 

211 

212 c = self 

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

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

215 # Create the gui frame. 

216 title = c.computeWindowTitle(c.mFileName) 

217 if not g.app.initing: 

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

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

220 assert self.frame 

221 assert self.frame.c == c 

222 from leo.core import leoHistory 

223 self.nodeHistory = leoHistory.NodeHistory(c) 

224 self.initConfigSettings() 

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

226 # Break circular import dependencies by doing imports here. 

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

228 from leo.core import leoAtFile 

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

230 assert leoBeautify # for pyflakes. 

231 from leo.core import leoChapters 

232 # from leo.core import leoTest2 # So decorators are executed. 

233 # assert leoTest2 # For pyflakes. 

234 # User commands... 

235 from leo.commands import abbrevCommands 

236 from leo.commands import bufferCommands 

237 from leo.commands import checkerCommands 

238 assert checkerCommands 

239 # To suppress a pyflakes warning. 

240 # The import *is* required to define commands. 

241 from leo.commands import controlCommands 

242 from leo.commands import convertCommands 

243 from leo.commands import debugCommands 

244 from leo.commands import editCommands 

245 from leo.commands import editFileCommands 

246 from leo.commands import gotoCommands 

247 from leo.commands import helpCommands 

248 from leo.commands import keyCommands 

249 from leo.commands import killBufferCommands 

250 from leo.commands import rectangleCommands 

251 from leo.commands import spellCommands 

252 # Import files to execute @g.commander_command decorators 

253 from leo.core import leoCompare 

254 assert leoCompare 

255 from leo.core import leoDebugger 

256 assert leoDebugger 

257 from leo.commands import commanderEditCommands 

258 assert commanderEditCommands 

259 from leo.commands import commanderFileCommands 

260 assert commanderFileCommands 

261 from leo.commands import commanderHelpCommands 

262 assert commanderHelpCommands 

263 from leo.commands import commanderOutlineCommands 

264 assert commanderOutlineCommands 

265 # Other subcommanders. 

266 from leo.core import leoFind # Leo 4.11.1 

267 from leo.core import leoKeys 

268 from leo.core import leoFileCommands 

269 from leo.core import leoImport 

270 from leo.core import leoMarkup 

271 from leo.core import leoPersistence 

272 from leo.core import leoPrinting 

273 from leo.core import leoRst 

274 from leo.core import leoShadow 

275 from leo.core import leoUndo 

276 from leo.core import leoVim 

277 # Import commands.testCommands to define commands. 

278 import leo.commands.testCommands as testCommands 

279 assert testCommands # For pylint. 

280 # Define the subcommanders. 

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

282 self.chapterController = leoChapters.ChapterController(c) 

283 self.shadowController = leoShadow.ShadowController(c) 

284 self.fileCommands = leoFileCommands.FileCommands(c) 

285 self.findCommands = leoFind.LeoFind(c) 

286 self.atFileCommands = leoAtFile.AtFile(c) 

287 self.importCommands = leoImport.LeoImportCommands(c) 

288 self.markupCommands = leoMarkup.MarkupCommands(c) 

289 self.persistenceController = leoPersistence.PersistenceDataController(c) 

290 self.printingController = leoPrinting.PrintingController(c) 

291 self.rstCommands = leoRst.RstCommands(c) 

292 self.vimCommands = leoVim.VimCommands(c) 

293 # User commands 

294 self.abbrevCommands = abbrevCommands.AbbrevCommandsClass(c) 

295 self.bufferCommands = bufferCommands.BufferCommandsClass(c) 

296 self.controlCommands = controlCommands.ControlCommandsClass(c) 

297 self.convertCommands = convertCommands.ConvertCommandsClass(c) 

298 self.debugCommands = debugCommands.DebugCommandsClass(c) 

299 self.editCommands = editCommands.EditCommandsClass(c) 

300 self.editFileCommands = editFileCommands.EditFileCommandsClass(c) 

301 self.gotoCommands = gotoCommands.GoToCommands(c) 

302 self.helpCommands = helpCommands.HelpCommandsClass(c) 

303 self.keyHandlerCommands = keyCommands.KeyHandlerCommandsClass(c) 

304 self.killBufferCommands = killBufferCommands.KillBufferCommandsClass(c) 

305 self.rectangleCommands = rectangleCommands.RectangleCommandsClass(c) 

306 self.spellCommands = spellCommands.SpellCommandsClass(c) 

307 self.undoer = leoUndo.Undoer(c) 

308 # Create the list of subcommanders. 

309 self.subCommanders = [ 

310 self.abbrevCommands, 

311 self.atFileCommands, 

312 self.bufferCommands, 

313 self.chapterController, 

314 self.controlCommands, 

315 self.convertCommands, 

316 self.debugCommands, 

317 self.editCommands, 

318 self.editFileCommands, 

319 self.fileCommands, 

320 self.findCommands, 

321 self.gotoCommands, 

322 self.helpCommands, 

323 self.importCommands, 

324 self.keyHandler, 

325 self.keyHandlerCommands, 

326 self.killBufferCommands, 

327 self.persistenceController, 

328 self.printingController, 

329 self.rectangleCommands, 

330 self.rstCommands, 

331 self.shadowController, 

332 self.spellCommands, 

333 self.vimCommands, 

334 self.undoer, 

335 ] 

336 # Other objects 

337 c.configurables = c.subCommanders[:] 

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

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

340 from leo.plugins import free_layout 

341 self.free_layout = free_layout.FreeLayoutController(c) 

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

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

344 self.subCommanders.append(self.styleSheetManager) 

345 else: 

346 self.styleSheetManager = None 

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

348 def initSettings(self, previousSettings): 

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

350 c = self 

351 from leo.core import leoConfig 

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

353 g.app.config.setIvarsFromSettings(c) 

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

355 def __repr__(self): 

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

357 

358 __str__ = __repr__ 

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

360 def finishCreate(self): 

361 """ 

362 Finish creating the commander and all sub-objects. 

363 This is the last step in the startup process. 

364 """ 

365 c, k = self, self.k 

366 assert c.gui 

367 assert k 

368 t1 = time.process_time() 

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

370 t2 = time.process_time() 

371 c.miniBufferWidget = c.frame.miniBufferWidget 

372 # Will be None for nullGui. 

373 # Only c.abbrevCommands needs a finishCreate method. 

374 c.abbrevCommands.finishCreate() 

375 # Finish other objects... 

376 c.createCommandNames() 

377 k.finishCreate() 

378 c.findCommands.finishCreate() 

379 if not c.gui.isNullGui: 

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

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

382 c.frame.menu.finishCreate() 

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

384 c.frame.log.finishCreate() 

385 c.undoer.clearUndoState() 

386 if c.vimCommands and c.vim_mode: 

387 c.vimCommands.finishCreate() 

388 # Menus must exist at this point. 

389 # Do not call chapterController.finishCreate here: 

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

391 g.check_cmd_instance_dict(c, g) 

392 c.bodyWantsFocus() 

393 t3 = time.process_time() 

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

395 print('c.finishCreate') 

396 print( 

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

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

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

400 ) 

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

402 def createCommandNames(self): 

403 """ 

404 Create all entries in c.commandsDict. 

405 Do *not* clear c.commandsDict here. 

406 """ 

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

408 self.k.registerCommand(commandName, func) 

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

410 def printCommandsDict(self): 

411 c = self 

412 print('Commands...') 

413 for key in sorted(c.commandsDict): 

414 command = c.commandsDict.get(key) 

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

416 print('') 

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

418 # This is a bad idea. 

419 

420 def hash(self): 

421 c = self 

422 if c.mFileName: 

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

424 return 0 

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

426 idle_focus_count = 0 

427 

428 def idle_focus_helper(self, tag, keys): 

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

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

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

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

433 c = self 

434 assert tag == 'idle' 

435 if g.unitTesting: 

436 return 

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

438 if trace: 

439 g.trace('no c') 

440 return 

441 self.idle_focus_count += 1 

442 if c.in_qt_dialog: 

443 if trace and trace_in_dialog: 

444 g.trace('in_qt_dialog') 

445 return 

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

447 if g.app.gui.active: 

448 # Always call trace_idle_focus. 

449 self.trace_idle_focus(w) 

450 if w and self.is_unusual_focus(w): 

451 if trace: 

452 w_class = w and w.__class__.__name__ 

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

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

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

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

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

458 c.treeWantsFocusNow() 

459 # elif not w and active: 

460 # c.bodyWantsFocusNow() 

461 elif trace and trace_inactive_focus: 

462 w_class = w and w.__class__.__name__ 

463 count = c.idle_focus_count 

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

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

466 def is_unusual_focus(self, w): 

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

468 # 

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

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

471 # 

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

473 from leo.plugins import qt_frame 

474 return isinstance(w, qt_frame.QtTabBarWrapper) 

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

476 last_unusual_focus = None 

477 # last_no_focus = False 

478 

479 def trace_idle_focus(self, w): 

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

481 from leo.core.leoQt import QtWidgets 

482 from leo.plugins import qt_frame 

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

484 trace_known = False 

485 c = self 

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

487 count = c.idle_focus_count 

488 if w: 

489 w_class = w and w.__class__.__name__ 

490 c.last_no_focus = False 

491 if self.is_unusual_focus(w): 

492 if trace: 

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

494 else: 

495 c.last_unusual_focus = None 

496 if isinstance(w, table): 

497 if trace and trace_known: 

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

499 elif trace: 

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

501 else: 

502 if trace: 

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

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

505 def initAfterLoad(self): 

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

507 pass 

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

509 def initConfigSettings(self): 

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

511 c = self 

512 getBool = c.config.getBool 

513 getColor = c.config.getColor 

514 getData = c.config.getData 

515 getInt = c.config.getInt 

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

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

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

519 c.contractVisitedNodes = getBool('contractVisitedNodes') 

520 c.fixedWindowPositionData = getData('fixedWindowPosition') 

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

522 c.focus_border_command_state_color = getColor( 

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

524 c.focus_border_overwrite_state_color = getColor( 

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

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

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

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

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

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

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

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

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

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

535 c.stayInTreeAfterSelect = getBool('stayInTreeAfterSelect') 

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

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

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

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

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

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

542 def setWindowPosition(self): 

543 c = self 

544 if c.fixedWindowPositionData: 

545 try: 

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

547 w, h, l, t = aList 

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

549 except Exception: 

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

551 repr(self.fixedWindowPosition)) 

552 else: 

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

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

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

556 def execute_general_script_command(self, event=None): 

557 """ 

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

559 

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

561 

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

563 

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

565 links for error messages. 

566 

567 Set the cwd before calling the command. 

568 """ 

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

570 

571 def get_setting_for_language(setting: str): 

572 """ 

573 Return the setting from the given @data setting. 

574 The first colon ends each key. 

575 """ 

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

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

578 if key.strip() == language: 

579 return val.strip() 

580 return None 

581 

582 # Get the language and extension. 

583 d = c.scanAllDirectives(p) 

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

585 if not language: 

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

587 return 

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

589 if not ext: 

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

591 return 

592 # Get the command. 

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

594 if not command: 

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

596 return 

597 # Get the optional pattern. 

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

599 # Set the directory, if possible. 

600 if p.isAnyAtFileNode(): 

601 path = g.fullPath(c, p) 

602 directory = os.path.dirname(path) 

603 else: 

604 directory = None 

605 c.general_script_helper(command, ext, language, 

606 directory=directory, regex=regex, root=p) 

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

608 @cmd('execute-pytest') 

609 def execute_pytest(self, event=None): 

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

611 c = self 

612 

613 def it(p): 

614 for p1 in p.self_and_parents(): 

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

616 yield p1 

617 return 

618 for p1 in p.subtree(): 

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

620 yield p1 

621 

622 try: 

623 for p in it(c.p): 

624 self.execute_single_pytest(p) 

625 except ImportError: 

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

627 return 

628 

629 def execute_single_pytest(self, p): 

630 c = self 

631 from _pytest.config import get_config 

632 from _pytest.assertion.rewrite import rewrite_asserts 

633 import ast 

634 cfg = get_config() 

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

636 '\n' 

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

638 'failed = 0\n' 

639 'for x in ls:\n' 

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

641 ' try:\n' 

642 ' ls[x]()\n' 

643 ' except AssertionError as e:\n' 

644 ' failed += 1\n' 

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

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

647 'if failed == 0:\n' 

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

649 'else:\n' 

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

651 

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

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

654 out.write(script) 

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

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

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

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

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

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

661 try: 

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

663 except KeyboardInterrupt: 

664 g.es('interrupted') 

665 except Exception: 

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

667 finally: 

668 del sys.path[:2] 

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

670 @cmd('execute-script') 

671 def executeScript(self, event=None, 

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

673 define_g=True, define_name='__main__', 

674 silent=False, namespace=None, raiseFlag=False, 

675 runPyflakes=True, 

676 ): 

677 """ 

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

679 Keyword args: 

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

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

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

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

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

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

686 silent=False No longer used. 

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

688 raiseFlag=False True: reraise any exceptions. 

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

690 """ 

691 c, script1 = self, script 

692 if runPyflakes: 

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

694 else: 

695 run_pyflakes = False 

696 if not script: 

697 if c.forceExecuteEntireBody: 

698 useSelectedText = False 

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

700 script_p = p or c.p 

701 # Only for error reporting below. 

702 # #532: check all scripts with pyflakes. 

703 if run_pyflakes and not g.unitTesting: 

704 from leo.commands import checkerCommands as cc 

705 # at = c.atFileCommands 

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

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

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

709 self.redirectScriptOutput() 

710 try: 

711 oldLog = g.app.log 

712 log = c.frame.log 

713 g.app.log = log 

714 if script.strip(): 

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

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

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

718 try: 

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

720 namespace = namespace or {} 

721 namespace.update(script_gnx=script_p.gnx) 

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

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

724 except KeyboardInterrupt: 

725 g.es('interrupted') 

726 except Exception: 

727 if raiseFlag: 

728 raise 

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

730 finally: 

731 del sys.path[0] 

732 del sys.path[0] 

733 else: 

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

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

736 finally: 

737 g.app.log = oldLog 

738 self.unredirectScriptOutput() 

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

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

741 c = self 

742 if c.p: 

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

744 c.setCurrentDirectoryFromContext(p) 

745 else: 

746 p = None 

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

748 if define_name: 

749 d['__name__'] = define_name 

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

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

752 if namespace: 

753 d.update(namespace) 

754 # 

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

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

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

758 try: 

759 c.inCommand = False 

760 g.inScript = g.app.inScript = True 

761 # g.inScript is a synonym for g.app.inScript. 

762 if c.write_script_file: 

763 scriptFile = self.writeScriptFile(script) 

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

765 else: 

766 exec(script, d) 

767 finally: 

768 g.inScript = g.app.inScript = False 

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

770 def redirectScriptOutput(self): 

771 c = self 

772 if c.config.redirect_execute_script_output_to_log_pane: 

773 g.redirectStdout() # Redirect stdout 

774 g.redirectStderr() # Redirect stderr 

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

776 def setCurrentDirectoryFromContext(self, p): 

777 c = self 

778 aList = g.get_directives_dict_list(p) 

779 path = c.scanAtPathDirectives(aList) 

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

781 if path and path != curDir: 

782 try: 

783 os.chdir(path) 

784 except Exception: 

785 pass 

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

787 def unredirectScriptOutput(self): 

788 c = self 

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

790 g.restoreStderr() 

791 g.restoreStdout() 

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

793 @cmd('recolor') 

794 def recolorCommand(self, event=None): 

795 """Force a full recolor.""" 

796 c = self 

797 wrapper = c.frame.body.wrapper 

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

799 i, j = wrapper.getSelectionRange() 

800 ins = wrapper.getInsertPoint() 

801 wrapper.setAllText(c.p.b) 

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

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

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

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

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

807 def all_nodes(self): 

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

809 c = self 

810 for p in c.all_positions(): 

811 yield p.v 

812 

813 def all_unique_nodes(self): 

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

815 c = self 

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

817 yield p.v 

818 

819 # Compatibility with old code... 

820 

821 all_vnodes_iter = all_nodes 

822 all_unique_vnodes_iter = all_unique_nodes 

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

824 def all_positions(self, copy=True): 

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

826 c = self 

827 p = c.rootPosition() 

828 while p: 

829 yield p.copy() if copy else p 

830 p.moveToThreadNext() 

831 

832 # Compatibility with old code... 

833 

834 all_positions_iter = all_positions 

835 allNodes_iter = all_positions 

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

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

838 """ 

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

840 

841 Should be called with stack=None. 

842 

843 The generated positions are not necessarily in outline order. 

844 

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

846 """ 

847 c = self 

848 

849 if stack is None: 

850 stack = [] 

851 

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

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

854 return # Stop the generator. 

855 

856 def allinds(v, target_v): 

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

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

859 if x is target_v: 

860 yield i 

861 

862 def stack2pos(stack): 

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

864 v, i = stack[-1] 

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

866 

867 for v2 in set(v.parents): 

868 for i in allinds(v2, v): 

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

870 if v2 is c.hiddenRootNode: 

871 yield stack2pos(stack) 

872 else: 

873 yield from c.all_positions_for_v(v2, stack) 

874 stack.pop(0) 

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

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

877 """ 

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

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

880 predicate. 

881 """ 

882 c = self 

883 if predicate is None: 

884 

885 # pylint: disable=function-redefined 

886 

887 def predicate(p): 

888 return p.isAnyAtFileNode() 

889 

890 p = c.rootPosition() 

891 while p: 

892 if predicate(p): 

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

894 p.moveToNodeAfterTree() 

895 else: 

896 p.moveToThreadNext() 

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

898 def all_unique_positions(self, copy=True): 

899 """ 

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

901 Returns only the first position for each vnode. 

902 """ 

903 c = self 

904 p = c.rootPosition() 

905 seen = set() 

906 while p: 

907 if p.v in seen: 

908 p.moveToNodeAfterTree() 

909 else: 

910 seen.add(p.v) 

911 yield p.copy() if copy else p 

912 p.moveToThreadNext() 

913 

914 # Compatibility with old code... 

915 

916 all_positions_with_unique_vnodes_iter = all_unique_positions 

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

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

919 """ 

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

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

922 predicate. 

923 """ 

924 c = self 

925 if predicate is None: 

926 

927 # pylint: disable=function-redefined 

928 

929 def predicate(p): 

930 return p.isAnyAtFileNode() 

931 

932 seen = set() 

933 p = c.rootPosition() 

934 while p: 

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

936 seen.add(p.v) 

937 yield p.copy() if copy else p 

938 p.moveToNodeAfterTree() 

939 else: 

940 p.moveToThreadNext() 

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

942 def safe_all_positions(self, copy=True): 

943 """ 

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

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

946 """ 

947 c = self 

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

949 while p: 

950 yield p.copy() if copy else p 

951 p.safeMoveToThreadNext() 

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

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

954 def currentPosition(self): 

955 """ 

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

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

958 """ 

959 c = self 

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

961 # *Always* return a copy. 

962 return c._currentPosition.copy() 

963 return c.rootPosition() 

964 

965 # For compatibiility with old scripts... 

966 

967 currentVnode = currentPosition 

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

969 @cmd('dump-expanded') 

970 def dump_expanded(self, event): 

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

972 c = event.get('c') 

973 if not c: 

974 return 

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

976 for p in c.all_positions(): 

977 if p.v.expandedPositions: 

978 indent = ' ' * p.level() 

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

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

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

982 def edit_widget(self, p): 

983 c = self 

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

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

986 # Compatibility with scripts 

987 

988 def fileName(self): 

989 s = self.mFileName or "" 

990 if g.isWindows: 

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

992 return s 

993 

994 def relativeFileName(self): 

995 return self.mRelativeFileName or self.mFileName 

996 

997 def shortFileName(self): 

998 return g.shortFileName(self.mFileName) 

999 

1000 shortFilename = shortFileName 

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

1002 def firstVisible(self): 

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

1004 c, p = self, self.p 

1005 while 1: 

1006 back = p.visBack(c) 

1007 if back and back.isVisible(c): 

1008 p = back 

1009 else: break 

1010 return p 

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

1012 def getBodyLines(self): 

1013 """ 

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

1015 

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

1017 text before the insert point if no selection) 

1018 - lines: list of lines containing the selected text 

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

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

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

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

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

1024 """ 

1025 c = self 

1026 body = c.frame.body 

1027 w = body.wrapper 

1028 oldVview = w.getYScrollPosition() 

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

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

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

1032 # Expand the selection. 

1033 i = len(head) 

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

1035 oldSel = i, j 

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

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

1038 def getTabWidth(self, p): 

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

1040 c = self 

1041 val = g.scanAllAtTabWidthDirectives(c, p) 

1042 return val 

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

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

1045 def currentPositionIsRootPosition(self): 

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

1047 

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

1049 here fixes a major leak. 

1050 """ 

1051 c = self 

1052 root = c.rootPosition() 

1053 return c._currentPosition and root and c._currentPosition == root 

1054 # return ( 

1055 # c._currentPosition and c._rootPosition and 

1056 # c._currentPosition == c._rootPosition) 

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

1058 def currentPositionHasNext(self): 

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

1060 

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

1062 here fixes a major leak. 

1063 """ 

1064 c = self 

1065 current = c._currentPosition 

1066 return current and current.hasNext() 

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

1068 def isCurrentPosition(self, p): 

1069 c = self 

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

1071 return False 

1072 return p == c._currentPosition 

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

1074 def isRootPosition(self, p): 

1075 c = self 

1076 root = c.rootPosition() 

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

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

1079 def isChanged(self): 

1080 return self.changed 

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

1082 def lastPosition(self): 

1083 c = self 

1084 p = c.rootPosition() 

1085 while p.hasNext(): 

1086 p.moveToNext() 

1087 while p.hasThreadNext(): 

1088 p.moveToThreadNext() 

1089 return p 

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

1091 def lastTopLevel(self): 

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

1093 c = self 

1094 p = c.rootPosition() 

1095 while p.hasNext(): 

1096 p.moveToNext() 

1097 return p 

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

1099 def lastVisible(self): 

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

1101 c, p = self, self.p 

1102 while 1: 

1103 next = p.visNext(c) 

1104 if next and next.isVisible(c): 

1105 p = next 

1106 else: break 

1107 return p 

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

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

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

1111 if not p or not p.v: 

1112 return False 

1113 

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

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

1116 

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

1118 return False 

1119 

1120 par = self.hiddenRootNode 

1121 for j, x in enumerate(pstack): 

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

1123 return False 

1124 v, i = x 

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

1126 return False 

1127 par = v 

1128 return True 

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

1130 def dumpPosition(self, p): 

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

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

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

1134 v, childIndex = data 

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

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

1137 _rootCount = 0 

1138 

1139 def rootPosition(self): 

1140 """Return the root position. 

1141 

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

1143 top level positions are siblings of this node. 

1144 """ 

1145 c = self 

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

1147 if c.hiddenRootNode.children: 

1148 v = c.hiddenRootNode.children[0] 

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

1150 return None 

1151 

1152 # For compatibiility with old scripts... 

1153 

1154 rootVnode = rootPosition 

1155 findRootPosition = rootPosition 

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

1157 def shouldBeExpanded(self, p): 

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

1159 c, v = self, p.v 

1160 if not p.hasChildren(): 

1161 return False 

1162 # Always clear non-existent positions. 

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

1164 if not p.isCloned(): 

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

1166 return p.v.isExpanded() 

1167 if p.isAncestorOf(c.p): 

1168 return True 

1169 for p2 in v.expandedPositions: 

1170 if p == p2: 

1171 return True 

1172 return False 

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

1174 def visLimit(self): 

1175 """ 

1176 Return the topmost visible node. 

1177 This is affected by chapters and hoists. 

1178 """ 

1179 c = self 

1180 cc = c.chapterController 

1181 if c.hoistStack: 

1182 bunch = c.hoistStack[-1] 

1183 p = bunch.p 

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

1185 return p, limitIsVisible 

1186 return None, None 

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

1188 def vnode2allPositions(self, v): 

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

1190 

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

1192 """ 

1193 c = self 

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

1195 assert c == context 

1196 positions = [] 

1197 for immediate in v.parents: 

1198 if v in immediate.children: 

1199 n = immediate.children.index(v) 

1200 else: 

1201 continue 

1202 stack = [(v, n)] 

1203 while immediate.parents: 

1204 parent = immediate.parents[0] 

1205 if immediate in parent.children: 

1206 n = parent.children.index(immediate) 

1207 else: 

1208 break 

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

1210 immediate = parent 

1211 else: 

1212 v, n = stack.pop() 

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

1214 positions.append(p) 

1215 return positions 

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

1217 def vnode2position(self, v): 

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

1219 """ 

1220 c = self 

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

1222 assert c == context 

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

1224 while v.parents: 

1225 parent = v.parents[0] 

1226 if v in parent.children: 

1227 n = parent.children.index(v) 

1228 else: 

1229 return None 

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

1231 v = parent 

1232 # v.parents includes the hidden root node. 

1233 if not stack: 

1234 # a VNode not in the tree 

1235 return None 

1236 v, n = stack.pop() 

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

1238 return p 

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

1240 def __get_p(self): 

1241 c = self 

1242 return c.currentPosition() 

1243 

1244 p = property( 

1245 __get_p, # No setter. 

1246 doc="commander current position property") 

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

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

1249 def appendStringToBody(self, p, s): 

1250 

1251 if s: 

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

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

1254 def clearAllMarked(self): 

1255 c = self 

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

1257 p.v.clearMarked() 

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

1259 def clearAllVisited(self): 

1260 c = self 

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

1262 p.v.clearVisited() 

1263 p.v.clearWriteBit() 

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

1265 def clearChanged(self): 

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

1267 c = self 

1268 if not c.frame: 

1269 return 

1270 c.changed = False 

1271 if c.loading: 

1272 return # don't update while loading. 

1273 # Clear all dirty bits _before_ setting the caption. 

1274 for v in c.all_unique_nodes(): 

1275 v.clearDirty() 

1276 c.changed = False 

1277 # Do nothing for null frames. 

1278 assert c.gui 

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

1280 return 

1281 if not c.frame.top: 

1282 return 

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

1284 if master: 

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

1286 s = c.frame.getTitle() 

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

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

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

1290 def clearMarked(self, p): 

1291 c = self 

1292 p.v.clearMarked() 

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

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

1295 def setBodyString(self, p, s): 

1296 """ 

1297 This is equivalent to p.b = s. 

1298 

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

1300 """ 

1301 c, v = self, p.v 

1302 if not c or not v: 

1303 return 

1304 s = g.toUnicode(s) 

1305 current = c.p 

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

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

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

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

1310 w = c.frame.body.wrapper 

1311 w.setAllText(s) 

1312 v.setSelection(0, 0) 

1313 c.recolor() 

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

1315 if v.b != s: 

1316 v.setBodyString(s) 

1317 v.setSelection(0, 0) 

1318 p.setDirty() 

1319 if not c.isChanged(): 

1320 c.setChanged() 

1321 c.redraw_after_icons_changed() 

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

1323 def setChanged(self): 

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

1325 c = self 

1326 if not c.frame: 

1327 return 

1328 c.changed = True 

1329 if c.loading: 

1330 return # don't update while loading. 

1331 # Do nothing for null frames. 

1332 assert c.gui 

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

1334 return 

1335 if not c.frame.top: 

1336 return 

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

1338 if master: 

1339 master.setChanged(c, changed=True) 

1340 # LeoTabbedTopLevel.setChanged. 

1341 s = c.frame.getTitle() 

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

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

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

1345 _currentCount = 0 

1346 

1347 def setCurrentPosition(self, p): 

1348 """ 

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

1350 Client code should use c.selectPosition instead. 

1351 """ 

1352 c = self 

1353 if not p: 

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

1355 return 

1356 if c.positionExists(p): 

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

1358 pass # We have already made a copy. 

1359 else: # Make a copy _now_ 

1360 c._currentPosition = p.copy() 

1361 else: 

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

1363 c._currentPosition = c.rootPosition() 

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

1365 g.trace(g.callers()) 

1366 

1367 # For compatibiility with old scripts. 

1368 

1369 setCurrentVnode = setCurrentPosition 

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

1371 def setHeadString(self, p, s): 

1372 """ 

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

1374 

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

1376 """ 

1377 c = self 

1378 p.initHeadString(s) 

1379 p.setDirty() 

1380 # Change the actual tree widget so 

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

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

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

1384 def setLog(self): 

1385 c = self 

1386 if c.exists: 

1387 try: 

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

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

1390 except AttributeError: 

1391 pass 

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

1393 def setMarked(self, p): 

1394 c = self 

1395 p.setMarked() 

1396 p.setDirty() # Defensive programming. 

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

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

1399 def setRootPosition(self, unused_p=None): 

1400 """Set c._rootPosition.""" 

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

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

1403 def setRootVnode(self, v): 

1404 pass 

1405 # c = self 

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

1407 # c.setRootPosition() 

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

1409 def topPosition(self): 

1410 """Return the root position.""" 

1411 c = self 

1412 if c._topPosition: 

1413 return c._topPosition.copy() 

1414 return None 

1415 

1416 def setTopPosition(self, p): 

1417 """Set the root positioin.""" 

1418 c = self 

1419 if p: 

1420 c._topPosition = p.copy() 

1421 else: 

1422 c._topPosition = None 

1423 

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

1425 

1426 topVnode = topPosition 

1427 setTopVnode = setTopPosition 

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

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

1430 def checkGnxs(self): 

1431 """ 

1432 Check the consistency of all gnx's. 

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

1434 Return the number of structure_errors found. 

1435 """ 

1436 c = self 

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

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

1439 ni = g.app.nodeIndices 

1440 t1 = time.time() 

1441 

1442 def new_gnx(v): 

1443 """Set v.fileIndex.""" 

1444 v.fileIndex = ni.getNewIndex(v) 

1445 

1446 count, gnx_errors = 0, 0 

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

1448 count += 1 

1449 v = p.v 

1450 gnx = v.fileIndex 

1451 if gnx: # gnx must be a string. 

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

1453 aSet.add(v) 

1454 d[gnx] = aSet 

1455 else: 

1456 gnx_errors += 1 

1457 new_gnx(v) 

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

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

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

1461 if len(aList) != 1: 

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

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

1464 for v in aList: 

1465 gnx_errors += 1 

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

1467 new_gnx(v) 

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

1469 t2 = time.time() 

1470 if not ok: 

1471 g.es_print( 

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

1473 f"{count} nodes, " 

1474 f"{gnx_errors} gnx errors, " 

1475 f"{g.app.structure_errors} " 

1476 f"structure errors", 

1477 color='red' 

1478 ) 

1479 elif c.verbose_check_outline and not g.unitTesting: 

1480 print( 

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

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

1483 return g.app.structure_errors 

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

1485 def checkLinks(self): 

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

1487 c = self 

1488 t1 = time.time() 

1489 count, errors = 0, 0 

1490 for p in c.safe_all_positions(): 

1491 count += 1 

1492 # try: 

1493 if not c.checkThreadLinks(p): 

1494 errors += 1 

1495 break 

1496 if not c.checkSiblings(p): 

1497 errors += 1 

1498 break 

1499 if not c.checkParentAndChildren(p): 

1500 errors += 1 

1501 break 

1502 # except AssertionError: 

1503 # errors += 1 

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

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

1506 t2 = time.time() 

1507 g.es_print( 

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

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

1510 return errors 

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

1512 def checkParentAndChildren(self, p): 

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

1514 c = self 

1515 

1516 def _assert(condition): 

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

1518 

1519 def dump(p): 

1520 if p and p.v: 

1521 p.v.dump() 

1522 elif p: 

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

1524 else: 

1525 print('<no p>') 

1526 if g.unitTesting: 

1527 assert False, g.callers() 

1528 

1529 if p.hasParent(): 

1530 n = p.childIndex() 

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

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

1533 dump(p) 

1534 dump(p.parent()) 

1535 return False 

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

1537 g.trace("no parents") 

1538 dump(p) 

1539 return False 

1540 for child in p.children(): 

1541 if not c.checkParentAndChildren(child): 

1542 return False 

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

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

1545 dump(p) 

1546 dump(child.parent()) 

1547 return False 

1548 if p.hasNext(): 

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

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

1551 dump(p.next().parent()) 

1552 dump(p.parent()) 

1553 return False 

1554 if p.hasBack(): 

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

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

1557 dump(p.back().parent()) 

1558 dump(p.parent()) 

1559 return False 

1560 # Check consistency of parent and children arrays. 

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

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

1563 parent_v = p._parentVnode() 

1564 n = p.childIndex() 

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

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

1567 parent_v.dump() 

1568 p.v.dump() 

1569 return False 

1570 return True 

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

1572 def checkSiblings(self, p): 

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

1574 back = p.back() 

1575 next = p.next() 

1576 if back: 

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

1578 g.trace( 

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

1580 f" back: {back}\n" 

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

1582 return False 

1583 if next: 

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

1585 g.trace( 

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

1587 f" next: {next}\n" 

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

1589 return False 

1590 return True 

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

1592 def checkThreadLinks(self, p): 

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

1594 threadBack = p.threadBack() 

1595 threadNext = p.threadNext() 

1596 if threadBack: 

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

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

1599 return False 

1600 if threadNext: 

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

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

1603 return False 

1604 return True 

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

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

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

1608 """ 

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

1610 or any of parents ancestors. 

1611 """ 

1612 c = self 

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

1614 clonedVnodes = {} 

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

1616 if ancestor.isCloned(): 

1617 v = ancestor.v 

1618 clonedVnodes[v] = v 

1619 if not clonedVnodes: 

1620 return True 

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

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

1623 if not g.unitTesting and warningFlag: 

1624 c.alert(message) 

1625 return False 

1626 return True 

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

1628 def checkDrag(self, root, target): 

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

1630 c = self 

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

1632 for z in root.subtree(): 

1633 if z == target: 

1634 if not g.unitTesting: 

1635 c.alert(message) 

1636 return False 

1637 return True 

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

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

1640 """ 

1641 Check for errors in the outline. 

1642 Return the count of serious structure errors. 

1643 """ 

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

1645 c = self 

1646 g.app.structure_errors = 0 

1647 structure_errors = c.checkGnxs() 

1648 if check_links and not structure_errors: 

1649 structure_errors += c.checkLinks() 

1650 return structure_errors 

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

1652 # Makes sure all nodes are valid. 

1653 

1654 def validateOutline(self, event=None): 

1655 c = self 

1656 if not g.app.validate_outline: 

1657 return True 

1658 root = c.rootPosition() 

1659 parent = None 

1660 if root: 

1661 return root.validateOutlineWithParent(parent) 

1662 return True 

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

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

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

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

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

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

1669 c = self 

1670 count = 0 

1671 result = "ok" 

1672 for p in c.all_unique_positions(): 

1673 count += 1 

1674 if not g.unitTesting: 

1675 #@+<< print dots >> 

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

1677 if count % 100 == 0: 

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

1679 if count % 2000 == 0: 

1680 g.enl() 

1681 #@-<< print dots >> 

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

1683 if not g.scanForAtSettings(p) and ( 

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

1685 ): 

1686 try: 

1687 c.checkPythonNode(p) 

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

1689 result = "error" # Continue to check. 

1690 except Exception: 

1691 return "surprise" # abort 

1692 if result != 'ok': 

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

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

1695 if not g.unitTesting: 

1696 g.blue("check complete") 

1697 return result 

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

1699 def checkPythonCode(self, 

1700 event=None, 

1701 ignoreAtIgnore=True, 

1702 checkOnSave=False 

1703 ): 

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

1705 c = self 

1706 count = 0 

1707 result = "ok" 

1708 if not g.unitTesting: 

1709 g.es("checking Python code ") 

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

1711 count += 1 

1712 if not g.unitTesting and not checkOnSave: 

1713 #@+<< print dots >> 

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

1715 if count % 100 == 0: 

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

1717 if count % 2000 == 0: 

1718 g.enl() 

1719 #@-<< print dots >> 

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

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

1722 try: 

1723 c.checkPythonNode(p) 

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

1725 result = "error" # Continue to check. 

1726 except Exception: 

1727 return "surprise" # abort 

1728 if not g.unitTesting: 

1729 g.blue("check complete") 

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

1731 return result 

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

1733 def checkPythonNode(self, p): 

1734 c, h = self, p.h 

1735 # Call getScript to ignore directives and section references. 

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

1737 if not body: 

1738 return 

1739 try: 

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

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

1742 c.tabNannyNode(p, h, body) 

1743 except SyntaxError: 

1744 if g.unitTesting: 

1745 raise 

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

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

1748 except Exception: 

1749 g.es_print('unexpected exception') 

1750 g.es_exception() 

1751 raise 

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

1753 # This code is based on tabnanny.check. 

1754 

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

1756 """Check indentation using tabnanny.""" 

1757 try: 

1758 readline = g.ReadLinesClass(body).next 

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

1760 except IndentationError: 

1761 if g.unitTesting: 

1762 raise 

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

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

1765 g.es('', msg) 

1766 except tokenize.TokenError: 

1767 if g.unitTesting: 

1768 raise 

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

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

1771 g.es('', msg) 

1772 except tabnanny.NannyNag: 

1773 if g.unitTesting: 

1774 raise 

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

1776 badline = nag.get_lineno() 

1777 line = nag.get_line() 

1778 message = nag.get_msg() 

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

1780 g.es(message) 

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

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

1783 except Exception: 

1784 g.trace("unexpected exception") 

1785 g.es_exception() 

1786 raise 

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

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

1789 def getTime(self, body=True): 

1790 c = self 

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

1792 # Try to get the format string from settings. 

1793 if body: 

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

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

1796 else: 

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

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

1799 if format is None: 

1800 format = default_format 

1801 try: 

1802 # import time 

1803 if gmt: 

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

1805 else: 

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

1807 except(ImportError, NameError): 

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

1809 return "" 

1810 except Exception: 

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

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

1813 return s 

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

1815 def goToLineNumber(self, n): 

1816 """ 

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

1818 A convenience method called from g.handleScriptException. 

1819 """ 

1820 c = self 

1821 c.gotoCommands.find_file_line(n) 

1822 

1823 def goToScriptLineNumber(self, n, p): 

1824 """ 

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

1826 A convenience method called from g.handleScriptException. 

1827 """ 

1828 c = self 

1829 c.gotoCommands.find_script_line(n, p) 

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

1831 def setFileTimeStamp(self, fn): 

1832 """Update the timestamp for fn..""" 

1833 # c = self 

1834 if g.app.externalFilesController: 

1835 g.app.externalFilesController.set_time(fn) 

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

1837 def updateSyntaxColorer(self, v): 

1838 self.frame.body.updateSyntaxColorer(v) 

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

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

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

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

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

1844 """ 

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

1846 

1847 The number of prompts determines the number of arguments. 

1848 

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

1850 

1851 @g.command('i3') 

1852 def i3_command(event): 

1853 c = event.get('c') 

1854 if not c: return 

1855 

1856 def callback(args, c, event): 

1857 g.trace(args) 

1858 c.bodyWantsFocus() 

1859 

1860 c.interactive(callback, event, 

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

1862 """ 

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

1864 # 

1865 # This pathetic code should be generalized, 

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

1867 c = self 

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

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

1870 if f: 

1871 f(callback, event, prompts) 

1872 else: 

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

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

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

1876 

1877 c, k = self, self.k 

1878 prompt = prompts[0] 

1879 

1880 def state1(event): 

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

1882 k.clearState() 

1883 k.resetLabel() 

1884 k.showStateAndMode() 

1885 

1886 k.setLabelBlue(prompt) 

1887 k.get1Arg(event, handler=state1) 

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

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

1890 

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

1892 prompt1, prompt2 = prompts 

1893 

1894 def state1(event): 

1895 d['arg1'] = k.arg 

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

1897 k.getNextArg(handler=state2) 

1898 

1899 def state2(event): 

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

1901 k.clearState() 

1902 k.resetLabel() 

1903 k.showStateAndMode() 

1904 

1905 k.setLabelBlue(prompt1) 

1906 k.get1Arg(event, handler=state1) 

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

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

1909 

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

1911 prompt1, prompt2, prompt3 = prompts 

1912 

1913 def state1(event): 

1914 d['arg1'] = k.arg 

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

1916 k.getNextArg(handler=state2) 

1917 

1918 def state2(event): 

1919 d['arg2'] = k.arg 

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

1921 k.get1Arg(event, handler=state3) 

1922 # Restart. 

1923 

1924 def state3(event): 

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

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

1927 k.clearState() 

1928 k.resetLabel() 

1929 k.showStateAndMode() 

1930 

1931 k.setLabelBlue(prompt1) 

1932 k.get1Arg(event, handler=state1) 

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

1934 # These are all new in Leo 4.5.1. 

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

1936 def getLanguageAtCursor(self, p, language): 

1937 """ 

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

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

1940 """ 

1941 c = self 

1942 tag = '@language' 

1943 w = c.frame.body.wrapper 

1944 ins = w.getInsertPoint() 

1945 n = 0 

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

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

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

1949 j = g.skip_id(s, i) 

1950 language = s[i:j] 

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

1952 break 

1953 else: 

1954 n += len(s) 

1955 return language 

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

1957 def getNodePath(self, p): 

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

1959 c = self 

1960 aList = g.get_directives_dict_list(p) 

1961 path = c.scanAtPathDirectives(aList) 

1962 return path 

1963 

1964 def getNodeFileName(self, p): 

1965 """ 

1966 Return the full file name at node p, 

1967 including effects of all @path directives. 

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

1969 """ 

1970 c = self 

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

1972 name = p.anyAtFileNodeName() 

1973 if name: 

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

1975 return '' 

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

1977 def hasAmbiguousLanguage(self, p): 

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

1979 # c = self 

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

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

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

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

1984 j = g.skip_id(s, i) 

1985 word = s[i:j] 

1986 languages.add(word) 

1987 return len(list(languages)) > 1 

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

1989 #@@nobeautify 

1990 

1991 def scanAllDirectives(self, p): 

1992 """ 

1993 Scan p and ancestors for directives. 

1994 

1995 Returns a dict containing the results, including defaults. 

1996 """ 

1997 c = self 

1998 p = p or c.p 

1999 # Defaults... 

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

2001 default_delims = g.set_delims_from_language(default_language) 

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

2003 table = ( # type:ignore 

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

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

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

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

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

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

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

2011 ) 

2012 # Set d by scanning all directives. 

2013 aList = g.get_directives_dict_list(p) 

2014 d = {} 

2015 for key, default, func in table: 

2016 val = func(aList) # type:ignore 

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

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

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

2020 d = { 

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

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

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

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

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

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

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

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

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

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

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

2032 } 

2033 return d 

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

2035 def scanAtPathDirectives(self, aList): 

2036 """ 

2037 Scan aList for @path directives. 

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

2039 """ 

2040 c = self 

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

2042 # Step 1: Compute the starting path. 

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

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

2045 base = c.openDirectory 

2046 else: 

2047 base = g.app.config.relative_path_base_directory 

2048 if base and base == "!": 

2049 base = g.app.loadDir 

2050 elif base and base == ".": 

2051 base = c.openDirectory 

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

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

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

2055 # Step 2: look for @path directives. 

2056 paths = [] 

2057 for d in aList: 

2058 # Look for @path directives. 

2059 path = d.get('path') 

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

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

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

2063 path = g.stripPathCruft(path) 

2064 if path and not warning: 

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

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

2067 paths.append(path) 

2068 # We will silently ignore empty @path directives. 

2069 # Add absbase and reverse the list. 

2070 paths.append(absbase) 

2071 paths.reverse() 

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

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

2074 return path or g.getBaseDirectory(c) 

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

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

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

2078 def check_event(self, event): 

2079 """Check an event object.""" 

2080 # c = self 

2081 from leo.core import leoGui 

2082 

2083 if not event: 

2084 return 

2085 stroke = event.stroke 

2086 got = event.char 

2087 if g.unitTesting: 

2088 return 

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

2090 expected = event.char 

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

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

2093 else: 

2094 expected = event.char 

2095 # disable the test. 

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

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

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

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

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

2101 if expected != got: 

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

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

2104 command_count = 0 

2105 

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

2107 """ 

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

2109 

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

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

2112 simple mechanism for overriding commands. 

2113 """ 

2114 c, p = self, self.p 

2115 c.setLog() 

2116 self.command_count += 1 

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

2118 self.command_function = command_func 

2119 self.command_name = command_name 

2120 # The presence of this message disables all commands. 

2121 if c.disableCommandsMessage: 

2122 g.blue(c.disableCommandsMessage) 

2123 return None 

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

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

2126 # 1912: This message is annoying and unhelpful. 

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

2128 return None 

2129 g.app.commandInterruptFlag = False 

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

2131 if len(c.recent_commands_list) > 99: 

2132 c.recent_commands_list.pop() 

2133 c.recent_commands_list.insert(0, command_name) 

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

2135 try: 

2136 c.inCommand = True 

2137 try: 

2138 return_value = command_func(event) 

2139 except Exception: 

2140 g.es_exception() 

2141 return_value = None 

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

2143 c.inCommand = False 

2144 ## c.k.funcReturn = return_value 

2145 except Exception: 

2146 c.inCommand = False 

2147 if g.unitTesting: 

2148 raise 

2149 g.es_print("exception executing command") 

2150 g.es_exception(c=c) 

2151 if c and c.exists: 

2152 if c.requestCloseWindow: 

2153 c.requestCloseWindow = False 

2154 g.app.closeLeoWindow(c.frame) 

2155 else: 

2156 c.outerUpdate() 

2157 # Be careful: the command could destroy c. 

2158 if c and c.exists: 

2159 p = c.p 

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

2161 return return_value 

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

2163 def doCommandByName(self, command_name, event): 

2164 """ 

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

2166 

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

2168 

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

2170 """ 

2171 c = self 

2172 # Get the command's function. 

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

2174 if not command_func: 

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

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

2177 raise AttributeError(message) 

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

2179 g.trace(g.callers()) 

2180 return None 

2181 # Invoke the function. 

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

2183 if c.exists: 

2184 c.frame.updateStatusLine() 

2185 return val 

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

2187 def executeMinibufferCommand(self, commandName): 

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

2189 c = self 

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

2191 return c.doCommandByName(commandName, event) 

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

2193 #@@nobeautify 

2194 

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

2196 """ 

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

2198 

2199 c: The Commander of the outline. 

2200 command: The os command to execute the script. 

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

2202 ext: The file extention for the tempory file. 

2203 language: The language name. 

2204 regex: Optional regular expression describing error messages. 

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

2206 May be a compiled regex expression or a string. 

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

2208 The script may contain section references and @others. 

2209 

2210 Other features: 

2211 

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

2213 - Compute the final command as follows. 

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

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

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

2217 3. Append the full path to the command. 

2218 """ 

2219 c, log = self, self.frame.log 

2220 #@+others # Define helper functions 

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

2222 def put_line(s): 

2223 """ 

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

2225 """ 

2226 if not regex: 

2227 g.es_print(s) 

2228 return 

2229 # Get the line number. 

2230 m = regex.match(s) 

2231 if not m: 

2232 g.es_print(s) 

2233 return 

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

2235 try: 

2236 s1 = m.group(1) 

2237 s2 = m.group(2) 

2238 except IndexError: 

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

2240 return 

2241 if s1.isdigit(): 

2242 n = int(s1) 

2243 fn = s2 

2244 elif s2.isdigit(): 

2245 n = int(s2) 

2246 fn = s1 

2247 else: 

2248 # No line number. 

2249 g.es_print(s) 

2250 return 

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

2252 # Print to the console. 

2253 print(s) 

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

2255 p, n2 = find_line(fn, n) 

2256 # Create the link. 

2257 unl = p.get_UNL() 

2258 if unl: 

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

2260 else: 

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

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

2263 def find_line(path, n): 

2264 """ 

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

2266 """ 

2267 if path == root_path: 

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

2269 else: 

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

2271 found = False 

2272 for p in c.all_positions(): 

2273 if p.isAnyAtFileNode(): 

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

2275 if path == norm_path: 

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

2277 break 

2278 if found: 

2279 return p, offset 

2280 return root, n 

2281 #@-others 

2282 # Compile and check the regex. 

2283 if regex: 

2284 if isinstance(regex, str): 

2285 try: 

2286 regex = re.compile(regex) 

2287 except Exception: 

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

2289 return None 

2290 # Get the script. 

2291 script = g.getScript(c, root, 

2292 useSelectedText=False, 

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

2294 useSentinels=True, 

2295 ) 

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

2297 use_temp = not root.isAnyAtFileNode() 

2298 if use_temp: 

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

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

2301 f.write(script) 

2302 else: 

2303 root_path = g.fullPath(c, root) 

2304 # Compute the final command. 

2305 if '<FILE>' in command: 

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

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

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

2309 else: 

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

2311 # Change directory. 

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

2313 if not directory: 

2314 directory = os.path.dirname(root_path) 

2315 os.chdir(directory) 

2316 # Execute the final command. 

2317 try: 

2318 proc = subprocess.Popen(final_command, 

2319 shell=True, 

2320 stdout=subprocess.PIPE, 

2321 stderr=subprocess.PIPE) 

2322 out, err = proc.communicate() 

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

2324 print(s.rstrip()) 

2325 print('') 

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

2327 put_line(s.rstrip()) 

2328 finally: 

2329 if use_temp: 

2330 os.remove(root_path) 

2331 os.chdir(old_dir) 

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

2333 def insertCharFromEvent(self, event): 

2334 """ 

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

2336 - getArg state: k.getArg. 

2337 - Tree: onCanvasKey or onHeadlineKey. 

2338 - Body: ec.selfInsertCommand 

2339 - Log: log_w.insert 

2340 """ 

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

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

2343 name = c.widget_name(w) 

2344 stroke = event.stroke 

2345 if trace: 

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

2347 if not stroke: 

2348 return 

2349 # 

2350 # Part 1: Very late special cases. 

2351 # 

2352 # #1448 

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

2354 stroke.removeNumPadModifier() 

2355 k.getArg(event, stroke=stroke) 

2356 return 

2357 # Handle all unbound characters in command mode. 

2358 if k.unboundKeyAction == 'command': 

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

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

2361 c.onCanvasKey(event) 

2362 return 

2363 # 

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

2365 # 

2366 # Ignore unbound F-keys. 

2367 if stroke.isFKey(): 

2368 return 

2369 # Ignore unbound Alt/Ctrl keys. 

2370 if stroke.isAltCtrl(): 

2371 if not k.enable_alt_ctrl_bindings: 

2372 return 

2373 if k.ignore_unbound_non_ascii_keys: 

2374 return 

2375 # #868 

2376 if stroke.isPlainNumPad(): 

2377 stroke.removeNumPadModifier() 

2378 event.stroke = stroke 

2379 # #868 

2380 if stroke.isNumPadKey(): 

2381 return 

2382 # Ignore unbound non-ascii character. 

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

2384 return 

2385 # Never insert escape or insert characters. 

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

2387 return 

2388 # 

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

2390 # 

2391 # Handle events in the body pane. 

2392 if name.startswith('body'): 

2393 action = k.unboundKeyAction 

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

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

2396 c.frame.updateStatusLine() 

2397 return 

2398 # 

2399 # Handle events in headlines. 

2400 if name.startswith('head'): 

2401 c.frame.tree.onHeadlineKey(event) 

2402 return 

2403 # 

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

2405 if name.startswith('canvas'): 

2406 if event.char: 

2407 k.searchTree(event.char) 

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

2409 elif not stroke: 

2410 c.onCanvasKey(event) 

2411 return 

2412 # 

2413 # Ignore all events outside the log pane. 

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

2415 return 

2416 # 

2417 # Make sure we can insert into w. 

2418 log_w = event.widget 

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

2420 return 

2421 # 

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

2423 i = log_w.getInsertPoint() 

2424 s = stroke.toGuiChar() 

2425 log_w.insert(i, s) 

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

2427 def setComplexCommand(self, commandName): 

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

2429 c = self 

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

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

2432 def universalCallback(self, source_c, function): 

2433 """Create a universal command callback. 

2434 

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

2436 signature in a callback which adapts standard minibufer command 

2437 callbacks to a compatible format. 

2438 

2439 This also serves to allow rClick callback functions to handle 

2440 minibuffer commands from sources other than rClick menus so allowing 

2441 a single function to handle calls from all sources. 

2442 

2443 A function wrapped in this wrapper can handle rclick generator 

2444 and invocation commands and commands typed in the minibuffer. 

2445 

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

2447 if rclick is not installed. 

2448 """ 

2449 

2450 def minibufferCallback(event, function=function): 

2451 # Avoid a pylint complaint. 

2452 if hasattr(self, 'theContextMenuController'): 

2453 cm = getattr(self, 'theContextMenuController') 

2454 keywords = cm.mb_keywords 

2455 else: 

2456 cm = keywords = None 

2457 if not keywords: 

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

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

2460 # context. 

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

2462 keywords['mb_event'] = event 

2463 retval = None 

2464 try: 

2465 retval = function(keywords) 

2466 finally: 

2467 if cm: 

2468 # Even if there is an error: 

2469 # clear mb_keywords prior to next command and 

2470 # ensure mb_retval from last command is wiped 

2471 cm.mb_keywords = None 

2472 cm.mb_retval = retval 

2473 

2474 minibufferCallback.__doc__ = function.__doc__ 

2475 # For g.getDocStringForFunction 

2476 minibufferCallback.source_c = source_c 

2477 # For GetArgs.command_source 

2478 return minibufferCallback 

2479 

2480 #fix bobjack's spelling error 

2481 

2482 universallCallback = universalCallback 

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

2484 def writeScriptFile(self, script): 

2485 

2486 # Get the path to the file. 

2487 c = self 

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

2489 if path: 

2490 isAbsPath = os.path.isabs(path) 

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

2492 parts = path.split('/') 

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

2494 path = g.app.loadDir 

2495 if isAbsPath: 

2496 # make the first element absolute 

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

2498 allParts = [path] + parts 

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

2500 else: 

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

2502 # 

2503 # Write the file. 

2504 try: 

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

2506 f.write(script) 

2507 except Exception: 

2508 g.es_exception() 

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

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

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

2512 path = None 

2513 return path 

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

2515 def expand_path_expression(self, s): 

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

2517 c = self 

2518 if not s: 

2519 return '' 

2520 s = g.toUnicode(s) 

2521 # find and replace repeated path expressions 

2522 previ, aList = 0, [] 

2523 while previ < len(s): 

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

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

2526 if -1 < i < j: 

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

2528 if previ < i: 

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

2530 # Get expression and find substitute 

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

2532 if exp: 

2533 try: 

2534 s2 = c.replace_path_expression(exp) 

2535 aList.append(s2) 

2536 except Exception: 

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

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

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

2540 previ = j + 2 

2541 else: 

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

2543 aList.append(s[previ:]) 

2544 break 

2545 val = ''.join(aList) 

2546 if g.isWindows: 

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

2548 return val 

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

2550 replace_errors: List[str] = [] 

2551 

2552 def replace_path_expression(self, expr): 

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

2554 c = self 

2555 d = { 

2556 'c': c, 

2557 'g': g, 

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

2559 'p': c.p, 

2560 'os': os, 

2561 'sep': os.sep, 

2562 'sys': sys, 

2563 } 

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

2565 try: 

2566 # pylint: disable=eval-used 

2567 path = eval(expr, d) 

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

2569 except Exception as e: 

2570 message = ( 

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

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

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

2574 if message not in self.replace_errors: 

2575 self.replace_errors.append(message) 

2576 g.trace(message) 

2577 return expr 

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

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

2580 def archivedPositionToPosition(self, s): 

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

2582 c = self 

2583 s = g.toUnicode(s) 

2584 aList = s.split(',') 

2585 try: 

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

2587 except Exception: 

2588 aList = None 

2589 if not aList: 

2590 return None 

2591 p = c.rootPosition() 

2592 level = 0 

2593 while level < len(aList): 

2594 i = aList[level] 

2595 while i > 0: 

2596 if p.hasNext(): 

2597 p.moveToNext() 

2598 i -= 1 

2599 else: 

2600 return None 

2601 level += 1 

2602 if level < len(aList): 

2603 p.moveToFirstChild() 

2604 return p 

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

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

2607 """ 

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

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

2610 """ 

2611 c = self 

2612 fn = fileName or c.fileName() 

2613 if not fn: 

2614 return None 

2615 theDir, base = g.os_path_split(fn) 

2616 if useTimeStamp: 

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

2618 base = base[:-4] 

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

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

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

2622 path = g.os_path_finalize_join(theDir, fn) 

2623 else: 

2624 path = fn 

2625 if path: 

2626 # pylint: disable=no-member 

2627 # Defined in commanderFileCommands.py. 

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

2629 # Issues saved message. 

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

2631 return path 

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

2633 def backup_helper(self, 

2634 base_dir=None, 

2635 env_key='LEO_BACKUP', 

2636 sub_dir=None, 

2637 use_git_prefix=True, 

2638 ): 

2639 """ 

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

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

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

2643 """ 

2644 c = self 

2645 old_cwd = os.getcwd() 

2646 join = g.os_path_finalize_join 

2647 if not base_dir: 

2648 if env_key: 

2649 try: 

2650 base_dir = os.environ[env_key] 

2651 except KeyError: 

2652 print(f"No environment var: {env_key}") 

2653 base_dir = None 

2654 if base_dir and g.os_path_exists(base_dir): 

2655 if use_git_prefix: 

2656 git_branch, junk = g.gitInfo() 

2657 else: 

2658 git_branch = None 

2659 theDir, fn = g.os_path_split(c.fileName()) 

2660 backup_dir = join(base_dir, sub_dir) if sub_dir else base_dir 

2661 path = join(backup_dir, fn) 

2662 if g.os_path_exists(backup_dir): 

2663 written_fn = c.backup( 

2664 path, 

2665 prefix=git_branch, 

2666 silent=True, 

2667 useTimeStamp=True, 

2668 ) 

2669 g.es_print(f"wrote: {written_fn}") 

2670 else: 

2671 g.es_print(f"backup_dir not found: {backup_dir!r}") 

2672 else: 

2673 g.es_print(f"base_dir not found: {base_dir!r}") 

2674 os.chdir(old_cwd) 

2675 #@+node:ekr.20090103070824.11: *4* c.checkFileTimeStamp 

2676 def checkFileTimeStamp(self, fn): 

2677 """ 

2678 Return True if the file given by fn has not been changed 

2679 since Leo read it or if the user agrees to overwrite it. 

2680 """ 

2681 c = self 

2682 if g.app.externalFilesController: 

2683 return g.app.externalFilesController.check_overwrite(c, fn) 

2684 return True 

2685 #@+node:ekr.20090212054250.9: *4* c.createNodeFromExternalFile 

2686 def createNodeFromExternalFile(self, fn): 

2687 """ 

2688 Read the file into a node. 

2689 Return None, indicating that c.open should set focus. 

2690 """ 

2691 c = self 

2692 s, e = g.readFileIntoString(fn) 

2693 if s is None: 

2694 return 

2695 head, ext = g.os_path_splitext(fn) 

2696 if ext.startswith('.'): 

2697 ext = ext[1:] 

2698 language = g.app.extension_dict.get(ext) 

2699 if language: 

2700 prefix = f"@color\n@language {language}\n\n" 

2701 else: 

2702 prefix = '@killcolor\n\n' 

2703 # pylint: disable=no-member 

2704 # Defined in commanderOutlineCommands.py 

2705 p2 = c.insertHeadline(op_name='Open File', as_child=False) 

2706 p2.h = f"@edit {fn}" 

2707 p2.b = prefix + s 

2708 w = c.frame.body.wrapper 

2709 if w: 

2710 w.setInsertPoint(0) 

2711 c.redraw() 

2712 c.recolor() 

2713 #@+node:ekr.20110530124245.18248: *4* c.looksLikeDerivedFile 

2714 def looksLikeDerivedFile(self, fn): 

2715 """ 

2716 Return True if fn names a file that looks like an 

2717 external file written by Leo. 

2718 """ 

2719 # c = self 

2720 try: 

2721 with open(fn, 'rb') as f: # 2020/11/14: Allow unicode characters! 

2722 b = f.read() 

2723 s = g.toUnicode(b) 

2724 return s.find('@+leo-ver=') > -1 

2725 except Exception: 

2726 g.es_exception() 

2727 return False 

2728 #@+node:ekr.20031218072017.2925: *4* c.markAllAtFileNodesDirty 

2729 def markAllAtFileNodesDirty(self, event=None): 

2730 """Mark all @file nodes as changed.""" 

2731 c = self 

2732 c.endEditing() 

2733 p = c.rootPosition() 

2734 while p: 

2735 if p.isAtFileNode(): 

2736 p.setDirty() 

2737 c.setChanged() 

2738 p.moveToNodeAfterTree() 

2739 else: 

2740 p.moveToThreadNext() 

2741 c.redraw_after_icons_changed() 

2742 #@+node:ekr.20031218072017.2926: *4* c.markAtFileNodesDirty 

2743 def markAtFileNodesDirty(self, event=None): 

2744 """Mark all @file nodes in the selected tree as changed.""" 

2745 c = self 

2746 p = c.p 

2747 if not p: 

2748 return 

2749 c.endEditing() 

2750 after = p.nodeAfterTree() 

2751 while p and p != after: 

2752 if p.isAtFileNode(): 

2753 p.setDirty() 

2754 c.setChanged() 

2755 p.moveToNodeAfterTree() 

2756 else: 

2757 p.moveToThreadNext() 

2758 c.redraw_after_icons_changed() 

2759 #@+node:ekr.20031218072017.2823: *4* c.openWith 

2760 def openWith(self, event=None, d=None): 

2761 """ 

2762 This is *not* a command. 

2763 

2764 Handles the items in the Open With... menu. 

2765 

2766 See ExternalFilesController.open_with for details about d. 

2767 """ 

2768 c = self 

2769 if d and g.app.externalFilesController: 

2770 # Select an ancestor @<file> node if possible. 

2771 if not d.get('p'): 

2772 d['p'] = None 

2773 p = c.p 

2774 while p: 

2775 if p.isAnyAtFileNode(): 

2776 d['p'] = p 

2777 break 

2778 p.moveToParent() 

2779 g.app.externalFilesController.open_with(c, d) 

2780 elif not d: 

2781 g.trace('can not happen: no d', g.callers()) 

2782 #@+node:ekr.20140717074441.17770: *4* c.recreateGnxDict 

2783 def recreateGnxDict(self): 

2784 """Recreate the gnx dict prior to refreshing nodes from disk.""" 

2785 c, d = self, {} 

2786 for v in c.all_unique_nodes(): 

2787 gnxString = v.fileIndex 

2788 if isinstance(gnxString, str): 

2789 d[gnxString] = v 

2790 if 'gnx' in g.app.debug: 

2791 g.trace(c.shortFileName(), gnxString, v) 

2792 else: 

2793 g.internalError(f"no gnx for vnode: {v}") 

2794 c.fileCommands.gnxDict = d 

2795 #@+node:ekr.20180508111544.1: *3* c.Git 

2796 #@+node:ekr.20180510104805.1: *4* c.diff_file 

2797 def diff_file(self, fn, rev1='HEAD', rev2=''): 

2798 """ 

2799 Create an outline describing the git diffs for all files changed 

2800 between rev1 and rev2. 

2801 """ 

2802 from leo.commands import editFileCommands as efc 

2803 x = efc.GitDiffController(c=self) 

2804 x.diff_file(fn=fn, rev1=rev1, rev2=rev2) 

2805 #@+node:ekr.20180508110755.1: *4* c.diff_two_revs 

2806 def diff_two_revs(self, directory=None, rev1='', rev2=''): 

2807 """ 

2808 Create an outline describing the git diffs for all files changed 

2809 between rev1 and rev2. 

2810 """ 

2811 from leo.commands import editFileCommands as efc 

2812 efc.GitDiffController(c=self).diff_two_revs(rev1=rev1, rev2=rev2) 

2813 #@+node:ekr.20180510103923.1: *4* c.diff_two_branches 

2814 def diff_two_branches(self, branch1, branch2, fn): 

2815 """ 

2816 Create an outline describing the git diffs for all files changed 

2817 between rev1 and rev2. 

2818 """ 

2819 from leo.commands import editFileCommands as efc 

2820 efc.GitDiffController(c=self).diff_two_branches( 

2821 branch1=branch1, branch2=branch2, fn=fn) 

2822 #@+node:ekr.20180510105125.1: *4* c.git_diff 

2823 def git_diff(self, rev1='HEAD', rev2=''): 

2824 

2825 from leo.commands import editFileCommands as efc 

2826 efc.GitDiffController(c=self).git_diff(rev1, rev2) 

2827 #@+node:ekr.20171124100534.1: *3* c.Gui 

2828 #@+node:ekr.20111217154130.10286: *4* c.Dialogs & messages 

2829 #@+node:ekr.20110510052422.14618: *5* c.alert 

2830 def alert(self, message): 

2831 c = self 

2832 # The unit tests just tests the args. 

2833 if not g.unitTesting: 

2834 g.es(message) 

2835 g.app.gui.alert(c, message) 

2836 #@+node:ekr.20111217154130.10284: *5* c.init_error_dialogs 

2837 def init_error_dialogs(self): 

2838 c = self 

2839 c.import_error_nodes = [] 

2840 c.ignored_at_file_nodes = [] 

2841 c.orphan_at_file_nodes = [] 

2842 #@+node:ekr.20171123135805.1: *5* c.notValidInBatchMode 

2843 def notValidInBatchMode(self, commandName): 

2844 g.es('the', commandName, "command is not valid in batch mode") 

2845 #@+node:ekr.20110530082209.18250: *5* c.putHelpFor 

2846 def putHelpFor(self, s, short_title=''): 

2847 """Helper for various help commands.""" 

2848 c = self 

2849 g.app.gui.put_help(c, s, short_title) 

2850 #@+node:ekr.20111217154130.10285: *5* c.raise_error_dialogs 

2851 warnings_dict: Dict[str, bool] = {} 

2852 

2853 def raise_error_dialogs(self, kind='read'): 

2854 """Warn about read/write failures.""" 

2855 c = self 

2856 use_dialogs = False 

2857 if g.unitTesting: 

2858 c.init_error_dialogs() 

2859 return 

2860 # 

2861 # Issue one or two dialogs or messages. 

2862 saved_body = c.rootPosition().b 

2863 # Save the root's body. Somehow the dialog destroys it! 

2864 if c.import_error_nodes or c.ignored_at_file_nodes or c.orphan_at_file_nodes: 

2865 g.app.gui.dismiss_splash_screen() 

2866 else: 

2867 # #1007: Exit now, so we don't have to restore c.rootPosition().b. 

2868 c.init_error_dialogs() 

2869 return 

2870 if c.import_error_nodes: 

2871 files = '\n'.join(sorted(set(c.import_error_nodes))) # type:ignore 

2872 if files not in self.warnings_dict: 

2873 self.warnings_dict[files] = True 

2874 import_message1 = 'The following were not imported properly.' 

2875 import_message2 = f"Inserted @ignore in...\n{files}" 

2876 g.es_print(import_message1, color='red') 

2877 g.es_print(import_message2) 

2878 if use_dialogs: 

2879 import_dialog_message = f"{import_message1}\n{import_message2}" 

2880 g.app.gui.runAskOkDialog(c, 

2881 message=import_dialog_message, title='Import errors') 

2882 if c.ignored_at_file_nodes: 

2883 files = '\n'.join(sorted(set(c.ignored_at_file_nodes))) # type:ignore 

2884 if files not in self.warnings_dict: 

2885 self.warnings_dict[files] = True 

2886 kind_s = 'read' if kind == 'read' else 'written' 

2887 ignored_message = f"The following were not {kind_s} because they contain @ignore:" 

2888 kind = 'read' if kind.startswith('read') else 'written' 

2889 g.es_print(ignored_message, color='red') 

2890 g.es_print(files) 

2891 if use_dialogs: 

2892 ignored_dialog_message = f"{ignored_message}\n{files}" 

2893 g.app.gui.runAskOkDialog(c, 

2894 message=ignored_dialog_message, title=f"Not {kind.capitalize()}") 

2895 # 

2896 # #1050: always raise a dialog for orphan @<file> nodes. 

2897 if c.orphan_at_file_nodes: 

2898 message = '\n'.join([ 

2899 'The following were not written because of errors:\n', 

2900 '\n'.join(sorted(set(c.orphan_at_file_nodes))), # type:ignore 

2901 '', 

2902 'Warning: changes to these files will be lost\n' 

2903 'unless you can save the files successfully.' 

2904 ]) 

2905 g.app.gui.runAskOkDialog(c, message=message, title='Not Written') 

2906 # Mark all the nodes dirty. 

2907 for z in c.all_unique_positions(): 

2908 if z.isOrphan(): 

2909 z.setDirty() 

2910 z.clearOrphan() 

2911 c.setChanged() 

2912 c.redraw() 

2913 # Restore the root position's body. 

2914 c.rootPosition().v.b = saved_body 

2915 # #1007: just set v.b. 

2916 c.init_error_dialogs() 

2917 #@+node:ekr.20150710083827.1: *5* c.syntaxErrorDialog 

2918 def syntaxErrorDialog(self): 

2919 """Warn about syntax errors in files.""" 

2920 c = self 

2921 if g.app.syntax_error_files and c.config.getBool( 

2922 'syntax-error-popup', default=False): 

2923 aList = sorted(set(g.app.syntax_error_files)) 

2924 g.app.syntax_error_files = [] 

2925 list_s = '\n'.join(aList) 

2926 g.app.gui.runAskOkDialog( 

2927 c, 

2928 title='Python Errors', 

2929 message=f"Python errors in:\n\n{list_s}", 

2930 text="Ok", 

2931 ) 

2932 #@+node:ekr.20031218072017.2945: *4* c.Dragging 

2933 #@+node:ekr.20031218072017.2947: *5* c.dragToNthChildOf 

2934 def dragToNthChildOf(self, p, parent, n): 

2935 c, p, u = self, self.p, self.undoer 

2936 if not c.checkDrag(p, parent): 

2937 return 

2938 if not c.checkMoveWithParentWithWarning(p, parent, True): 

2939 return 

2940 c.endEditing() 

2941 undoData = u.beforeMoveNode(p) 

2942 p.setDirty() 

2943 p.moveToNthChildOf(parent, n) 

2944 p.setDirty() 

2945 c.setChanged() 

2946 u.afterMoveNode(p, 'Drag', undoData) 

2947 c.redraw(p) 

2948 c.updateSyntaxColorer(p) # Dragging can change syntax coloring. 

2949 #@+node:ekr.20031218072017.2353: *5* c.dragAfter 

2950 def dragAfter(self, p, after): 

2951 c, p, u = self, self.p, self.undoer 

2952 if not c.checkDrag(p, after): 

2953 return 

2954 if not c.checkMoveWithParentWithWarning(p, after.parent(), True): 

2955 return 

2956 c.endEditing() 

2957 undoData = u.beforeMoveNode(p) 

2958 p.setDirty() 

2959 p.moveAfter(after) 

2960 p.setDirty() 

2961 c.setChanged() 

2962 u.afterMoveNode(p, 'Drag', undoData) 

2963 c.redraw(p) 

2964 c.updateSyntaxColorer(p) # Dragging can change syntax coloring. 

2965 #@+node:ekr.20031218072017.2946: *5* c.dragCloneToNthChildOf 

2966 def dragCloneToNthChildOf(self, p, parent, n): 

2967 c = self 

2968 u = c.undoer 

2969 undoType = 'Clone Drag' 

2970 current = c.p 

2971 clone = p.clone() # Creates clone & dependents, does not set undo. 

2972 if ( 

2973 not c.checkDrag(p, parent) or 

2974 not c.checkMoveWithParentWithWarning(clone, parent, True) 

2975 ): 

2976 clone.doDelete(newNode=p) # Destroys clone and makes p the current node. 

2977 c.selectPosition(p) # Also sets root position. 

2978 return 

2979 c.endEditing() 

2980 undoData = u.beforeInsertNode(current) 

2981 clone.setDirty() 

2982 clone.moveToNthChildOf(parent, n) 

2983 clone.setDirty() 

2984 c.setChanged() 

2985 u.afterInsertNode(clone, undoType, undoData) 

2986 c.redraw(clone) 

2987 c.updateSyntaxColorer(clone) # Dragging can change syntax coloring. 

2988 #@+node:ekr.20031218072017.2948: *5* c.dragCloneAfter 

2989 def dragCloneAfter(self, p, after): 

2990 c = self 

2991 u = c.undoer 

2992 undoType = 'Clone Drag' 

2993 current = c.p 

2994 clone = p.clone() # Creates clone. Does not set undo. 

2995 if c.checkDrag( 

2996 p, after) and c.checkMoveWithParentWithWarning(clone, after.parent(), True): 

2997 c.endEditing() 

2998 undoData = u.beforeInsertNode(current) 

2999 clone.setDirty() 

3000 clone.moveAfter(after) 

3001 clone.v.setDirty() 

3002 c.setChanged() 

3003 u.afterInsertNode(clone, undoType, undoData) 

3004 p = clone 

3005 else: 

3006 clone.doDelete(newNode=p) 

3007 c.redraw(p) 

3008 c.updateSyntaxColorer(clone) # Dragging can change syntax coloring. 

3009 #@+node:ekr.20031218072017.2949: *4* c.Drawing 

3010 #@+node:ekr.20080514131122.8: *5* c.bringToFront 

3011 def bringToFront(self, c2=None): 

3012 c = self 

3013 c2 = c2 or c 

3014 g.app.gui.ensure_commander_visible(c2) 

3015 

3016 BringToFront = bringToFront # Compatibility with old scripts 

3017 #@+node:ekr.20040803072955.143: *5* c.expandAllAncestors 

3018 def expandAllAncestors(self, p): 

3019 """ 

3020 Expand all ancestors without redrawing. 

3021 Return a flag telling whether a redraw is needed. 

3022 """ 

3023 # c = self 

3024 redraw_flag = False 

3025 for p in p.parents(): 

3026 if not p.v.isExpanded(): 

3027 p.v.expand() 

3028 p.expand() 

3029 redraw_flag = True 

3030 elif p.isExpanded(): 

3031 p.v.expand() 

3032 else: 

3033 p.expand() 

3034 redraw_flag = True 

3035 return redraw_flag 

3036 #@+node:ekr.20080514131122.20: *5* c.outerUpdate 

3037 def outerUpdate(self): 

3038 """Handle delayed focus requests and modified events.""" 

3039 c = self 

3040 if not c.exists or not c.k: 

3041 return 

3042 # New in Leo 5.6: Delayed redraws are useful in utility methods. 

3043 if c.requestLaterRedraw: 

3044 if c.enableRedrawFlag: 

3045 c.requestLaterRedraw = False 

3046 if 'drawing' in g.app.debug and not g.unitTesting: 

3047 g.trace('\nDELAYED REDRAW') 

3048 time.sleep(1.0) 

3049 c.redraw() 

3050 # Delayed focus requests will always be useful. 

3051 if c.requestedFocusWidget: 

3052 w = c.requestedFocusWidget 

3053 if 'focus' in g.app.debug and not g.unitTesting: 

3054 if hasattr(w, 'objectName'): 

3055 name = w.objectName() 

3056 else: 

3057 name = w.__class__.__name__ 

3058 g.trace('DELAYED FOCUS', name) 

3059 c.set_focus(w) 

3060 c.requestedFocusWidget = None 

3061 table = ( 

3062 ("childrenModified", g.childrenModifiedSet), 

3063 ("contentModified", g.contentModifiedSet), 

3064 ) 

3065 for kind, mods in table: 

3066 if mods: 

3067 g.doHook(kind, c=c, nodes=mods) 

3068 mods.clear() 

3069 #@+node:ekr.20080514131122.13: *5* c.recolor 

3070 def recolor(self, p=None): 

3071 # Support QScintillaColorizer.colorize. 

3072 c = self 

3073 colorizer = c.frame.body.colorizer 

3074 if colorizer and hasattr(colorizer, 'colorize'): 

3075 colorizer.colorize(p or c.p) 

3076 

3077 recolor_now = recolor 

3078 #@+node:ekr.20080514131122.14: *5* c.redrawing... 

3079 #@+node:ekr.20170808014610.1: *6* c.enable/disable_redraw 

3080 def disable_redraw(self): 

3081 """Disable all redrawing until enabled.""" 

3082 c = self 

3083 c.enableRedrawFlag = False 

3084 

3085 def enable_redraw(self): 

3086 c = self 

3087 c.enableRedrawFlag = True 

3088 #@+node:ekr.20090110073010.1: *6* c.redraw 

3089 @cmd('redraw') 

3090 def redraw_command(self, event): 

3091 c = event.get('c') 

3092 if c: 

3093 c.redraw() 

3094 

3095 def redraw(self, p=None): 

3096 """ 

3097 Redraw the screen immediately. 

3098 If p is given, set c.p to p. 

3099 """ 

3100 c = self 

3101 # New in Leo 5.6: clear the redraw request. 

3102 c.requestLaterRedraw = False 

3103 if not p: 

3104 p = c.p or c.rootPosition() 

3105 if not p: 

3106 return 

3107 c.expandAllAncestors(p) 

3108 if p: 

3109 # Fix bug https://bugs.launchpad.net/leo-editor/+bug/1183855 

3110 # This looks redundant, but it is probably the only safe fix. 

3111 c.frame.tree.select(p) 

3112 # tree.redraw will change the position if p is a hoisted @chapter node. 

3113 p2 = c.frame.tree.redraw(p) 

3114 # Be careful. NullTree.redraw returns None. 

3115 # #503: NullTree.redraw(p) now returns p. 

3116 c.selectPosition(p2 or p) 

3117 # Do not call treeFocusHelper here. 

3118 # c.treeFocusHelper() 

3119 # Clear the redraw request, again. 

3120 c.requestLaterRedraw = False 

3121 

3122 # Compatibility with old scripts 

3123 

3124 force_redraw = redraw 

3125 redraw_now = redraw 

3126 #@+node:ekr.20090110073010.3: *6* c.redraw_afer_icons_changed 

3127 def redraw_after_icons_changed(self): 

3128 """Update the icon for the presently selected node""" 

3129 c = self 

3130 if c.enableRedrawFlag: 

3131 c.frame.tree.redraw_after_icons_changed() 

3132 # Do not call treeFocusHelper here. 

3133 # c.treeFocusHelper() 

3134 else: 

3135 c.requestLaterRedraw = True 

3136 #@+node:ekr.20090110131802.2: *6* c.redraw_after_contract 

3137 def redraw_after_contract(self, p=None): 

3138 c = self 

3139 if c.enableRedrawFlag: 

3140 if p: 

3141 c.setCurrentPosition(p) 

3142 else: 

3143 p = c.currentPosition() 

3144 c.frame.tree.redraw_after_contract(p) 

3145 c.treeFocusHelper() 

3146 else: 

3147 c.requestLaterRedraw = True 

3148 #@+node:ekr.20090112065525.1: *6* c.redraw_after_expand 

3149 def redraw_after_expand(self, p): 

3150 c = self 

3151 if c.enableRedrawFlag: 

3152 if p: 

3153 c.setCurrentPosition(p) 

3154 else: 

3155 p = c.currentPosition() 

3156 c.frame.tree.redraw_after_expand(p) 

3157 c.treeFocusHelper() 

3158 else: 

3159 c.requestLaterRedraw = True 

3160 #@+node:ekr.20090110073010.2: *6* c.redraw_after_head_changed 

3161 def redraw_after_head_changed(self): 

3162 """ 

3163 Redraw the screen (if needed) when editing ends. 

3164 This may be a do-nothing for some gui's. 

3165 """ 

3166 c = self 

3167 if c.enableRedrawFlag: 

3168 self.frame.tree.redraw_after_head_changed() 

3169 else: 

3170 c.requestLaterRedraw = True 

3171 #@+node:ekr.20090110073010.4: *6* c.redraw_after_select 

3172 def redraw_after_select(self, p): 

3173 """Redraw the screen after node p has been selected.""" 

3174 c = self 

3175 if c.enableRedrawFlag: 

3176 flag = c.expandAllAncestors(p) 

3177 if flag: 

3178 c.frame.tree.redraw_after_select(p) 

3179 # This is the same as c.frame.tree.full_redraw(). 

3180 else: 

3181 c.requestLaterRedraw = True 

3182 #@+node:ekr.20170908081918.1: *6* c.redraw_later 

3183 def redraw_later(self): 

3184 """ 

3185 Ensure that c.redraw() will be called eventually. 

3186 

3187 c.outerUpdate will call c.redraw() only if no other code calls c.redraw(). 

3188 """ 

3189 c = self 

3190 c.requestLaterRedraw = True 

3191 if 'drawing' in g.app.debug: 

3192 # g.trace('\n' + g.callers(8)) 

3193 g.trace(g.callers()) 

3194 #@+node:ekr.20080514131122.17: *5* c.widget_name 

3195 def widget_name(self, widget): 

3196 # c = self 

3197 return g.app.gui.widget_name(widget) if g.app.gui else '<no widget>' 

3198 #@+node:ekr.20171124101045.1: *4* c.Events 

3199 #@+node:ekr.20060923202156: *5* c.onCanvasKey 

3200 def onCanvasKey(self, event): 

3201 """ 

3202 Navigate to the next headline starting with ch = event.char. 

3203 If ch is uppercase, search all headlines; otherwise search only visible headlines. 

3204 This is modelled on Windows explorer. 

3205 """ 

3206 if not event or not event.char or not event.char.isalnum(): 

3207 return 

3208 c, p = self, self.p 

3209 p1 = p.copy() 

3210 invisible = c.config.getBool('invisible-outline-navigation') 

3211 ch = event.char if event else '' 

3212 allFlag = ch.isupper() and invisible # all is a global (!?) 

3213 if not invisible: 

3214 ch = ch.lower() 

3215 found = False 

3216 extend = self.navQuickKey() 

3217 attempts = (True, False) if extend else (False,) 

3218 for extend2 in attempts: 

3219 p = p1.copy() 

3220 while 1: 

3221 if allFlag: 

3222 p.moveToThreadNext() 

3223 else: 

3224 p.moveToVisNext(c) 

3225 if not p: 

3226 p = c.rootPosition() 

3227 if p == p1: # Never try to match the same position. 

3228 found = False 

3229 break 

3230 newPrefix = c.navHelper(p, ch, extend2) 

3231 if newPrefix: 

3232 found = True 

3233 break 

3234 if found: 

3235 break 

3236 if found: 

3237 c.selectPosition(p) 

3238 c.redraw_after_select(p) 

3239 c.navTime = time.time() 

3240 c.navPrefix = newPrefix 

3241 else: 

3242 c.navTime = None 

3243 c.navPrefix = '' 

3244 c.treeWantsFocus() 

3245 #@+node:ekr.20061002095711.1: *6* c.navQuickKey 

3246 def navQuickKey(self) -> bool: 

3247 """ 

3248 Return true if there are two quick outline navigation keys 

3249 in quick succession. 

3250 

3251 Returns False if @float outline_nav_extend_delay setting is 0.0 or unspecified. 

3252 """ 

3253 c = self 

3254 deltaTime = c.config.getFloat('outline-nav-extend-delay') 

3255 if deltaTime in (None, 0.0): 

3256 return False 

3257 if c.navTime is None: 

3258 return False # mypy. 

3259 return time.time() - c.navTime < deltaTime 

3260 #@+node:ekr.20061002095711: *6* c.navHelper 

3261 def navHelper(self, p, ch, extend): 

3262 c = self 

3263 h = p.h.lower() 

3264 if extend: 

3265 prefix = c.navPrefix + ch 

3266 return h.startswith(prefix.lower()) and prefix 

3267 if h.startswith(ch): 

3268 return ch 

3269 # New feature: search for first non-blank character after @x for common x. 

3270 if ch != '@' and h.startswith('@'): 

3271 for s in ('button', 'command', 'file', 'thin', 'asis', 'nosent',): 

3272 prefix = '@' + s 

3273 if h.startswith('@' + s): 

3274 while 1: 

3275 n = len(prefix) 

3276 ch2 = h[n] if n < len(h) else '' 

3277 if ch2.isspace(): 

3278 prefix = prefix + ch2 

3279 else: break 

3280 if len(prefix) < len(h) and h.startswith(prefix + ch.lower()): 

3281 return prefix + ch 

3282 return '' 

3283 #@+node:ekr.20031218072017.2909: *4* c.Expand/contract 

3284 #@+node:ekr.20171124091426.1: *5* c.contractAllHeadlines 

3285 def contractAllHeadlines(self, event=None): 

3286 """Contract all nodes in the outline.""" 

3287 c = self 

3288 for v in c.all_nodes(): 

3289 v.contract() 

3290 if c.hoistStack: 

3291 # #2380: Handle hoists properly. 

3292 bunch = c.hoistStack[-1] 

3293 p = bunch.p 

3294 else: 

3295 # Select the topmost ancestor of the presently selected node. 

3296 p = c.p 

3297 while p and p.hasParent(): 

3298 p.moveToParent() 

3299 c.selectPosition(p) # #2380: Don't redraw here. 

3300 c.expansionLevel = 1 # Reset expansion level. 

3301 #@+node:ekr.20031218072017.2910: *5* c.contractSubtree 

3302 def contractSubtree(self, p): 

3303 for p in p.subtree(): 

3304 p.contract() 

3305 #@+node:ekr.20031218072017.2911: *5* c.expandSubtree 

3306 def expandSubtree(self, p): 

3307 # c = self 

3308 last = p.lastNode() 

3309 p = p.copy() 

3310 while p and p != last: 

3311 p.expand() 

3312 p = p.moveToThreadNext() 

3313 #@+node:ekr.20031218072017.2912: *5* c.expandToLevel 

3314 def expandToLevel(self, level): 

3315 

3316 c = self 

3317 n = c.p.level() 

3318 old_expansion_level = c.expansionLevel 

3319 max_level = 0 

3320 for p in c.p.self_and_subtree(copy=False): 

3321 if p.level() - n + 1 < level: 

3322 p.expand() 

3323 max_level = max(max_level, p.level() - n + 1) 

3324 else: 

3325 p.contract() 

3326 c.expansionNode = c.p.copy() 

3327 c.expansionLevel = max_level + 1 

3328 if c.expansionLevel != old_expansion_level: 

3329 c.redraw() 

3330 # It's always useful to announce the level. 

3331 # c.k.setLabelBlue('level: %s' % (max_level+1)) 

3332 # g.es('level', max_level + 1) 

3333 c.frame.putStatusLine(f"level: {max_level + 1}") 

3334 # bg='red', fg='red') 

3335 #@+node:ekr.20141028061518.23: *4* c.Focus 

3336 #@+node:ekr.20080514131122.9: *5* c.get/request/set_focus 

3337 def get_focus(self): 

3338 c = self 

3339 w = g.app.gui and g.app.gui.get_focus(c) 

3340 if 'focus' in g.app.debug: 

3341 name = w.objectName() if hasattr(w, 'objectName') else w.__class__.__name__ 

3342 g.trace('(c)', name) 

3343 # g.trace('\n(c)', w.__class__.__name__) 

3344 # g.trace(g.callers(6)) 

3345 return w 

3346 

3347 def get_requested_focus(self): 

3348 c = self 

3349 return c.requestedFocusWidget 

3350 

3351 def request_focus(self, w): 

3352 c = self 

3353 if w and g.app.gui: 

3354 if 'focus' in g.app.debug: 

3355 # g.trace('\n(c)', repr(w)) 

3356 name = w.objectName( 

3357 ) if hasattr(w, 'objectName') else w.__class__.__name__ 

3358 g.trace('(c)', name) 

3359 c.requestedFocusWidget = w 

3360 

3361 def set_focus(self, w): 

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

3363 c = self 

3364 if w and g.app.gui: 

3365 if trace: 

3366 name = w.objectName( 

3367 ) if hasattr(w, 'objectName') else w.__class__.__name__ 

3368 g.trace('(c)', name) 

3369 g.app.gui.set_focus(c, w) 

3370 else: 

3371 if trace: 

3372 g.trace('(c) no w') 

3373 c.requestedFocusWidget = None 

3374 #@+node:ekr.20080514131122.10: *5* c.invalidateFocus (do nothing) 

3375 def invalidateFocus(self): 

3376 """Indicate that the focus is in an invalid location, or is unknown.""" 

3377 # c = self 

3378 # c.requestedFocusWidget = None 

3379 pass 

3380 #@+node:ekr.20080514131122.16: *5* c.traceFocus (not used) 

3381 def traceFocus(self, w): 

3382 c = self 

3383 if 'focus' in g.app.debug: 

3384 c.trace_focus_count += 1 

3385 g.pr(f"{c.trace_focus_count:4d}", c.widget_name(w), g.callers(8)) 

3386 #@+node:ekr.20070226121510: *5* c.xFocusHelper & initialFocusHelper 

3387 def treeFocusHelper(self): 

3388 c = self 

3389 if c.stayInTreeAfterSelect: 

3390 c.treeWantsFocus() 

3391 else: 

3392 c.bodyWantsFocus() 

3393 

3394 def initialFocusHelper(self): 

3395 c = self 

3396 if c.outlineHasInitialFocus: 

3397 c.treeWantsFocus() 

3398 else: 

3399 c.bodyWantsFocus() 

3400 #@+node:ekr.20080514131122.18: *5* c.xWantsFocus 

3401 def bodyWantsFocus(self): 

3402 c = self 

3403 body = c.frame.body 

3404 c.request_focus(body and body.wrapper) 

3405 

3406 def logWantsFocus(self): 

3407 c = self 

3408 log = c.frame.log 

3409 c.request_focus(log and log.logCtrl) 

3410 

3411 def minibufferWantsFocus(self): 

3412 c = self 

3413 c.request_focus(c.miniBufferWidget) 

3414 

3415 def treeWantsFocus(self): 

3416 c = self 

3417 tree = c.frame.tree 

3418 c.request_focus(tree and tree.canvas) 

3419 

3420 def widgetWantsFocus(self, w): 

3421 c = self 

3422 c.request_focus(w) 

3423 #@+node:ekr.20080514131122.19: *5* c.xWantsFocusNow 

3424 # widgetWantsFocusNow does an automatic update. 

3425 

3426 def widgetWantsFocusNow(self, w): 

3427 c = self 

3428 if w: 

3429 c.set_focus(w) 

3430 c.requestedFocusWidget = None 

3431 

3432 # New in 4.9: all FocusNow methods now *do* call c.outerUpdate(). 

3433 

3434 def bodyWantsFocusNow(self): 

3435 c, body = self, self.frame.body 

3436 c.widgetWantsFocusNow(body and body.wrapper) 

3437 

3438 def logWantsFocusNow(self): 

3439 c, log = self, self.frame.log 

3440 c.widgetWantsFocusNow(log and log.logCtrl) 

3441 

3442 def minibufferWantsFocusNow(self): 

3443 c = self 

3444 c.widgetWantsFocusNow(c.miniBufferWidget) 

3445 

3446 def treeWantsFocusNow(self): 

3447 c, tree = self, self.frame.tree 

3448 c.widgetWantsFocusNow(tree and tree.canvas) 

3449 #@+node:ekr.20031218072017.2955: *4* c.Menus 

3450 #@+node:ekr.20080610085158.2: *5* c.add_command 

3451 def add_command(self, menu, **keys): 

3452 c = self 

3453 command = keys.get('command') 

3454 if command: 

3455 # Command is always either: 

3456 # one of two callbacks defined in createMenuEntries or 

3457 # recentFilesCallback, defined in createRecentFilesMenuItems. 

3458 

3459 def add_commandCallback(c=c, command=command): 

3460 val = command() 

3461 # Careful: func may destroy c. 

3462 if c.exists: 

3463 c.outerUpdate() 

3464 return val 

3465 

3466 keys['command'] = add_commandCallback 

3467 menu.add_command(**keys) 

3468 else: 

3469 g.trace('can not happen: no "command" arg') 

3470 #@+node:ekr.20171123203044.1: *5* c.Menu Enablers 

3471 #@+node:ekr.20040131170659: *6* c.canClone 

3472 def canClone(self): 

3473 c = self 

3474 if c.hoistStack: 

3475 current = c.p 

3476 bunch = c.hoistStack[-1] 

3477 return current != bunch.p 

3478 return True 

3479 #@+node:ekr.20031218072017.2956: *6* c.canContractAllHeadlines 

3480 def canContractAllHeadlines(self): 

3481 """Contract all nodes in the tree.""" 

3482 c = self 

3483 for p in c.all_positions(): # was c.all_unique_positions() 

3484 if p.isExpanded(): 

3485 return True 

3486 return False 

3487 #@+node:ekr.20031218072017.2957: *6* c.canContractAllSubheads 

3488 def canContractAllSubheads(self): 

3489 current = self.p 

3490 for p in current.subtree(): 

3491 if p != current and p.isExpanded(): 

3492 return True 

3493 return False 

3494 #@+node:ekr.20031218072017.2958: *6* c.canContractParent 

3495 def canContractParent(self) -> bool: 

3496 c = self 

3497 return c.p.parent() 

3498 #@+node:ekr.20031218072017.2959: *6* c.canContractSubheads 

3499 def canContractSubheads(self): 

3500 current = self.p 

3501 for child in current.children(): 

3502 if child.isExpanded(): 

3503 return True 

3504 return False 

3505 #@+node:ekr.20031218072017.2960: *6* c.canCutOutline & canDeleteHeadline 

3506 def canDeleteHeadline(self): 

3507 c, p = self, self.p 

3508 if c.hoistStack: 

3509 bunch = c.hoistStack[0] 

3510 if p == bunch.p: 

3511 return False 

3512 return p.hasParent() or p.hasThreadBack() or p.hasNext() 

3513 

3514 canCutOutline = canDeleteHeadline 

3515 #@+node:ekr.20031218072017.2961: *6* c.canDemote 

3516 def canDemote(self) -> bool: 

3517 c = self 

3518 return c.p.hasNext() 

3519 #@+node:ekr.20031218072017.2962: *6* c.canExpandAllHeadlines 

3520 def canExpandAllHeadlines(self): 

3521 """Return True if the Expand All Nodes menu item should be enabled.""" 

3522 c = self 

3523 for p in c.all_positions(): # was c.all_unique_positions() 

3524 if not p.isExpanded(): 

3525 return True 

3526 return False 

3527 #@+node:ekr.20031218072017.2963: *6* c.canExpandAllSubheads 

3528 def canExpandAllSubheads(self): 

3529 c = self 

3530 for p in c.p.subtree(): 

3531 if not p.isExpanded(): 

3532 return True 

3533 return False 

3534 #@+node:ekr.20031218072017.2964: *6* c.canExpandSubheads 

3535 def canExpandSubheads(self): 

3536 current = self.p 

3537 for p in current.children(): 

3538 if p != current and not p.isExpanded(): 

3539 return True 

3540 return False 

3541 #@+node:ekr.20031218072017.2287: *6* c.canExtract, canExtractSection & canExtractSectionNames 

3542 def canExtract(self) -> bool: 

3543 c = self 

3544 w = c.frame.body.wrapper 

3545 return w and w.hasSelection() 

3546 

3547 canExtractSectionNames = canExtract 

3548 

3549 def canExtractSection(self): 

3550 c = self 

3551 w = c.frame.body.wrapper 

3552 if not w: 

3553 return False 

3554 s = w.getSelectedText() 

3555 if not s: 

3556 return False 

3557 line = g.get_line(s, 0) 

3558 i1 = line.find("<<") 

3559 j1 = line.find(">>") 

3560 i2 = line.find("@<") 

3561 j2 = line.find("@>") 

3562 return -1 < i1 < j1 or -1 < i2 < j2 

3563 #@+node:ekr.20031218072017.2965: *6* c.canFindMatchingBracket 

3564 #@@nobeautify 

3565 

3566 def canFindMatchingBracket(self): 

3567 c = self 

3568 brackets = "()[]{}" 

3569 w = c.frame.body.wrapper 

3570 s = w.getAllText() 

3571 ins = w.getInsertPoint() 

3572 c1 = s[ins] if 0 <= ins < len(s) else '' 

3573 c2 = s[ins-1] if 0 <= ins-1 < len(s) else '' 

3574 val = (c1 and c1 in brackets) or (c2 and c2 in brackets) 

3575 return bool(val) 

3576 #@+node:ekr.20040303165342: *6* c.canHoist & canDehoist 

3577 def canDehoist(self): 

3578 """ 

3579 Return True if do-hoist should be enabled in a menu. 

3580 Should not be used in any other context. 

3581 """ 

3582 c = self 

3583 return bool(c.hoistStack) 

3584 

3585 def canHoist(self): 

3586 # This is called at idle time, so minimizing positions is crucial! 

3587 """ 

3588 Return True if hoist should be enabled in a menu. 

3589 Should not be used in any other context. 

3590 """ 

3591 return True 

3592 #@+node:ekr.20031218072017.2970: *6* c.canMoveOutlineDown 

3593 def canMoveOutlineDown(self) -> bool: 

3594 c, p = self, self.p 

3595 return p and p.visNext(c) 

3596 #@+node:ekr.20031218072017.2971: *6* c.canMoveOutlineLeft 

3597 def canMoveOutlineLeft(self) -> bool: 

3598 c, p = self, self.p 

3599 if c.hoistStack: 

3600 bunch = c.hoistStack[-1] 

3601 if p and p.hasParent(): 

3602 p.moveToParent() 

3603 return p != bunch.p and bunch.p.isAncestorOf(p) 

3604 return False 

3605 return p and p.hasParent() 

3606 #@+node:ekr.20031218072017.2972: *6* c.canMoveOutlineRight 

3607 def canMoveOutlineRight(self) -> bool: 

3608 c, p = self, self.p 

3609 if c.hoistStack: 

3610 bunch = c.hoistStack[-1] 

3611 return p and p.hasBack() and p != bunch.p 

3612 return p and p.hasBack() 

3613 #@+node:ekr.20031218072017.2973: *6* c.canMoveOutlineUp 

3614 def canMoveOutlineUp(self): 

3615 c, current = self, self.p 

3616 visBack = current and current.visBack(c) 

3617 if not visBack: 

3618 return False 

3619 if visBack.visBack(c): 

3620 return True 

3621 if c.hoistStack: 

3622 limit, limitIsVisible = c.visLimit() 

3623 if limitIsVisible: # A hoist 

3624 return current != limit 

3625 # A chapter. 

3626 return current != limit.firstChild() 

3627 return current != c.rootPosition() 

3628 #@+node:ekr.20031218072017.2974: *6* c.canPasteOutline 

3629 def canPasteOutline(self, s=None): 

3630 # c = self 

3631 if not s: 

3632 s = g.app.gui.getTextFromClipboard() 

3633 if s and g.match(s, 0, g.app.prolog_prefix_string): 

3634 return True 

3635 return False 

3636 #@+node:ekr.20031218072017.2975: *6* c.canPromote 

3637 def canPromote(self) -> bool: 

3638 p = self.p 

3639 return p and p.hasChildren() 

3640 #@+node:ekr.20031218072017.2977: *6* c.canSelect.... 

3641 def canSelectThreadBack(self): 

3642 p = self.p 

3643 return p.hasThreadBack() 

3644 

3645 def canSelectThreadNext(self): 

3646 p = self.p 

3647 return p.hasThreadNext() 

3648 

3649 def canSelectVisBack(self): 

3650 c, p = self, self.p 

3651 return p.visBack(c) 

3652 

3653 def canSelectVisNext(self): 

3654 c, p = self, self.p 

3655 return p.visNext(c) 

3656 #@+node:ekr.20031218072017.2978: *6* c.canShiftBodyLeft/Right 

3657 def canShiftBodyLeft(self) -> bool: 

3658 c = self 

3659 w = c.frame.body.wrapper 

3660 return w and w.getAllText() 

3661 

3662 canShiftBodyRight = canShiftBodyLeft 

3663 #@+node:ekr.20031218072017.2979: *6* c.canSortChildren, canSortSiblings 

3664 def canSortChildren(self) -> bool: 

3665 p = self.p 

3666 return p and p.hasChildren() 

3667 

3668 def canSortSiblings(self) -> bool: 

3669 p = self.p 

3670 return p and (p.hasNext() or p.hasBack()) 

3671 #@+node:ekr.20031218072017.2980: *6* c.canUndo & canRedo 

3672 def canUndo(self) -> bool: 

3673 c = self 

3674 return c.undoer.canUndo() 

3675 

3676 def canRedo(self) -> bool: 

3677 c = self 

3678 return c.undoer.canRedo() 

3679 #@+node:ekr.20031218072017.2981: *6* c.canUnmarkAll 

3680 def canUnmarkAll(self): 

3681 c = self 

3682 for p in c.all_unique_positions(): 

3683 if p.isMarked(): 

3684 return True 

3685 return False 

3686 #@+node:ekr.20040323172420: *6* Slow routines: no longer used 

3687 #@+node:ekr.20031218072017.2966: *7* c.canGoToNextDirtyHeadline (slow) 

3688 def canGoToNextDirtyHeadline(self): 

3689 c, current = self, self.p 

3690 for p in c.all_unique_positions(): 

3691 if p != current and p.isDirty(): 

3692 return True 

3693 return False 

3694 #@+node:ekr.20031218072017.2967: *7* c.canGoToNextMarkedHeadline (slow) 

3695 def canGoToNextMarkedHeadline(self): 

3696 c, current = self, self.p 

3697 for p in c.all_unique_positions(): 

3698 if p != current and p.isMarked(): 

3699 return True 

3700 return False 

3701 #@+node:ekr.20031218072017.2968: *7* c.canMarkChangedHeadline (slow) 

3702 def canMarkChangedHeadlines(self): 

3703 c = self 

3704 for p in c.all_unique_positions(): 

3705 if p.isDirty(): 

3706 return True 

3707 return False 

3708 #@+node:ekr.20031218072017.2969: *7* c.canMarkChangedRoots 

3709 def canMarkChangedRoots(self): 

3710 c = self 

3711 for p in c.all_unique_positions(): 

3712 if p.isDirty() and p.isAnyAtFileNode(): 

3713 return True 

3714 return False 

3715 #@+node:ekr.20031218072017.2990: *4* c.Selecting 

3716 #@+node:ekr.20031218072017.2992: *5* c.endEditing 

3717 def endEditing(self): 

3718 """End the editing of a headline.""" 

3719 c = self 

3720 p = c.p 

3721 if p: 

3722 c.frame.tree.endEditLabel() 

3723 #@+node:ville.20090525205736.12325: *5* c.getSelectedPositions 

3724 def getSelectedPositions(self): 

3725 """ Get list (PosList) of currently selected positions 

3726 

3727 So far only makes sense on qt gui (which supports multiselection) 

3728 """ 

3729 c = self 

3730 return c.frame.tree.getSelectedPositions() 

3731 #@+node:ekr.20031218072017.2991: *5* c.redrawAndEdit 

3732 def redrawAndEdit(self, p, selectAll=False, selection=None, keepMinibuffer=False): 

3733 """Redraw the screen and edit p's headline.""" 

3734 c, k = self, self.k 

3735 c.redraw(p) # This *must* be done now. 

3736 if p: 

3737 # This should request focus. 

3738 c.frame.tree.editLabel(p, selectAll=selectAll, selection=selection) 

3739 if k and not keepMinibuffer: 

3740 # Setting the input state has no effect on focus. 

3741 if selectAll: 

3742 k.setInputState('insert') 

3743 else: 

3744 k.setDefaultInputState() 

3745 # This *does* affect focus. 

3746 k.showStateAndMode() 

3747 else: 

3748 g.trace('** no p') 

3749 # Update the focus immediately. 

3750 if not keepMinibuffer: 

3751 c.outerUpdate() 

3752 #@+node:ekr.20031218072017.2997: *5* c.selectPosition (trace of unexpected de-hoists) 

3753 def selectPosition(self, p, **kwargs): 

3754 """ 

3755 Select a new position, redrawing the screen *only* if we must 

3756 change chapters. 

3757 """ 

3758 trace = False # For # 2167. 

3759 if kwargs: 

3760 print('c.selectPosition: all keyword args are ignored', g.callers()) 

3761 c = self 

3762 cc = c.chapterController 

3763 if not p: 

3764 if not g.app.batchMode: # A serious error. 

3765 g.trace('Warning: no p', g.callers()) 

3766 return 

3767 if cc and not cc.selectChapterLockout: 

3768 cc.selectChapterForPosition(p) 

3769 # Calls c.redraw only if the chapter changes. 

3770 # De-hoist as necessary to make p visible. 

3771 if c.hoistStack: 

3772 while c.hoistStack: 

3773 bunch = c.hoistStack[-1] 

3774 if c.positionExists(p, bunch.p): 

3775 break 

3776 if trace: 

3777 # #2167: Give detailed trace. 

3778 print('') 

3779 print('pop hoist stack! callers:', g.callers()) 

3780 g.printObj(c.hoistStack, tag='c.hoistStack before pop') 

3781 print('Recent keystrokes') 

3782 for i, data in enumerate(reversed(g.app.lossage)): 

3783 print(f"{i:>2} {data!r}") 

3784 print('Recently-executed commands...') 

3785 for i, command in enumerate(reversed(c.recent_commands_list)): 

3786 print(f"{i:>2} {command}") 

3787 c.hoistStack.pop() 

3788 c.frame.tree.select(p) 

3789 c.setCurrentPosition(p) 

3790 # Do *not* test whether the position exists! 

3791 # We may be in the midst of an undo. 

3792 

3793 # Compatibility, but confusing. 

3794 

3795 selectVnode = selectPosition 

3796 #@+node:ekr.20080503055349.1: *5* c.setPositionAfterSort 

3797 def setPositionAfterSort(self, sortChildren): 

3798 """ 

3799 Return the position to be selected after a sort. 

3800 """ 

3801 c = self 

3802 p = c.p 

3803 p_v = p.v 

3804 parent = p.parent() 

3805 parent_v = p._parentVnode() 

3806 if sortChildren: 

3807 return parent or c.rootPosition() 

3808 if parent: 

3809 p = parent.firstChild() 

3810 else: 

3811 p = leoNodes.Position(parent_v.children[0]) 

3812 while p and p.v != p_v: 

3813 p.moveToNext() 

3814 p = p or parent 

3815 return p 

3816 #@+node:ekr.20070226113916: *5* c.treeSelectHelper 

3817 def treeSelectHelper(self, p): 

3818 c = self 

3819 if not p: 

3820 p = c.p 

3821 if p: 

3822 # Do not call expandAllAncestors here. 

3823 c.selectPosition(p) 

3824 c.redraw_after_select(p) 

3825 c.treeFocusHelper() 

3826 # This is essential. 

3827 #@+node:ekr.20130823083943.12559: *3* c.recursiveImport 

3828 def recursiveImport(self, dir_, kind, 

3829 add_context=None, # Override setting only if True/False 

3830 add_file_context=None, # Override setting only if True/False 

3831 add_path=True, 

3832 recursive=True, 

3833 safe_at_file=True, 

3834 theTypes=None, 

3835 # force_at_others=False, # tag:no-longer-used 

3836 ignore_pattern=None, 

3837 verbose=True, # legacy value. 

3838 ): 

3839 #@+<< docstring >> 

3840 #@+node:ekr.20130823083943.12614: *4* << docstring >> 

3841 """ 

3842 Recursively import all python files in a directory and clean the results. 

3843 

3844 Parameters:: 

3845 dir_ The root directory or file to import. 

3846 kind One of ('@clean','@edit','@file','@nosent'). 

3847 add_path=True True: add a full @path directive to @<file> nodes. 

3848 recursive=True True: recurse into subdirectories. 

3849 safe_at_file=True True: produce @@file nodes instead of @file nodes. 

3850 theTypes=None A list of file extensions to import. 

3851 None is equivalent to ['.py'] 

3852 

3853 This method cleans imported files as follows: 

3854 

3855 - Replace backslashes with forward slashes in headlines. 

3856 - Remove empty nodes. 

3857 - Add @path directives that reduce the needed path specifiers in descendant nodes. 

3858 - Add @file to nodes or replace @file with @@file. 

3859 """ 

3860 #@-<< docstring >> 

3861 c = self 

3862 if g.os_path_exists(dir_): 

3863 # Import all files in dir_ after c.p. 

3864 try: 

3865 from leo.core import leoImport 

3866 cc = leoImport.RecursiveImportController(c, kind, 

3867 add_context=add_context, 

3868 add_file_context=add_file_context, 

3869 add_path=add_path, 

3870 ignore_pattern=ignore_pattern, 

3871 recursive=recursive, 

3872 safe_at_file=safe_at_file, 

3873 theTypes=['.py'] if not theTypes else theTypes, 

3874 verbose=verbose, 

3875 ) 

3876 cc.run(dir_) 

3877 finally: 

3878 c.redraw() 

3879 else: 

3880 g.es_print(f"Does not exist: {dir_}") 

3881 #@+node:ekr.20171124084149.1: *3* c.Scripting utils 

3882 #@+node:ekr.20160201072634.1: *4* c.cloneFindByPredicate 

3883 def cloneFindByPredicate(self, 

3884 generator, # The generator used to traverse the tree. 

3885 predicate, # A function of one argument p, returning True 

3886 # if p should be included in the results. 

3887 failMsg=None, # Failure message. Default is no message. 

3888 flatten=False, # True: Put all matches at the top level. 

3889 iconPath=None, # Full path to icon to attach to all matches. 

3890 undoType=None, # The undo name, shown in the Edit:Undo menu. 

3891 # The default is 'clone-find-predicate' 

3892 ): 

3893 """ 

3894 Traverse the tree given using the generator, cloning all positions for 

3895 which predicate(p) is True. Undoably move all clones to a new node, created 

3896 as the last top-level node. Returns the newly-created node. Arguments: 

3897 

3898 generator, The generator used to traverse the tree. 

3899 predicate, A function of one argument p returning true if p should be included. 

3900 failMsg=None, Message given if nothing found. Default is no message. 

3901 flatten=False, True: Move all node to be parents of the root node. 

3902 iconPath=None, Full path to icon to attach to all matches. 

3903 undo_type=None, The undo/redo name shown in the Edit:Undo menu. 

3904 The default is 'clone-find-predicate' 

3905 """ 

3906 c = self 

3907 u, undoType = c.undoer, undoType or 'clone-find-predicate' 

3908 clones, root, seen = [], None, set() 

3909 for p in generator(): 

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

3911 c.setCloneFindByPredicateIcon(iconPath, p) 

3912 if flatten: 

3913 seen.add(p.v) 

3914 else: 

3915 for p2 in p.self_and_subtree(copy=False): 

3916 seen.add(p2.v) 

3917 clones.append(p.copy()) 

3918 if clones: 

3919 undoData = u.beforeInsertNode(c.p) 

3920 root = c.createCloneFindPredicateRoot(flatten, undoType) 

3921 for p in clones: 

3922 # Create the clone directly as a child of found. 

3923 p2 = p.copy() 

3924 n = root.numberOfChildren() 

3925 p2._linkCopiedAsNthChild(root, n) 

3926 u.afterInsertNode(root, undoType, undoData) 

3927 c.selectPosition(root) 

3928 c.setChanged() 

3929 c.contractAllHeadlines() 

3930 root.expand() 

3931 elif failMsg: 

3932 g.es(failMsg, color='red') 

3933 return root 

3934 #@+node:ekr.20160304054950.1: *5* c.setCloneFindByPredicateIcon 

3935 def setCloneFindByPredicateIcon(self, iconPath, p): 

3936 """Attach an icon to p.v.u.""" 

3937 if iconPath and g.os_path_exists(iconPath) and not g.os_path_isdir(iconPath): 

3938 aList = p.v.u.get('icons', []) 

3939 for d in aList: 

3940 if d.get('file') == iconPath: 

3941 break 

3942 else: 

3943 aList.append({ 

3944 'type': 'file', 

3945 'file': iconPath, 

3946 'on': 'VNode', 

3947 # 'relPath': iconPath, 

3948 'where': 'beforeHeadline', 

3949 'xoffset': 2, 'xpad': 1, 

3950 'yoffset': 0, 

3951 

3952 }) 

3953 p.v.u['icons'] = aList 

3954 elif iconPath: 

3955 g.trace('bad icon path', iconPath) 

3956 #@+node:ekr.20160201075438.1: *5* c.createCloneFindPredicateRoot 

3957 def createCloneFindPredicateRoot(self, flatten, undoType): 

3958 """Create a root node for clone-find-predicate.""" 

3959 c = self 

3960 root = c.lastTopLevel().insertAfter() 

3961 root.h = undoType + (' (flattened)' if flatten else '') 

3962 return root 

3963 #@+node:peckj.20131023115434.10114: *4* c.createNodeHierarchy 

3964 def createNodeHierarchy(self, heads, parent=None, forcecreate=False): 

3965 """ Create the proper hierarchy of nodes with headlines defined in 

3966 'heads' under 'parent' 

3967 

3968 params: 

3969 parent - parent node to start from. Set to None for top-level nodes 

3970 heads - list of headlines in order to create, i.e. ['foo','bar','baz'] 

3971 will create: 

3972 parent 

3973 -foo 

3974 --bar 

3975 ---baz 

3976 forcecreate - If False (default), will not create nodes unless they don't exist 

3977 If True, will create nodes regardless of existing nodes 

3978 returns the final position ('baz' in the above example) 

3979 """ 

3980 u = self.undoer 

3981 undoType = 'Create Node Hierarchy' 

3982 undoType2 = 'Insert Node In Hierarchy' 

3983 u_node = parent or self.rootPosition() 

3984 undoData = u.beforeChangeGroup(u_node, undoType) 

3985 changed_node = False 

3986 for idx, head in enumerate(heads): 

3987 if parent is None and idx == 0: # if parent = None, create top level node for first head 

3988 if not forcecreate: 

3989 for pos in self.all_positions(): 

3990 if pos.h == head: 

3991 parent = pos 

3992 break 

3993 if parent is None or forcecreate: 

3994 u_d = u.beforeInsertNode(u_node) 

3995 n = self.rootPosition().insertAfter() 

3996 n.h = head 

3997 u.afterInsertNode(n, undoType2, u_d) 

3998 parent = n 

3999 else: # else, simply create child nodes each round 

4000 if not forcecreate: 

4001 for ch in parent.children(): 

4002 if ch.h == head: 

4003 parent = ch 

4004 changed_node = True 

4005 break 

4006 if parent.h != head or not changed_node or forcecreate: 

4007 u_d = u.beforeInsertNode(parent) 

4008 n = parent.insertAsLastChild() 

4009 n.h = head 

4010 u.afterInsertNode(n, undoType2, u_d) 

4011 parent = n 

4012 changed_node = False 

4013 u.afterChangeGroup(parent, undoType, undoData) 

4014 return parent # actually the last created/found position 

4015 #@+node:ekr.20100802121531.5804: *4* c.deletePositionsInList 

4016 def deletePositionsInList(self, aList): 

4017 """ 

4018 Delete all vnodes corresponding to the positions in aList. 

4019  

4020 Set c.p if the old position no longer exists. 

4021 

4022 See "Theory of operation of c.deletePositionsInList" in LeoDocs.leo. 

4023 """ 

4024 # New implementation by Vitalije 2020-03-17 17:29 

4025 c = self 

4026 # Ensure all positions are valid. 

4027 aList = [p for p in aList if c.positionExists(p)] 

4028 if not aList: 

4029 return [] 

4030 

4031 def p2link(p): 

4032 parent_v = p.stack[-1][0] if p.stack else c.hiddenRootNode 

4033 return p._childIndex, parent_v 

4034 

4035 links_to_be_cut = sorted(set(map(p2link, aList)), key=lambda x: -x[0]) 

4036 undodata = [] 

4037 for i, v in links_to_be_cut: 

4038 ch = v.children.pop(i) 

4039 ch.parents.remove(v) 

4040 undodata.append((v.gnx, i, ch.gnx)) 

4041 if not c.positionExists(c.p): 

4042 c.selectPosition(c.rootPosition()) 

4043 return undodata 

4044 

4045 #@+node:vitalije.20200318161844.1: *4* c.undoableDeletePositions 

4046 def undoableDeletePositions(self, aList): 

4047 """ 

4048 Deletes all vnodes corresponding to the positions in aList, 

4049 and make changes undoable. 

4050 """ 

4051 c = self 

4052 u = c.undoer 

4053 data = c.deletePositionsInList(aList) 

4054 gnx2v = c.fileCommands.gnxDict 

4055 def undo(): 

4056 for pgnx, i, chgnx in reversed(u.getBead(u.bead).data): 

4057 v = gnx2v[pgnx] 

4058 ch = gnx2v[chgnx] 

4059 v.children.insert(i, ch) 

4060 ch.parents.append(v) 

4061 if not c.positionExists(c.p): 

4062 c.setCurrentPosition(c.rootPosition()) 

4063 def redo(): 

4064 for pgnx, i, chgnx in u.getBead(u.bead + 1).data: 

4065 v = gnx2v[pgnx] 

4066 ch = v.children.pop(i) 

4067 ch.parents.remove(v) 

4068 if not c.positionExists(c.p): 

4069 c.setCurrentPosition(c.rootPosition()) 

4070 u.pushBead(g.Bunch( 

4071 data=data, 

4072 undoType='delete nodes', 

4073 undoHelper=undo, 

4074 redoHelper=redo, 

4075 )) 

4076 #@+node:ekr.20091211111443.6265: *4* c.doBatchOperations & helpers 

4077 def doBatchOperations(self, aList=None): 

4078 # Validate aList and create the parents dict 

4079 if aList is None: 

4080 aList = [] 

4081 ok, d = self.checkBatchOperationsList(aList) 

4082 if not ok: 

4083 g.error('do-batch-operations: invalid list argument') 

4084 return 

4085 for v in list(d.keys()): 

4086 aList2 = d.get(v, []) 

4087 if aList2: 

4088 aList.sort() 

4089 #@+node:ekr.20091211111443.6266: *5* c.checkBatchOperationsList 

4090 def checkBatchOperationsList(self, aList): 

4091 ok = True 

4092 d: Dict["leoNodes.VNode", List[Any]] = {} 

4093 for z in aList: 

4094 try: 

4095 op, p, n = z 

4096 ok = (op in ('insert', 'delete') and 

4097 isinstance(p, leoNodes.position) and isinstance(n, int)) 

4098 if ok: 

4099 aList2 = d.get(p.v, []) 

4100 data = n, op 

4101 aList2.append(data) 

4102 d[p.v] = aList2 

4103 except ValueError: 

4104 ok = False 

4105 if not ok: 

4106 break 

4107 return ok, d 

4108 #@+node:ekr.20091002083910.6106: *4* c.find_b & find_h (PosList) 

4109 #@+<< PosList doc >> 

4110 #@+node:bob.20101215134608.5898: *5* << PosList doc >> 

4111 #@@language rest 

4112 #@+at 

4113 # List of positions 

4114 # 

4115 # Functions find_h() and find_b() both return an instance of PosList. 

4116 # 

4117 # Methods filter_h() and filter_b() refine a PosList. 

4118 # 

4119 # Method children() generates a new PosList by descending one level from 

4120 # all the nodes in a PosList. 

4121 # 

4122 # A chain of PosList method calls must begin with find_h() or find_b(). 

4123 # The rest of the chain can be any combination of filter_h(), 

4124 # filter_b(), and children(). For example: 

4125 # 

4126 # pl = c.find_h('@file.*py').children().filter_h('class.*').filter_b('import (.*)') 

4127 # 

4128 # For each position, pos, in the PosList returned, find_h() and 

4129 # filter_h() set attribute pos.mo to the match object (see Python 

4130 # Regular Expression documentation) for the pattern match. 

4131 # 

4132 # Caution: The pattern given to find_h() or filter_h() must match zero 

4133 # or more characters at the beginning of the headline. 

4134 # 

4135 # For each position, pos, the postlist returned, find_b() and filter_b() 

4136 # set attribute pos.matchiter to an iterator that will return a match 

4137 # object for each of the non-overlapping matches of the pattern in the 

4138 # body of the node. 

4139 #@-<< PosList doc >> 

4140 #@+node:ville.20090311190405.70: *5* c.find_h 

4141 def find_h(self, regex, flags=re.IGNORECASE): 

4142 """ Return list (a PosList) of all nodes where zero or more characters at 

4143 the beginning of the headline match regex 

4144 """ 

4145 c = self 

4146 pat = re.compile(regex, flags) 

4147 res = leoNodes.PosList() 

4148 for p in c.all_positions(): 

4149 m = re.match(pat, p.h) 

4150 if m: 

4151 pc = p.copy() 

4152 pc.mo = m 

4153 res.append(pc) 

4154 return res 

4155 #@+node:ville.20090311200059.1: *5* c.find_b 

4156 def find_b(self, regex, flags=re.IGNORECASE | re.MULTILINE): 

4157 """ Return list (a PosList) of all nodes whose body matches regex 

4158 one or more times. 

4159 

4160 """ 

4161 c = self 

4162 pat = re.compile(regex, flags) 

4163 res = leoNodes.PosList() 

4164 for p in c.all_positions(): 

4165 m = re.finditer(pat, p.b) 

4166 t1, t2 = itertools.tee(m, 2) 

4167 try: 

4168 t1.__next__() 

4169 except StopIteration: 

4170 continue 

4171 pc = p.copy() 

4172 pc.matchiter = t2 

4173 res.append(pc) 

4174 return res 

4175 #@+node:ekr.20171124155725.1: *3* c.Settings 

4176 #@+node:ekr.20171114114908.1: *4* c.registerReloadSettings 

4177 def registerReloadSettings(self, obj): 

4178 """Enter object into c.configurables.""" 

4179 c = self 

4180 if obj not in c.configurables: 

4181 c.configurables.append(obj) 

4182 #@+node:ekr.20170221040621.1: *4* c.reloadConfigurableSettings 

4183 def reloadConfigurableSettings(self): 

4184 """ 

4185 Call all reloadSettings method in c.subcommanders, c.configurables and 

4186 other known classes. 

4187 """ 

4188 c = self 

4189 table = [ 

4190 g.app.gui, 

4191 g.app.pluginsController, 

4192 c.k.autoCompleter, 

4193 c.frame, c.frame.body, c.frame.log, c.frame.tree, 

4194 c.frame.body.colorizer, 

4195 getattr(c.frame.body.colorizer, 'highlighter', None), 

4196 ] 

4197 for obj in table: 

4198 if obj: 

4199 c.registerReloadSettings(obj) 

4200 c.configurables = list(set(c.configurables)) 

4201 # Useful now that instances add themselves to c.configurables. 

4202 c.configurables.sort(key=lambda obj: obj.__class__.__name__.lower()) 

4203 for obj in c.configurables: 

4204 func = getattr(obj, 'reloadSettings', None) 

4205 if func: 

4206 # pylint: disable=not-callable 

4207 try: 

4208 func() 

4209 except Exception: 

4210 g.es_exception() 

4211 c.configurables.remove(obj) 

4212 #@-others 

4213#@-others 

4214#@@language python 

4215#@@tabwidth -4 

4216#@@pagewidth 70 

4217#@-leo