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.20171124080430.1: * @file ../commands/commanderOutlineCommands.py 

4#@@first 

5"""Outline commands that used to be defined in leoCommands.py""" 

6import xml.etree.ElementTree as ElementTree 

7from leo.core import leoGlobals as g 

8from leo.core import leoNodes 

9from leo.core import leoFileCommands 

10#@+others 

11#@+node:ekr.20031218072017.1548: ** c_oc.Cut & Paste Outlines 

12#@+node:ekr.20031218072017.1550: *3* c_oc.copyOutline 

13@g.commander_command('copy-node') 

14def copyOutline(self, event=None): 

15 """Copy the selected outline to the clipboard.""" 

16 # Copying an outline has no undo consequences. 

17 c = self 

18 c.endEditing() 

19 s = c.fileCommands.outline_to_clipboard_string() 

20 g.app.paste_c = c 

21 g.app.gui.replaceClipboardWith(s) 

22#@+node:ekr.20031218072017.1549: *3* c_oc.cutOutline 

23@g.commander_command('cut-node') 

24def cutOutline(self, event=None): 

25 """Delete the selected outline and send it to the clipboard.""" 

26 c = self 

27 if c.canDeleteHeadline(): 

28 c.copyOutline() 

29 c.deleteOutline(op_name="Cut Node") 

30 c.recolor() 

31#@+node:ekr.20031218072017.1551: *3* c_oc.pasteOutline 

32@g.commander_command('paste-node') 

33def pasteOutline(self, event=None, s=None, undoFlag=True): 

34 """ 

35 Paste an outline into the present outline from the clipboard. 

36 Nodes do *not* retain their original identify. 

37 """ 

38 c = self 

39 if s is None: 

40 s = g.app.gui.getTextFromClipboard() 

41 c.endEditing() 

42 if not s or not c.canPasteOutline(s): 

43 return None # This should never happen. 

44 isLeo = g.match(s, 0, g.app.prolog_prefix_string) 

45 if not isLeo: 

46 return None 

47 # Get *position* to be pasted. 

48 pasted = c.fileCommands.getLeoOutlineFromClipboard(s) 

49 if not pasted: 

50 # Leo no longer supports MORE outlines. Use import-MORE-files instead. 

51 return None 

52 # Validate. 

53 c.validateOutline() 

54 c.checkOutline() 

55 # Handle the "before" data for undo. 

56 if undoFlag: 

57 undoData = c.undoer.beforeInsertNode(c.p, 

58 pasteAsClone=False, 

59 copiedBunchList=[], 

60 ) 

61 # Paste the node into the outline. 

62 c.selectPosition(pasted) 

63 pasted.setDirty() 

64 c.setChanged() 

65 back = pasted.back() 

66 if back and back.hasChildren() and back.isExpanded(): 

67 pasted.moveToNthChildOf(back, 0) 

68 # Finish the command. 

69 if undoFlag: 

70 c.undoer.afterInsertNode(pasted, 'Paste Node', undoData) 

71 c.redraw(pasted) 

72 c.recolor() 

73 return pasted 

74#@+node:EKR.20040610130943: *3* c_oc.pasteOutlineRetainingClones & helpers 

75@g.commander_command('paste-retaining-clones') 

76def pasteOutlineRetainingClones(self, event=None, s=None, undoFlag=True): 

77 """ 

78 Paste an outline into the present outline from the clipboard. 

79 Nodes *retain* their original identify. 

80 """ 

81 c = self 

82 if s is None: 

83 s = g.app.gui.getTextFromClipboard() 

84 c.endEditing() 

85 if not s or not c.canPasteOutline(s): 

86 return None # This should never happen. 

87 isLeo = g.match(s, 0, g.app.prolog_prefix_string) 

88 if not isLeo: 

89 return None 

90 # Get *position* to be pasted. 

91 pasted = c.fileCommands.getLeoOutlineFromClipboardRetainingClones(s) 

92 if not pasted: 

93 # Leo no longer supports MORE outlines. Use import-MORE-files instead. 

94 return None 

95 # Validate. 

96 c.validateOutline() 

97 c.checkOutline() 

98 # Handle the "before" data for undo. 

99 if undoFlag: 

100 vnodeInfoDict = computeVnodeInfoDict(c) 

101 undoData = c.undoer.beforeInsertNode(c.p, 

102 pasteAsClone=True, 

103 copiedBunchList=computeCopiedBunchList(c, pasted, vnodeInfoDict), 

104 ) 

105 # Paste the node into the outline. 

106 c.selectPosition(pasted) 

107 pasted.setDirty() 

108 c.setChanged() 

109 back = pasted.back() 

110 if back and back.hasChildren() and back.isExpanded(): 

111 pasted.moveToNthChildOf(back, 0) 

112 pasted.setDirty() 

113 # Set dirty bits for ancestors of *all* pasted nodes. 

114 for p in pasted.self_and_subtree(): 

115 p.setAllAncestorAtFileNodesDirty() 

116 # Finish the command. 

117 if undoFlag: 

118 c.undoer.afterInsertNode(pasted, 'Paste As Clone', undoData) 

119 c.redraw(pasted) 

120 c.recolor() 

121 return pasted 

122#@+node:ekr.20050418084539.2: *4* def computeCopiedBunchList 

123def computeCopiedBunchList(c, pasted, vnodeInfoDict): 

124 """Create a dict containing only copied vnodes.""" 

125 d = {} 

126 for p in pasted.self_and_subtree(copy=False): 

127 d[p.v] = p.v 

128 aList = [] 

129 for v in vnodeInfoDict: 

130 if d.get(v): 

131 bunch = vnodeInfoDict.get(v) 

132 aList.append(bunch) 

133 return aList 

134#@+node:ekr.20050418084539: *4* def computeVnodeInfoDict 

135def computeVnodeInfoDict(c): 

136 """ 

137 We don't know yet which nodes will be affected by the paste, so we remember 

138 everything. This is expensive, but foolproof. 

139 

140 The alternative is to try to remember the 'before' values of nodes in the 

141 FileCommands read logic. Several experiments failed, and the code is very ugly. 

142 In short, it seems wise to do things the foolproof way. 

143 """ 

144 d = {} 

145 for v in c.all_unique_nodes(): 

146 if v not in d: 

147 d[v] = g.Bunch(v=v, head=v.h, body=v.b) 

148 return d 

149#@+node:vitalije.20200529105105.1: *3* c_oc.pasteAsTemplate 

150@g.commander_command('paste-as-template') 

151def pasteAsTemplate(self, event=None): 

152 c = self 

153 p = c.p 

154 #@+others 

155 #@+node:vitalije.20200529112224.1: *4* skip_root 

156 def skip_root(v): 

157 """ 

158 generates v nodes in the outline order 

159 but skips a subtree of the node with root_gnx 

160 """ 

161 if v.gnx != root_gnx: 

162 yield v 

163 for ch in v.children: 

164 yield from skip_root(ch) 

165 #@+node:vitalije.20200529112459.1: *4* translate_gnx 

166 def translate_gnx(gnx): 

167 """ 

168 allocates a new gnx for all nodes that 

169 are not found outside copied tree 

170 """ 

171 if gnx in outside: 

172 return gnx 

173 return g.app.nodeIndices.computeNewIndex() 

174 #@+node:vitalije.20200529115141.1: *4* viter 

175 def viter(parent_gnx, xv): 

176 """ 

177 iterates <v> nodes generating tuples: 

178 

179 (parent_gnx, child_gnx, headline, body) 

180 

181 skipping the descendants of already seen nodes. 

182 """ 

183 chgnx = xv.attrib.get('t') 

184 b = bodies[chgnx] 

185 gnx = translation.get(chgnx) 

186 if gnx in seen: 

187 yield parent_gnx, gnx, heads.get(gnx), b 

188 else: 

189 seen.add(gnx) 

190 h = xv[0].text 

191 heads[gnx] = h 

192 yield parent_gnx, gnx, h, b 

193 for xch in xv[1:]: 

194 yield from viter(gnx, xch) 

195 #@+node:vitalije.20200529114857.1: *4* getv 

196 gnx2v = c.fileCommands.gnxDict 

197 def getv(gnx): 

198 """ 

199 returns a pair (vnode, is_new) for the given gnx. 

200 if node doesn't exist, creates a new one. 

201 """ 

202 v = gnx2v.get(gnx) 

203 if v is None: 

204 return leoNodes.VNode(c, gnx), True 

205 return v, False 

206 #@+node:vitalije.20200529115539.1: *4* do_paste 

207 def do_paste(vpar, index): 

208 """ 

209 pastes a new node as a child of vpar at given index 

210 """ 

211 vpargnx = vpar.gnx 

212 # the first node is inserted at the given index 

213 # and the rest are just appended at parents children 

214 # to achieve this we first create a generator object 

215 rows = viter(vpargnx, xvelements[0]) 

216 

217 # then we just take first tuple 

218 pgnx, gnx, h, b = next(rows) 

219 

220 # create vnode 

221 v, _ = getv(gnx) 

222 v.h = h 

223 v.b = b 

224 

225 # and finally insert it at the given index 

226 vpar.children.insert(index, v) 

227 v.parents.append(vpar) 

228 

229 pasted = v # remember the first node as a return value 

230 

231 # now we iterate the rest of tuples 

232 for pgnx, gnx, h, b in rows: 

233 

234 # get or create a child `v` 

235 v, isNew = getv(gnx) 

236 if isNew: 

237 v.h = h 

238 v.b = b 

239 ua = uas.get(gnx) 

240 if ua: 

241 v.unknownAttributes = ua 

242 # get parent node `vpar` 

243 vpar = getv(pgnx)[0] 

244 

245 # and link them 

246 vpar.children.append(v) 

247 v.parents.append(vpar) 

248 

249 return pasted 

250 #@+node:vitalije.20200529120440.1: *4* undoHelper 

251 def undoHelper(): 

252 v = vpar.children.pop(index) 

253 v.parents.remove(vpar) 

254 c.redraw(bunch.p) 

255 #@+node:vitalije.20200529120537.1: *4* redoHelper 

256 def redoHelper(): 

257 vpar.children.insert(index, pasted) 

258 pasted.parents.append(vpar) 

259 c.redraw(newp) 

260 #@-others 

261 xroot = ElementTree.fromstring(g.app.gui.getTextFromClipboard()) 

262 xvelements = xroot.find('vnodes') # <v> elements. 

263 xtelements = xroot.find('tnodes') # <t> elements. 

264 

265 bodies, uas = leoFileCommands.FastRead(c, {}).scanTnodes(xtelements) 

266 

267 root_gnx = xvelements[0].attrib.get('t') # the gnx of copied node 

268 outside = {x.gnx for x in skip_root(c.hiddenRootNode)} 

269 # outside will contain gnxes of nodes that are outside the copied tree 

270 

271 translation = {x: translate_gnx(x) for x in bodies} 

272 # we generate new gnx for each node in the copied tree 

273 

274 seen = set(outside) # required for the treatment of local clones inside the copied tree 

275 

276 heads = {} 

277 

278 bunch = c.undoer.createCommonBunch(p) 

279 #@+<< prepare destination data >> 

280 #@+node:vitalije.20200529111500.1: *4* << prepare destination data >> 

281 # destination data consists of 

282 # 1. vpar --- parent v node that should receive pasted child 

283 # 2. index --- at which pasted child will be 

284 # 3. parStack --- a stack for creating new position of the pasted node 

285 # 

286 # the new position will be: Position(vpar.children[index], index, parStack) 

287 # but it can't be calculated yet, before actual paste is done 

288 if p.isExpanded(): 

289 # paste as a first child of current position 

290 vpar = p.v 

291 index = 0 

292 parStack = p.stack + [(p.v, p._childIndex)] 

293 else: 

294 # paste after the current position 

295 parStack = p.stack 

296 vpar = p.stack[-1][0] if p.stack else c.hiddenRootNode 

297 index = p._childIndex + 1 

298 

299 #@-<< prepare destination data >> 

300 

301 pasted = do_paste(vpar, index) 

302 

303 newp = leoNodes.Position(pasted, index, parStack) 

304 

305 bunch.undoHelper = undoHelper 

306 bunch.redoHelper = redoHelper 

307 bunch.undoType = 'paste-retaining-outside-clones' 

308 

309 newp.setDirty() 

310 c.undoer.pushBead(bunch) 

311 c.redraw(newp) 

312#@+node:ekr.20040412060927: ** c_oc.dumpOutline 

313@g.commander_command('dump-outline') 

314def dumpOutline(self, event=None): 

315 """ Dump all nodes in the outline.""" 

316 c = self 

317 seen = {} 

318 print('') 

319 print('=' * 40) 

320 v = c.hiddenRootNode 

321 v.dump() 

322 seen[v] = True 

323 for p in c.all_positions(): 

324 if p.v not in seen: 

325 seen[p.v] = True 

326 p.v.dump() 

327#@+node:ekr.20031218072017.2898: ** c_oc.Expand & contract commands 

328#@+node:ekr.20031218072017.2900: *3* c_oc.contract-all 

329@g.commander_command('contract-all') 

330def contractAllHeadlinesCommand(self, event=None): 

331 """Contract all nodes in the outline.""" 

332 # The helper does all the work. 

333 c = self 

334 c.contractAllHeadlines() 

335 c.redraw() 

336#@+node:ekr.20080819075811.3: *3* c_oc.contractAllOtherNodes & helper 

337@g.commander_command('contract-all-other-nodes') 

338def contractAllOtherNodes(self, event=None): 

339 """ 

340 Contract all nodes except those needed to make the 

341 presently selected node visible. 

342 """ 

343 c = self 

344 leaveOpen = c.p 

345 for p in c.rootPosition().self_and_siblings(): 

346 contractIfNotCurrent(c, p, leaveOpen) 

347 c.redraw() 

348#@+node:ekr.20080819075811.7: *4* def contractIfNotCurrent 

349def contractIfNotCurrent(c, p, leaveOpen): 

350 if p == leaveOpen or not p.isAncestorOf(leaveOpen): 

351 p.contract() 

352 for child in p.children(): 

353 if child != leaveOpen and child.isAncestorOf(leaveOpen): 

354 contractIfNotCurrent(c, child, leaveOpen) 

355 else: 

356 for p2 in child.self_and_subtree(): 

357 p2.contract() 

358#@+node:ekr.20200824130837.1: *3* c_oc.contractAllSubheads (new) 

359@g.commander_command('contract-all-subheads') 

360def contractAllSubheads(self, event=None): 

361 """Contract all children of the presently selected node.""" 

362 c, p = self, self.p 

363 if not p: 

364 return 

365 child = p.firstChild() 

366 c.contractSubtree(p) 

367 while child: 

368 c.contractSubtree(child) 

369 child = child.next() 

370 c.redraw(p) 

371#@+node:ekr.20031218072017.2901: *3* c_oc.contractNode 

372@g.commander_command('contract-node') 

373def contractNode(self, event=None): 

374 """Contract the presently selected node.""" 

375 c = self 

376 p = c.p 

377 c.endEditing() 

378 p.contract() 

379 c.redraw_after_contract(p) 

380 c.selectPosition(p) 

381#@+node:ekr.20040930064232: *3* c_oc.contractNodeOrGoToParent 

382@g.commander_command('contract-or-go-left') 

383def contractNodeOrGoToParent(self, event=None): 

384 """Simulate the left Arrow Key in folder of Windows Explorer.""" 

385 c, cc, p = self, self.chapterController, self.p 

386 parent = p.parent() 

387 redraw = False 

388 # Bug fix: 2016/04/19: test p.v.isExpanded(). 

389 if p.hasChildren() and (p.v.isExpanded() or p.isExpanded()): 

390 c.contractNode() 

391 elif parent and parent.isVisible(c): 

392 # Contract all children first. 

393 if c.collapse_on_lt_arrow: 

394 for child in parent.children(): 

395 if child.isExpanded(): 

396 child.contract() 

397 if child.hasChildren(): 

398 redraw = True 

399 if cc and cc.inChapter and parent.h.startswith('@chapter '): 

400 pass 

401 else: 

402 c.goToParent() 

403 if redraw: 

404 # A *child* should be collapsed. Do a *full* redraw. 

405 c.redraw() 

406#@+node:ekr.20031218072017.2902: *3* c_oc.contractParent 

407@g.commander_command('contract-parent') 

408def contractParent(self, event=None): 

409 """Contract the parent of the presently selected node.""" 

410 c = self 

411 c.endEditing() 

412 p = c.p 

413 parent = p.parent() 

414 if not parent: 

415 return 

416 parent.contract() 

417 c.redraw_after_contract(p=parent) 

418#@+node:ekr.20031218072017.2903: *3* c_oc.expandAllHeadlines 

419@g.commander_command('expand-all') 

420def expandAllHeadlines(self, event=None): 

421 """Expand all headlines. 

422 Warning: this can take a long time for large outlines.""" 

423 c = self 

424 c.endEditing() 

425 p = c.rootPosition() 

426 while p: 

427 c.expandSubtree(p) 

428 p.moveToNext() 

429 c.redraw_after_expand(p=c.rootPosition()) 

430 c.expansionLevel = 0 # Reset expansion level. 

431#@+node:ekr.20031218072017.2904: *3* c_oc.expandAllSubheads 

432@g.commander_command('expand-all-subheads') 

433def expandAllSubheads(self, event=None): 

434 """Expand all children of the presently selected node.""" 

435 c, p = self, self.p 

436 if not p: 

437 return 

438 child = p.firstChild() 

439 c.expandSubtree(p) 

440 while child: 

441 c.expandSubtree(child) 

442 child = child.next() 

443 c.redraw(p) 

444#@+node:ekr.20031218072017.2905: *3* c_oc.expandLevel1..9 

445@g.commander_command('expand-to-level-1') 

446def expandLevel1(self, event=None): 

447 """Expand the outline to level 1""" 

448 self.expandToLevel(1) 

449 

450@g.commander_command('expand-to-level-2') 

451def expandLevel2(self, event=None): 

452 """Expand the outline to level 2""" 

453 self.expandToLevel(2) 

454 

455@g.commander_command('expand-to-level-3') 

456def expandLevel3(self, event=None): 

457 """Expand the outline to level 3""" 

458 self.expandToLevel(3) 

459 

460@g.commander_command('expand-to-level-4') 

461def expandLevel4(self, event=None): 

462 """Expand the outline to level 4""" 

463 self.expandToLevel(4) 

464 

465@g.commander_command('expand-to-level-5') 

466def expandLevel5(self, event=None): 

467 """Expand the outline to level 5""" 

468 self.expandToLevel(5) 

469 

470@g.commander_command('expand-to-level-6') 

471def expandLevel6(self, event=None): 

472 """Expand the outline to level 6""" 

473 self.expandToLevel(6) 

474 

475@g.commander_command('expand-to-level-7') 

476def expandLevel7(self, event=None): 

477 """Expand the outline to level 7""" 

478 self.expandToLevel(7) 

479 

480@g.commander_command('expand-to-level-8') 

481def expandLevel8(self, event=None): 

482 """Expand the outline to level 8""" 

483 self.expandToLevel(8) 

484 

485@g.commander_command('expand-to-level-9') 

486def expandLevel9(self, event=None): 

487 """Expand the outline to level 9""" 

488 self.expandToLevel(9) 

489#@+node:ekr.20031218072017.2906: *3* c_oc.expandNextLevel 

490@g.commander_command('expand-next-level') 

491def expandNextLevel(self, event=None): 

492 """ 

493 Increase the expansion level of the outline and 

494 Expand all nodes at that level or lower. 

495 """ 

496 c = self 

497 # Expansion levels are now local to a particular tree. 

498 if c.expansionNode != c.p: 

499 c.expansionLevel = 1 

500 c.expansionNode = c.p.copy() 

501 self.expandToLevel(c.expansionLevel + 1) 

502#@+node:ekr.20031218072017.2907: *3* c_oc.expandNode 

503@g.commander_command('expand-node') 

504def expandNode(self, event=None): 

505 """Expand the presently selected node.""" 

506 c = self 

507 p = c.p 

508 c.endEditing() 

509 p.expand() 

510 c.redraw_after_expand(p) 

511 c.selectPosition(p) 

512#@+node:ekr.20040930064232.1: *3* c_oc.expandNodeAndGoToFirstChild 

513@g.commander_command('expand-and-go-right') 

514def expandNodeAndGoToFirstChild(self, event=None): 

515 """If a node has children, expand it if needed and go to the first child.""" 

516 c, p = self, self.p 

517 c.endEditing() 

518 if p.hasChildren(): 

519 if not p.isExpanded(): 

520 c.expandNode() 

521 c.selectPosition(p.firstChild()) 

522 c.treeFocusHelper() 

523#@+node:ekr.20171125082744.1: *3* c_oc.expandNodeOrGoToFirstChild 

524@g.commander_command('expand-or-go-right') 

525def expandNodeOrGoToFirstChild(self, event=None): 

526 """ 

527 Simulate the Right Arrow Key in folder of Windows Explorer. 

528 if c.p has no children, do nothing. 

529 Otherwise, if c.p is expanded, select the first child. 

530 Otherwise, expand c.p. 

531 """ 

532 c, p = self, self.p 

533 c.endEditing() 

534 if p.hasChildren(): 

535 if p.isExpanded(): 

536 c.redraw_after_expand(p.firstChild()) 

537 else: 

538 c.expandNode() 

539#@+node:ekr.20060928062431: *3* c_oc.expandOnlyAncestorsOfNode 

540@g.commander_command('expand-ancestors-only') 

541def expandOnlyAncestorsOfNode(self, event=None, p=None): 

542 """Contract all nodes in the outline.""" 

543 c = self 

544 level = 1 

545 if p: 

546 c.selectPosition(p) # 2013/12/25 

547 root = c.p 

548 for p in c.all_unique_positions(): 

549 p.v.expandedPositions = [] 

550 p.v.contract() 

551 for p in root.parents(): 

552 p.expand() 

553 level += 1 

554 c.expansionLevel = level # Reset expansion level. 

555#@+node:ekr.20031218072017.2908: *3* c_oc.expandPrevLevel 

556@g.commander_command('expand-prev-level') 

557def expandPrevLevel(self, event=None): 

558 """Decrease the expansion level of the outline and 

559 Expand all nodes at that level or lower.""" 

560 c = self 

561 # Expansion levels are now local to a particular tree. 

562 if c.expansionNode != c.p: 

563 c.expansionLevel = 1 

564 c.expansionNode = c.p.copy() 

565 self.expandToLevel(max(1, c.expansionLevel - 1)) 

566#@+node:ekr.20171124081846.1: ** c_oc.fullCheckOutline 

567@g.commander_command('check-outline') 

568def fullCheckOutline(self, event=None): 

569 """ 

570 Performs a full check of the consistency of a .leo file. 

571 

572 As of Leo 5.1, Leo performs checks of gnx's and outline structure 

573 before writes and after reads, pastes and undo/redo. 

574 """ 

575 c = self 

576 return c.checkOutline(check_links=True) 

577#@+node:ekr.20031218072017.2913: ** c_oc.Goto commands 

578#@+node:ekr.20071213123942: *3* c_oc.findNextClone 

579@g.commander_command('find-next-clone') 

580def findNextClone(self, event=None): 

581 """Select the next cloned node.""" 

582 c, p = self, self.p 

583 cc = c.chapterController 

584 if not p: 

585 return 

586 if p.isCloned(): 

587 p.moveToThreadNext() 

588 flag = False 

589 while p: 

590 if p.isCloned(): 

591 flag = True 

592 break 

593 else: 

594 p.moveToThreadNext() 

595 if flag: 

596 if cc: 

597 cc.selectChapterByName('main') 

598 c.selectPosition(p) 

599 c.redraw_after_select(p) 

600 else: 

601 g.blue('no more clones') 

602#@+node:ekr.20031218072017.1628: *3* c_oc.goNextVisitedNode 

603@g.commander_command('go-forward') 

604def goNextVisitedNode(self, event=None): 

605 """Select the next visited node.""" 

606 c = self 

607 p = c.nodeHistory.goNext() 

608 if p: 

609 c.nodeHistory.skipBeadUpdate = True 

610 try: 

611 c.selectPosition(p) 

612 finally: 

613 c.nodeHistory.skipBeadUpdate = False 

614 c.redraw_after_select(p) 

615#@+node:ekr.20031218072017.1627: *3* c_oc.goPrevVisitedNode 

616@g.commander_command('go-back') 

617def goPrevVisitedNode(self, event=None): 

618 """Select the previously visited node.""" 

619 c = self 

620 p = c.nodeHistory.goPrev() 

621 if p: 

622 c.nodeHistory.skipBeadUpdate = True 

623 try: 

624 c.selectPosition(p) 

625 finally: 

626 c.nodeHistory.skipBeadUpdate = False 

627 c.redraw_after_select(p) 

628#@+node:ekr.20031218072017.2914: *3* c_oc.goToFirstNode 

629@g.commander_command('goto-first-node') 

630def goToFirstNode(self, event=None): 

631 """ 

632 Select the first node of the entire outline. 

633  

634 But (#2167), go to the first node of a chapter or hoist 

635 if Leo is hoisted or within a chapter. 

636 """ 

637 c = self 

638 p = c.rootPosition() 

639 c.expandOnlyAncestorsOfNode(p=p) 

640 c.redraw() 

641#@+node:ekr.20051012092453: *3* c_oc.goToFirstSibling 

642@g.commander_command('goto-first-sibling') 

643def goToFirstSibling(self, event=None): 

644 """Select the first sibling of the selected node.""" 

645 c, p = self, self.p 

646 if p.hasBack(): 

647 while p.hasBack(): 

648 p.moveToBack() 

649 c.treeSelectHelper(p) 

650#@+node:ekr.20070615070925: *3* c_oc.goToFirstVisibleNode 

651@g.commander_command('goto-first-visible-node') 

652def goToFirstVisibleNode(self, event=None): 

653 """Select the first visible node of the selected chapter or hoist.""" 

654 c = self 

655 p = c.firstVisible() 

656 if p: 

657 c.expandOnlyAncestorsOfNode(p=p) 

658 c.redraw() 

659#@+node:ekr.20031218072017.2915: *3* c_oc.goToLastNode 

660@g.commander_command('goto-last-node') 

661def goToLastNode(self, event=None): 

662 """Select the last node in the entire tree.""" 

663 c = self 

664 p = c.rootPosition() 

665 while p and p.hasThreadNext(): 

666 p.moveToThreadNext() 

667 c.expandOnlyAncestorsOfNode(p=p) 

668 c.redraw() 

669#@+node:ekr.20051012092847.1: *3* c_oc.goToLastSibling 

670@g.commander_command('goto-last-sibling') 

671def goToLastSibling(self, event=None): 

672 """Select the last sibling of the selected node.""" 

673 c, p = self, self.p 

674 if p.hasNext(): 

675 while p.hasNext(): 

676 p.moveToNext() 

677 c.treeSelectHelper(p) 

678#@+node:ekr.20050711153537: *3* c_oc.goToLastVisibleNode 

679@g.commander_command('goto-last-visible-node') 

680def goToLastVisibleNode(self, event=None): 

681 """Select the last visible node of selected chapter or hoist.""" 

682 c = self 

683 p = c.lastVisible() 

684 if p: 

685 c.expandOnlyAncestorsOfNode(p=p) 

686 c.redraw() 

687#@+node:ekr.20031218072017.2916: *3* c_oc.goToNextClone 

688@g.commander_command('goto-next-clone') 

689def goToNextClone(self, event=None): 

690 """ 

691 Select the next node that is a clone of the selected node. 

692 If the selected node is not a clone, do find-next-clone. 

693 """ 

694 c, p = self, self.p 

695 cc = c.chapterController 

696 if not p: 

697 return 

698 if not p.isCloned(): 

699 c.findNextClone() 

700 return 

701 v = p.v 

702 p.moveToThreadNext() 

703 wrapped = False 

704 while 1: 

705 if p and p.v == v: 

706 break 

707 elif p: 

708 p.moveToThreadNext() 

709 elif wrapped: 

710 break 

711 else: 

712 wrapped = True 

713 p = c.rootPosition() 

714 if p: 

715 c.expandAllAncestors(p) 

716 if cc: 

717 # #252: goto-next clone activate chapter. 

718 chapter = cc.getSelectedChapter() 

719 old_name = chapter and chapter.name 

720 new_name = cc.findChapterNameForPosition(p) 

721 if new_name != old_name: 

722 cc.selectChapterByName(new_name) 

723 # Always do a full redraw. 

724 c.redraw(p) 

725 else: 

726 g.blue('done') 

727#@+node:ekr.20031218072017.2917: *3* c_oc.goToNextDirtyHeadline 

728@g.commander_command('goto-next-changed') 

729def goToNextDirtyHeadline(self, event=None): 

730 """Select the node that is marked as changed.""" 

731 c, p = self, self.p 

732 if not p: 

733 return 

734 p.moveToThreadNext() 

735 wrapped = False 

736 while 1: 

737 if p and p.isDirty(): 

738 break 

739 elif p: 

740 p.moveToThreadNext() 

741 elif wrapped: 

742 break 

743 else: 

744 wrapped = True 

745 p = c.rootPosition() 

746 if not p: 

747 g.blue('done') 

748 c.treeSelectHelper(p) # Sets focus. 

749#@+node:ekr.20031218072017.2918: *3* c_oc.goToNextMarkedHeadline 

750@g.commander_command('goto-next-marked') 

751def goToNextMarkedHeadline(self, event=None): 

752 """Select the next marked node.""" 

753 c, p = self, self.p 

754 if not p: 

755 return 

756 p.moveToThreadNext() 

757 wrapped = False 

758 while 1: 

759 if p and p.isMarked(): 

760 break 

761 elif p: 

762 p.moveToThreadNext() 

763 elif wrapped: 

764 break 

765 else: 

766 wrapped = True 

767 p = c.rootPosition() 

768 if not p: 

769 g.blue('done') 

770 c.treeSelectHelper(p) # Sets focus. 

771#@+node:ekr.20031218072017.2919: *3* c_oc.goToNextSibling 

772@g.commander_command('goto-next-sibling') 

773def goToNextSibling(self, event=None): 

774 """Select the next sibling of the selected node.""" 

775 c, p = self, self.p 

776 c.treeSelectHelper(p and p.next()) 

777#@+node:ekr.20031218072017.2920: *3* c_oc.goToParent 

778@g.commander_command('goto-parent') 

779def goToParent(self, event=None): 

780 """Select the parent of the selected node.""" 

781 c, p = self, self.p 

782 c.treeSelectHelper(p and p.parent()) 

783#@+node:ekr.20190211104913.1: *3* c_oc.goToPrevMarkedHeadline 

784@g.commander_command('goto-prev-marked') 

785def goToPrevMarkedHeadline(self, event=None): 

786 """Select the next marked node.""" 

787 c, p = self, self.p 

788 if not p: 

789 return 

790 p.moveToThreadBack() 

791 wrapped = False 

792 while 1: 

793 if p and p.isMarked(): 

794 break 

795 elif p: 

796 p.moveToThreadBack() 

797 elif wrapped: 

798 break 

799 else: 

800 wrapped = True 

801 p = c.rootPosition() 

802 if not p: 

803 g.blue('done') 

804 c.treeSelectHelper(p) # Sets focus. 

805#@+node:ekr.20031218072017.2921: *3* c_oc.goToPrevSibling 

806@g.commander_command('goto-prev-sibling') 

807def goToPrevSibling(self, event=None): 

808 """Select the previous sibling of the selected node.""" 

809 c, p = self, self.p 

810 c.treeSelectHelper(p and p.back()) 

811#@+node:ekr.20031218072017.2993: *3* c_oc.selectThreadBack 

812@g.commander_command('goto-prev-node') 

813def selectThreadBack(self, event=None): 

814 """Select the node preceding the selected node in outline order.""" 

815 c, p = self, self.p 

816 if not p: 

817 return 

818 p.moveToThreadBack() 

819 c.treeSelectHelper(p) 

820#@+node:ekr.20031218072017.2994: *3* c_oc.selectThreadNext 

821@g.commander_command('goto-next-node') 

822def selectThreadNext(self, event=None): 

823 """Select the node following the selected node in outline order.""" 

824 c, p = self, self.p 

825 if not p: 

826 return 

827 p.moveToThreadNext() 

828 c.treeSelectHelper(p) 

829#@+node:ekr.20031218072017.2995: *3* c_oc.selectVisBack 

830@g.commander_command('goto-prev-visible') 

831def selectVisBack(self, event=None): 

832 """Select the visible node preceding the presently selected node.""" 

833 # This has an up arrow for a control key. 

834 c, p = self, self.p 

835 if not p: 

836 return 

837 if c.canSelectVisBack(): 

838 p.moveToVisBack(c) 

839 c.treeSelectHelper(p) 

840 else: 

841 c.endEditing() # 2011/05/28: A special case. 

842#@+node:ekr.20031218072017.2996: *3* c_oc.selectVisNext 

843@g.commander_command('goto-next-visible') 

844def selectVisNext(self, event=None): 

845 """Select the visible node following the presently selected node.""" 

846 c, p = self, self.p 

847 if not p: 

848 return 

849 if c.canSelectVisNext(): 

850 p.moveToVisNext(c) 

851 c.treeSelectHelper(p) 

852 else: 

853 c.endEditing() # 2011/05/28: A special case. 

854#@+node:ekr.20031218072017.2028: ** c_oc.hoist/dehoist/clearAllHoists 

855#@+node:ekr.20120308061112.9865: *3* c_oc.deHoist 

856@g.commander_command('de-hoist') 

857@g.commander_command('dehoist') 

858def dehoist(self, event=None): 

859 """Undo a previous hoist of an outline.""" 

860 c = self 

861 if not c.p or not c.hoistStack: 

862 return 

863 # Don't de-hoist an @chapter node. 

864 if c.chapterController and c.p.h.startswith('@chapter '): 

865 if not g.unitTesting: 

866 g.es('can not de-hoist an @chapter node.', color='blue') 

867 return 

868 bunch = c.hoistStack.pop() 

869 p = bunch.p 

870 if not p: 

871 p.expand() 

872 else: 

873 p.contract() 

874 c.setCurrentPosition(p) 

875 c.redraw() 

876 c.frame.clearStatusLine() 

877 c.frame.putStatusLine("De-Hoist: " + p.h) 

878 c.undoer.afterDehoist(p, 'DeHoist') 

879 g.doHook('hoist-changed', c=c) 

880#@+node:ekr.20120308061112.9866: *3* c_oc.clearAllHoists 

881@g.commander_command('clear-all-hoists') 

882def clearAllHoists(self, event=None): 

883 """Undo a previous hoist of an outline.""" 

884 c = self 

885 c.hoistStack = [] 

886 c.frame.putStatusLine("Hoists cleared") 

887 g.doHook('hoist-changed', c=c) 

888#@+node:ekr.20120308061112.9867: *3* c_oc.hoist 

889@g.commander_command('hoist') 

890def hoist(self, event=None): 

891 """Make only the selected outline visible.""" 

892 c = self 

893 p = c.p 

894 if not p: 

895 return 

896 # Don't hoist an @chapter node. 

897 if c.chapterController and p.h.startswith('@chapter '): 

898 if not g.unitTesting: 

899 g.es('can not hoist an @chapter node.', color='blue') 

900 return 

901 # Remember the expansion state. 

902 bunch = g.Bunch(p=p.copy(), expanded=p.isExpanded()) 

903 c.hoistStack.append(bunch) 

904 p.expand() 

905 c.redraw(p) 

906 c.frame.clearStatusLine() 

907 c.frame.putStatusLine("Hoist: " + p.h) 

908 c.undoer.afterHoist(p, 'Hoist') 

909 g.doHook('hoist-changed', c=c) 

910#@+node:ekr.20031218072017.1759: ** c_oc.Insert, Delete & Clone commands 

911#@+node:ekr.20031218072017.1762: *3* c_oc.clone 

912@g.commander_command('clone-node') 

913def clone(self, event=None): 

914 """Create a clone of the selected outline.""" 

915 c, p, u = self, self.p, self.undoer 

916 if not p: 

917 return None 

918 undoData = c.undoer.beforeCloneNode(p) 

919 c.endEditing() # Capture any changes to the headline. 

920 clone = p.clone() 

921 clone.setDirty() 

922 c.setChanged() 

923 if c.validateOutline(): 

924 u.afterCloneNode(clone, 'Clone Node', undoData) 

925 c.redraw(clone) 

926 c.treeWantsFocus() 

927 return clone # For mod_labels and chapters plugins. 

928 clone.doDelete() 

929 c.setCurrentPosition(p) 

930 return None 

931#@+node:ekr.20150630152607.1: *3* c_oc.cloneToAtSpot 

932@g.commander_command('clone-to-at-spot') 

933def cloneToAtSpot(self, event=None): 

934 """ 

935 Create a clone of the selected node and move it to the last @spot node 

936 of the outline. Create the @spot node if necessary. 

937 """ 

938 c, p, u = self, self.p, self.undoer 

939 if not p: 

940 return 

941 # 2015/12/27: fix bug 220: do not allow clone-to-at-spot on @spot node. 

942 if p.h.startswith('@spot'): 

943 g.es("can not clone @spot node", color='red') 

944 return 

945 last_spot = None 

946 for p2 in c.all_positions(): 

947 if g.match_word(p2.h, 0, '@spot'): 

948 last_spot = p2.copy() 

949 if not last_spot: 

950 last = c.lastTopLevel() 

951 last_spot = last.insertAfter() 

952 last_spot.h = '@spot' 

953 undoData = c.undoer.beforeCloneNode(p) 

954 c.endEditing() # Capture any changes to the headline. 

955 clone = p.copy() 

956 clone._linkAsNthChild(last_spot, n=last_spot.numberOfChildren()) 

957 clone.setDirty() 

958 c.setChanged() 

959 if c.validateOutline(): 

960 u.afterCloneNode(clone, 'Clone Node', undoData) 

961 c.contractAllHeadlines() 

962 c.redraw(clone) 

963 else: 

964 clone.doDelete() 

965 c.setCurrentPosition(p) 

966#@+node:ekr.20141023154408.5: *3* c_oc.cloneToLastNode 

967@g.commander_command('clone-node-to-last-node') 

968def cloneToLastNode(self, event=None): 

969 """ 

970 Clone the selected node and move it to the last node. 

971 Do *not* change the selected node. 

972 """ 

973 c, p, u = self, self.p, self.undoer 

974 if not p: 

975 return 

976 prev = p.copy() 

977 undoData = c.undoer.beforeCloneNode(p) 

978 c.endEditing() # Capture any changes to the headline. 

979 clone = p.clone() 

980 last = c.rootPosition() 

981 while last and last.hasNext(): 

982 last.moveToNext() 

983 clone.moveAfter(last) 

984 clone.setDirty() 

985 c.setChanged() 

986 u.afterCloneNode(clone, 'Clone Node To Last', undoData) 

987 c.redraw(prev) 

988 # return clone # For mod_labels and chapters plugins. 

989#@+node:ekr.20031218072017.1193: *3* c_oc.deleteOutline 

990@g.commander_command('delete-node') 

991def deleteOutline(self, event=None, op_name="Delete Node"): 

992 """Deletes the selected outline.""" 

993 c, u = self, self.undoer 

994 p = c.p 

995 if not p: 

996 return 

997 c.endEditing() # Make sure we capture the headline for Undo. 

998 if False: # c.config.getBool('select-next-after-delete'): 

999 # #721: Optionally select next node after delete. 

1000 if p.hasVisNext(c): 

1001 newNode = p.visNext(c) 

1002 elif p.hasParent(): 

1003 newNode = p.parent() 

1004 else: 

1005 newNode = p.back() # _not_ p.visBack(): we are at the top level. 

1006 else: 

1007 # Legacy: select previous node if possible. 

1008 if p.hasVisBack(c): 

1009 newNode = p.visBack(c) 

1010 else: 

1011 newNode = p.next() # _not_ p.visNext(): we are at the top level. 

1012 if not newNode: 

1013 return 

1014 undoData = u.beforeDeleteNode(p) 

1015 p.setDirty() 

1016 p.doDelete(newNode) 

1017 c.setChanged() 

1018 u.afterDeleteNode(newNode, op_name, undoData) 

1019 c.redraw(newNode) 

1020 c.validateOutline() 

1021#@+node:ekr.20071005173203.1: *3* c_oc.insertChild 

1022@g.commander_command('insert-child') 

1023def insertChild(self, event=None): 

1024 """Insert a node after the presently selected node.""" 

1025 c = self 

1026 return c.insertHeadline(event=event, op_name='Insert Child', as_child=True) 

1027#@+node:ekr.20031218072017.1761: *3* c_oc.insertHeadline (insert-*) 

1028@g.commander_command('insert-node') 

1029def insertHeadline(self, event=None, op_name="Insert Node", as_child=False): 

1030 """ 

1031 If c.p is expanded, insert a new node as the first or last child of c.p, 

1032 depending on @bool insert-new-nodes-at-end. 

1033  

1034 If c.p is not expanded, insert a new node after c.p. 

1035 """ 

1036 c = self 

1037 # Fix #600. 

1038 return insertHeadlineHelper(c, event=event, as_child=as_child) 

1039 

1040@g.commander_command('insert-as-first-child') 

1041def insertNodeAsFirstChild(self, event=None): 

1042 """Insert a node as the last last of the previous node.""" 

1043 c = self 

1044 return insertHeadlineHelper(c, event=event, as_first_child=True) 

1045 

1046@g.commander_command('insert-as-last-child') 

1047def insertNodeAsLastChild(self, event=None): 

1048 """Insert a node as the last child of the previous node.""" 

1049 c = self 

1050 return insertHeadlineHelper(c, event=event, as_last_child=True) 

1051#@+node:ekr.20171124091846.1: *4* function: insertHeadlineHelper 

1052def insertHeadlineHelper(c, 

1053 event=None, 

1054 op_name="Insert Node", 

1055 as_child=False, 

1056 as_first_child=False, 

1057 as_last_child=False, 

1058): 

1059 """Insert a node after the presently selected node.""" 

1060 u = c.undoer 

1061 current = c.p 

1062 if not current: 

1063 return None 

1064 c.endEditing() 

1065 undoData = c.undoer.beforeInsertNode(current) 

1066 if as_first_child: 

1067 p = current.insertAsNthChild(0) 

1068 elif as_last_child: 

1069 p = current.insertAsLastChild() 

1070 elif ( 

1071 as_child or 

1072 (current.hasChildren() and current.isExpanded()) or 

1073 (c.hoistStack and current == c.hoistStack[-1].p) 

1074 ): 

1075 # Make sure the new node is visible when hoisting. 

1076 if c.config.getBool('insert-new-nodes-at-end'): 

1077 p = current.insertAsLastChild() 

1078 else: 

1079 p = current.insertAsNthChild(0) 

1080 else: 

1081 p = current.insertAfter() 

1082 g.doHook('create-node', c=c, p=p) 

1083 p.setDirty() 

1084 c.setChanged() 

1085 u.afterInsertNode(p, op_name, undoData) 

1086 c.redrawAndEdit(p, selectAll=True) 

1087 return p 

1088#@+node:ekr.20130922133218.11540: *3* c_oc.insertHeadlineBefore 

1089@g.commander_command('insert-node-before') 

1090def insertHeadlineBefore(self, event=None): 

1091 """Insert a node before the presently selected node.""" 

1092 c, current, u = self, self.p, self.undoer 

1093 op_name = 'Insert Node Before' 

1094 if not current: 

1095 return None 

1096 # Can not insert before the base of a hoist. 

1097 if c.hoistStack and current == c.hoistStack[-1].p: 

1098 g.warning('can not insert a node before the base of a hoist') 

1099 return None 

1100 c.endEditing() 

1101 undoData = u.beforeInsertNode(current) 

1102 p = current.insertBefore() 

1103 g.doHook('create-node', c=c, p=p) 

1104 p.setDirty() 

1105 c.setChanged() 

1106 u.afterInsertNode(p, op_name, undoData) 

1107 c.redrawAndEdit(p, selectAll=True) 

1108 return p 

1109#@+node:ekr.20031218072017.2922: ** c_oc.Mark commands 

1110#@+node:ekr.20090905110447.6098: *3* c_oc.cloneMarked 

1111@g.commander_command('clone-marked-nodes') 

1112def cloneMarked(self, event=None): 

1113 """Clone all marked nodes as children of a new node.""" 

1114 c, u = self, self.undoer 

1115 p1 = c.p.copy() 

1116 # Create a new node to hold clones. 

1117 parent = p1.insertAfter() 

1118 parent.h = 'Clones of marked nodes' 

1119 cloned, n, p = [], 0, c.rootPosition() 

1120 while p: 

1121 # Careful: don't clone already-cloned nodes. 

1122 if p == parent: 

1123 p.moveToNodeAfterTree() 

1124 elif p.isMarked() and p.v not in cloned: 

1125 cloned.append(p.v) 

1126 if 0: # old code 

1127 # Calling p.clone would cause problems 

1128 p.clone().moveToLastChildOf(parent) 

1129 else: # New code. 

1130 # Create the clone directly as a child of parent. 

1131 p2 = p.copy() 

1132 n = parent.numberOfChildren() 

1133 p2._linkAsNthChild(parent, n) 

1134 p.moveToNodeAfterTree() 

1135 n += 1 

1136 else: 

1137 p.moveToThreadNext() 

1138 if n: 

1139 c.setChanged() 

1140 parent.expand() 

1141 c.selectPosition(parent) 

1142 u.afterCloneMarkedNodes(p1) 

1143 else: 

1144 parent.doDelete() 

1145 c.selectPosition(p1) 

1146 if not g.unitTesting: 

1147 g.blue(f"cloned {n} nodes") 

1148 c.redraw() 

1149#@+node:ekr.20160502090456.1: *3* c_oc.copyMarked 

1150@g.commander_command('copy-marked-nodes') 

1151def copyMarked(self, event=None): 

1152 """Copy all marked nodes as children of a new node.""" 

1153 c, u = self, self.undoer 

1154 p1 = c.p.copy() 

1155 # Create a new node to hold clones. 

1156 parent = p1.insertAfter() 

1157 parent.h = 'Copies of marked nodes' 

1158 copied, n, p = [], 0, c.rootPosition() 

1159 while p: 

1160 # Careful: don't clone already-cloned nodes. 

1161 if p == parent: 

1162 p.moveToNodeAfterTree() 

1163 elif p.isMarked() and p.v not in copied: 

1164 copied.append(p.v) 

1165 p2 = p.copyWithNewVnodes(copyMarked=True) 

1166 p2._linkAsNthChild(parent, n) 

1167 p.moveToNodeAfterTree() 

1168 n += 1 

1169 else: 

1170 p.moveToThreadNext() 

1171 if n: 

1172 c.setChanged() 

1173 parent.expand() 

1174 c.selectPosition(parent) 

1175 u.afterCopyMarkedNodes(p1) 

1176 else: 

1177 parent.doDelete() 

1178 c.selectPosition(p1) 

1179 if not g.unitTesting: 

1180 g.blue(f"copied {n} nodes") 

1181 c.redraw() 

1182#@+node:ekr.20111005081134.15540: *3* c_oc.deleteMarked 

1183@g.commander_command('delete-marked-nodes') 

1184def deleteMarked(self, event=None): 

1185 """Delete all marked nodes.""" 

1186 c, u = self, self.undoer 

1187 p1 = c.p.copy() 

1188 undo_data, p = [], c.rootPosition() 

1189 while p: 

1190 if p.isMarked(): 

1191 undo_data.append(p.copy()) 

1192 next = p.positionAfterDeletedTree() 

1193 p.doDelete() 

1194 p = next 

1195 else: 

1196 p.moveToThreadNext() 

1197 if undo_data: 

1198 u.afterDeleteMarkedNodes(undo_data, p1) 

1199 if not g.unitTesting: 

1200 g.blue(f"deleted {len(undo_data)} nodes") 

1201 c.setChanged() 

1202 # Don't even *think* about restoring the old position. 

1203 c.contractAllHeadlines() 

1204 c.redraw(c.rootPosition()) 

1205#@+node:ekr.20111005081134.15539: *3* c_oc.moveMarked & helper 

1206@g.commander_command('move-marked-nodes') 

1207def moveMarked(self, event=None): 

1208 """ 

1209 Move all marked nodes as children of a new node. 

1210 This command is not undoable. 

1211 Consider using clone-marked-nodes, followed by copy/paste instead. 

1212 """ 

1213 c = self 

1214 p1 = c.p.copy() 

1215 # Check for marks. 

1216 for v in c.all_unique_nodes(): 

1217 if v.isMarked(): 

1218 break 

1219 else: 

1220 g.warning('no marked nodes') 

1221 return 

1222 result = g.app.gui.runAskYesNoDialog(c, 

1223 'Move Marked Nodes?', 

1224 message='move-marked-nodes is not undoable\nProceed?', 

1225 ) 

1226 if result == 'no': 

1227 return 

1228 # Create a new *root* node to hold the moved nodes. 

1229 # This node's position remains stable while other nodes move. 

1230 parent = createMoveMarkedNode(c) 

1231 assert not parent.isMarked() 

1232 moved = [] 

1233 p = c.rootPosition() 

1234 while p: 

1235 assert parent == c.rootPosition() 

1236 # Careful: don't move already-moved nodes. 

1237 if p.isMarked() and not parent.isAncestorOf(p): 

1238 moved.append(p.copy()) 

1239 next = p.positionAfterDeletedTree() 

1240 p.moveToLastChildOf(parent) 

1241 # This does not change parent's position. 

1242 p = next 

1243 else: 

1244 p.moveToThreadNext() 

1245 if moved: 

1246 # Find a position p2 outside of parent's tree with p2.v == p1.v. 

1247 # Such a position may not exist. 

1248 p2 = c.rootPosition() 

1249 while p2: 

1250 if p2 == parent: 

1251 p2.moveToNodeAfterTree() 

1252 elif p2.v == p1.v: 

1253 break 

1254 else: 

1255 p2.moveToThreadNext() 

1256 else: 

1257 # Not found. Move to last top-level. 

1258 p2 = c.lastTopLevel() 

1259 parent.moveAfter(p2) 

1260 # u.afterMoveMarkedNodes(moved, p1) 

1261 if not g.unitTesting: 

1262 g.blue(f"moved {len(moved)} nodes") 

1263 c.setChanged() 

1264 # Calling c.contractAllHeadlines() causes problems when in a chapter. 

1265 c.redraw(parent) 

1266#@+node:ekr.20111005081134.15543: *4* def createMoveMarkedNode 

1267def createMoveMarkedNode(c): 

1268 oldRoot = c.rootPosition() 

1269 p = oldRoot.insertAfter() 

1270 p.h = 'Moved marked nodes' 

1271 p.moveToRoot() 

1272 return p 

1273#@+node:ekr.20031218072017.2923: *3* c_oc.markChangedHeadlines 

1274@g.commander_command('mark-changed-items') 

1275def markChangedHeadlines(self, event=None): 

1276 """Mark all nodes that have been changed.""" 

1277 c, current, u = self, self.p, self.undoer 

1278 undoType = 'Mark Changed' 

1279 c.endEditing() 

1280 u.beforeChangeGroup(current, undoType) 

1281 for p in c.all_unique_positions(): 

1282 if p.isDirty() and not p.isMarked(): 

1283 bunch = u.beforeMark(p, undoType) 

1284 # c.setMarked calls a hook. 

1285 c.setMarked(p) 

1286 p.setDirty() 

1287 c.setChanged() 

1288 u.afterMark(p, undoType, bunch) 

1289 u.afterChangeGroup(current, undoType) 

1290 if not g.unitTesting: 

1291 g.blue('done') 

1292 c.redraw_after_icons_changed() 

1293#@+node:ekr.20031218072017.2924: *3* c_oc.markChangedRoots 

1294def markChangedRoots(self, event=None): 

1295 """Mark all changed @root nodes.""" 

1296 c, current, u = self, self.p, self.undoer 

1297 undoType = 'Mark Changed' 

1298 c.endEditing() 

1299 u.beforeChangeGroup(current, undoType) 

1300 for p in c.all_unique_positions(): 

1301 if p.isDirty() and not p.isMarked(): 

1302 s = p.b 

1303 flag, i = g.is_special(s, "@root") 

1304 if flag: 

1305 bunch = u.beforeMark(p, undoType) 

1306 c.setMarked(p) # Calls a hook. 

1307 p.setDirty() 

1308 c.setChanged() 

1309 u.afterMark(p, undoType, bunch) 

1310 u.afterChangeGroup(current, undoType) 

1311 if not g.unitTesting: 

1312 g.blue('done') 

1313 c.redraw_after_icons_changed() 

1314#@+node:ekr.20031218072017.2928: *3* c_oc.markHeadline 

1315@g.commander_command('mark') # Compatibility 

1316@g.commander_command('toggle-mark') 

1317def markHeadline(self, event=None): 

1318 """Toggle the mark of the selected node.""" 

1319 c, p, u = self, self.p, self.undoer 

1320 if not p: 

1321 return 

1322 c.endEditing() 

1323 undoType = 'Unmark' if p.isMarked() else 'Mark' 

1324 bunch = u.beforeMark(p, undoType) 

1325 # c.set/clearMarked call a hook. 

1326 if p.isMarked(): 

1327 c.clearMarked(p) 

1328 else: 

1329 c.setMarked(p) 

1330 p.setDirty() 

1331 c.setChanged() 

1332 u.afterMark(p, undoType, bunch) 

1333 c.redraw_after_icons_changed() 

1334#@+node:ekr.20031218072017.2929: *3* c_oc.markSubheads 

1335@g.commander_command('mark-subheads') 

1336def markSubheads(self, event=None): 

1337 """Mark all children of the selected node as changed.""" 

1338 c, current, u = self, self.p, self.undoer 

1339 undoType = 'Mark Subheads' 

1340 if not current: 

1341 return 

1342 c.endEditing() 

1343 u.beforeChangeGroup(current, undoType) 

1344 for p in current.children(): 

1345 if not p.isMarked(): 

1346 bunch = u.beforeMark(p, undoType) 

1347 c.setMarked(p) # Calls a hook. 

1348 p.setDirty() 

1349 c.setChanged() 

1350 u.afterMark(p, undoType, bunch) 

1351 u.afterChangeGroup(current, undoType) 

1352 c.redraw_after_icons_changed() 

1353#@+node:ekr.20031218072017.2930: *3* c_oc.unmarkAll 

1354@g.commander_command('unmark-all') 

1355def unmarkAll(self, event=None): 

1356 """Unmark all nodes in the entire outline.""" 

1357 c, current, u = self, self.p, self.undoer 

1358 undoType = 'Unmark All' 

1359 if not current: 

1360 return 

1361 c.endEditing() 

1362 u.beforeChangeGroup(current, undoType) 

1363 changed = False 

1364 p = None # To keep pylint happy. 

1365 for p in c.all_unique_positions(): 

1366 if p.isMarked(): 

1367 bunch = u.beforeMark(p, undoType) 

1368 # c.clearMarked(p) # Very slow: calls a hook. 

1369 p.v.clearMarked() 

1370 p.setDirty() 

1371 u.afterMark(p, undoType, bunch) 

1372 changed = True 

1373 if changed: 

1374 g.doHook("clear-all-marks", c=c, p=p) 

1375 c.setChanged() 

1376 u.afterChangeGroup(current, undoType) 

1377 c.redraw_after_icons_changed() 

1378#@+node:ekr.20031218072017.1766: ** c_oc.Move commands 

1379#@+node:ekr.20031218072017.1767: *3* c_oc.demote 

1380@g.commander_command('demote') 

1381def demote(self, event=None): 

1382 """Make all following siblings children of the selected node.""" 

1383 c, p, u = self, self.p, self.undoer 

1384 if not p or not p.hasNext(): 

1385 c.treeFocusHelper() 

1386 return 

1387 # Make sure all the moves will be valid. 

1388 next = p.next() 

1389 while next: 

1390 if not c.checkMoveWithParentWithWarning(next, p, True): 

1391 c.treeFocusHelper() 

1392 return 

1393 next.moveToNext() 

1394 c.endEditing() 

1395 parent_v = p._parentVnode() 

1396 n = p.childIndex() 

1397 followingSibs = parent_v.children[n + 1 :] 

1398 # Remove the moved nodes from the parent's children. 

1399 parent_v.children = parent_v.children[: n + 1] 

1400 # Add the moved nodes to p's children 

1401 p.v.children.extend(followingSibs) 

1402 # Adjust the parent links in the moved nodes. 

1403 # There is no need to adjust descendant links. 

1404 for child in followingSibs: 

1405 child.parents.remove(parent_v) 

1406 child.parents.append(p.v) 

1407 p.expand() 

1408 p.setDirty() 

1409 c.setChanged() 

1410 u.afterDemote(p, followingSibs) 

1411 c.redraw(p) 

1412 c.updateSyntaxColorer(p) # Moving can change syntax coloring. 

1413#@+node:ekr.20031218072017.1768: *3* c_oc.moveOutlineDown 

1414@g.commander_command('move-outline-down') 

1415def moveOutlineDown(self, event=None): 

1416 """Move the selected node down.""" 

1417 # Moving down is more tricky than moving up because we can't 

1418 # move p to be a child of itself. 

1419 # 

1420 # An important optimization: 

1421 # we don't have to call checkMoveWithParentWithWarning() if the parent of 

1422 # the moved node remains the same. 

1423 c, p, u = self, self.p, self.undoer 

1424 if not p: 

1425 return 

1426 if not c.canMoveOutlineDown(): 

1427 if c.hoistStack: 

1428 cantMoveMessage(c) 

1429 c.treeFocusHelper() 

1430 return 

1431 parent = p.parent() 

1432 next = p.visNext(c) 

1433 while next and p.isAncestorOf(next): 

1434 next = next.visNext(c) 

1435 if not next: 

1436 if c.hoistStack: 

1437 cantMoveMessage(c) 

1438 c.treeFocusHelper() 

1439 return 

1440 c.endEditing() 

1441 undoData = u.beforeMoveNode(p) 

1442 #@+<< Move p down & set moved if successful >> 

1443 #@+node:ekr.20031218072017.1769: *4* << Move p down & set moved if successful >> 

1444 if next.hasChildren() and next.isExpanded(): 

1445 # Attempt to move p to the first child of next. 

1446 moved = c.checkMoveWithParentWithWarning(p, next, True) 

1447 if moved: 

1448 p.setDirty() 

1449 p.moveToNthChildOf(next, 0) 

1450 else: 

1451 # Attempt to move p after next. 

1452 moved = c.checkMoveWithParentWithWarning(p, next.parent(), True) 

1453 if moved: 

1454 p.setDirty() 

1455 p.moveAfter(next) 

1456 # Patch by nh2: 0004-Add-bool-collapse_nodes_after_move-option.patch 

1457 if ( 

1458 c.collapse_nodes_after_move 

1459 and moved and c.sparse_move 

1460 and parent and not parent.isAncestorOf(p) 

1461 ): 

1462 # New in Leo 4.4.2: contract the old parent if it is no longer the parent of p. 

1463 parent.contract() 

1464 #@-<< Move p down & set moved if successful >> 

1465 if moved: 

1466 p.setDirty() 

1467 c.setChanged() 

1468 u.afterMoveNode(p, 'Move Down', undoData) 

1469 c.redraw(p) 

1470 c.updateSyntaxColorer(p) # Moving can change syntax coloring. 

1471#@+node:ekr.20031218072017.1770: *3* c_oc.moveOutlineLeft 

1472@g.commander_command('move-outline-left') 

1473def moveOutlineLeft(self, event=None): 

1474 """Move the selected node left if possible.""" 

1475 c, p, u = self, self.p, self.undoer 

1476 if not p: 

1477 return 

1478 if not c.canMoveOutlineLeft(): 

1479 if c.hoistStack: 

1480 cantMoveMessage(c) 

1481 c.treeFocusHelper() 

1482 return 

1483 if not p.hasParent(): 

1484 c.treeFocusHelper() 

1485 return 

1486 parent = p.parent() 

1487 c.endEditing() 

1488 undoData = u.beforeMoveNode(p) 

1489 p.setDirty() 

1490 p.moveAfter(parent) 

1491 p.setDirty() 

1492 c.setChanged() 

1493 u.afterMoveNode(p, 'Move Left', undoData) 

1494 # Patch by nh2: 0004-Add-bool-collapse_nodes_after_move-option.patch 

1495 if c.collapse_nodes_after_move and c.sparse_move: # New in Leo 4.4.2 

1496 parent.contract() 

1497 c.redraw(p) 

1498 c.recolor() # Moving can change syntax coloring. 

1499#@+node:ekr.20031218072017.1771: *3* c_oc.moveOutlineRight 

1500@g.commander_command('move-outline-right') 

1501def moveOutlineRight(self, event=None): 

1502 """Move the selected node right if possible.""" 

1503 c, p, u = self, self.p, self.undoer 

1504 if not p: 

1505 return 

1506 if not c.canMoveOutlineRight(): # 11/4/03: Support for hoist. 

1507 if c.hoistStack: 

1508 cantMoveMessage(c) 

1509 c.treeFocusHelper() 

1510 return 

1511 back = p.back() 

1512 if not back: 

1513 c.treeFocusHelper() 

1514 return 

1515 if not c.checkMoveWithParentWithWarning(p, back, True): 

1516 c.treeFocusHelper() 

1517 return 

1518 c.endEditing() 

1519 undoData = u.beforeMoveNode(p) 

1520 p.setDirty() 

1521 n = back.numberOfChildren() 

1522 p.moveToNthChildOf(back, n) 

1523 p.setDirty() 

1524 c.setChanged() # #2036. 

1525 u.afterMoveNode(p, 'Move Right', undoData) 

1526 c.redraw(p) 

1527 c.recolor() 

1528#@+node:ekr.20031218072017.1772: *3* c_oc.moveOutlineUp 

1529@g.commander_command('move-outline-up') 

1530def moveOutlineUp(self, event=None): 

1531 """Move the selected node up if possible.""" 

1532 c, p, u = self, self.p, self.undoer 

1533 if not p: 

1534 return 

1535 if not c.canMoveOutlineUp(): # Support for hoist. 

1536 if c.hoistStack: 

1537 cantMoveMessage(c) 

1538 c.treeFocusHelper() 

1539 return 

1540 back = p.visBack(c) 

1541 if not back: 

1542 return 

1543 back2 = back.visBack(c) 

1544 c.endEditing() 

1545 undoData = u.beforeMoveNode(p) 

1546 moved = False 

1547 #@+<< Move p up >> 

1548 #@+node:ekr.20031218072017.1773: *4* << Move p up >> 

1549 parent = p.parent() 

1550 if not back2: 

1551 if c.hoistStack: # hoist or chapter. 

1552 limit, limitIsVisible = c.visLimit() 

1553 assert limit 

1554 if limitIsVisible: 

1555 # canMoveOutlineUp should have caught this. 

1556 g.trace('can not happen. In hoist') 

1557 else: 

1558 moved = True 

1559 p.setDirty() 

1560 p.moveToFirstChildOf(limit) 

1561 else: 

1562 # p will be the new root node 

1563 p.setDirty() 

1564 p.moveToRoot() 

1565 moved = True 

1566 elif back2.hasChildren() and back2.isExpanded(): 

1567 if c.checkMoveWithParentWithWarning(p, back2, True): 

1568 moved = True 

1569 p.setDirty() 

1570 p.moveToNthChildOf(back2, 0) 

1571 else: 

1572 if c.checkMoveWithParentWithWarning(p, back2.parent(), True): 

1573 moved = True 

1574 p.setDirty() 

1575 p.moveAfter(back2) 

1576 # Patch by nh2: 0004-Add-bool-collapse_nodes_after_move-option.patch 

1577 if ( 

1578 c.collapse_nodes_after_move 

1579 and moved and c.sparse_move 

1580 and parent and not parent.isAncestorOf(p) 

1581 ): 

1582 # New in Leo 4.4.2: contract the old parent if it is no longer the parent of p. 

1583 parent.contract() 

1584 #@-<< Move p up >> 

1585 if moved: 

1586 p.setDirty() 

1587 c.setChanged() 

1588 u.afterMoveNode(p, 'Move Up', undoData) 

1589 c.redraw(p) 

1590 c.updateSyntaxColorer(p) # Moving can change syntax coloring. 

1591#@+node:ekr.20031218072017.1774: *3* c_oc.promote 

1592@g.commander_command('promote') 

1593def promote(self, event=None, undoFlag=True): 

1594 """Make all children of the selected nodes siblings of the selected node.""" 

1595 c, p, u = self, self.p, self.undoer 

1596 if not p or not p.hasChildren(): 

1597 c.treeFocusHelper() 

1598 return 

1599 c.endEditing() 

1600 children = p.v.children # First, for undo. 

1601 p.promote() 

1602 c.setChanged() 

1603 if undoFlag: 

1604 p.setDirty() 

1605 u.afterPromote(p, children) 

1606 c.redraw(p) 

1607 c.updateSyntaxColorer(p) # Moving can change syntax coloring. 

1608#@+node:ekr.20071213185710: *3* c_oc.toggleSparseMove 

1609@g.commander_command('toggle-sparse-move') 

1610def toggleSparseMove(self, event=None): 

1611 """Toggle whether moves collapse the outline.""" 

1612 c = self 

1613 c.sparse_move = not c.sparse_move 

1614 if not g.unitTesting: 

1615 g.blue(f"sparse-move: {c.sparse_move}") 

1616#@+node:ekr.20080425060424.1: ** c_oc.Sort commands 

1617#@+node:ekr.20050415134809: *3* c_oc.sortChildren 

1618@g.commander_command('sort-children') 

1619def sortChildren(self, event=None, key=None, reverse=False): 

1620 """Sort the children of a node.""" 

1621 # This method no longer supports the 'cmp' keyword arg. 

1622 c, p = self, self.p 

1623 if p and p.hasChildren(): 

1624 c.sortSiblings(p=p.firstChild(), sortChildren=True, key=key, reverse=reverse) 

1625#@+node:ekr.20050415134809.1: *3* c_oc.sortSiblings 

1626@g.commander_command('sort-siblings') 

1627def sortSiblings(self, event=None, 

1628 # cmp keyword is no longer supported. 

1629 key=None, 

1630 p=None, 

1631 sortChildren=False, 

1632 reverse=False 

1633): 

1634 """Sort the siblings of a node.""" 

1635 c, u = self, self.undoer 

1636 if not p: 

1637 p = c.p 

1638 if not p: 

1639 return 

1640 c.endEditing() 

1641 undoType = 'Sort Children' if sortChildren else 'Sort Siblings' 

1642 parent_v = p._parentVnode() 

1643 oldChildren = parent_v.children[:] 

1644 newChildren = parent_v.children[:] 

1645 if key is None: 

1646 

1647 def lowerKey(self): 

1648 return self.h.lower() 

1649 

1650 key = lowerKey 

1651 newChildren.sort(key=key, reverse=reverse) 

1652 if oldChildren == newChildren: 

1653 return 

1654 # 2010/01/20. Fix bug 510148. 

1655 c.setChanged() 

1656 bunch = u.beforeSort(p, undoType, oldChildren, newChildren, sortChildren) 

1657 parent_v.children = newChildren 

1658 u.afterSort(p, bunch) 

1659 # Sorting destroys position p, and possibly the root position. 

1660 p = c.setPositionAfterSort(sortChildren) 

1661 if p.parent(): 

1662 p.parent().setDirty() 

1663 c.redraw(p) 

1664#@+node:ekr.20070420092425: ** def cantMoveMessage 

1665def cantMoveMessage(c): 

1666 h = c.rootPosition().h 

1667 kind = 'chapter' if h.startswith('@chapter') else 'hoist' 

1668 g.warning("can't move node out of", kind) 

1669#@+node:ekr.20180201040936.1: ** count-children 

1670@g.command('count-children') 

1671def count_children(event=None): 

1672 c = event and event.get('c') 

1673 if c: 

1674 g.es_print(f"{c.p.numberOfChildren()} children") 

1675#@-others 

1676#@-leo