Coverage for C:\Repos\leo-editor\leo\commands\commanderOutlineCommands.py: 41%

1224 statements  

« prev     ^ index     » next       coverage.py v6.4, created at 2022-05-24 10:21 -0500

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.20220314071523.1: *3* c_oc.copyOutlineAsJson & helpers 

23@g.commander_command('copy-node-as-json') 

24def copyOutlineAsJSON(self, event=None): 

25 """Copy the selected outline to the clipboard in json format.""" 

26 # Copying an outline has no undo consequences. 

27 import json 

28 #@+others # Define helper functions 

29 #@+node:ekr.20220314072801.1: *4* function: json_globals 

30 def json_globals(c): 

31 """Put json representation of Leo's cached globals.""" 

32 width, height, left, top = c.frame.get_window_info() 

33 return { 

34 'body_outline_ratio': c.frame.ratio, 

35 'body_secondary_ratio': c.frame.secondary_ratio, 

36 'globalWindowPosition': { 

37 'height': height, 

38 'left': left, 

39 'top': top, 

40 'width': width, 

41 }, 

42 } 

43 #@+node:ekr.20220314073155.1: *4* function: json_vnode 

44 def json_vnode(v): 

45 return { 

46 'gnx': v.fileIndex, 

47 'vh': v._headString, 

48 'status': v.statusBits, 

49 'children': [json_vnode(child) for child in v.children] 

50 } 

51 #@+node:ekr.20220314071805.1: *4* function: outline_to_json 

52 def outline_to_json(c): 

53 """Return the JSON representation of c.""" 

54 positions = list(c.p.self_and_subtree()) 

55 d = { 

56 'leoHeader': {'fileFormat': 2}, 

57 'globals': json_globals(c), 

58 'tnodes': { 

59 p.v.gnx: p.v._bodyString for p in positions 

60 }, 

61 'uas': { 

62 p.v.gnx: json.dumps(p.u, skipkeys=True) for p in positions if p.u 

63 }, 

64 'vnodes': [ 

65 json_vnode(c.p.v) 

66 ], 

67 } 

68 return json.dumps(d, indent=2, sort_keys=False) 

69 #@-others 

70 c = self 

71 c.endEditing() 

72 s = outline_to_json(c) 

73 g.app.paste_c = c 

74 g.app.gui.replaceClipboardWith(s) 

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

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

77def cutOutline(self, event=None): 

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

79 c = self 

80 if c.canDeleteHeadline(): 

81 c.copyOutline() 

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

83 c.recolor() 

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

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

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

87 """ 

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

89 Nodes do *not* retain their original identify. 

90 """ 

91 c = self 

92 if s is None: 

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

94 c.endEditing() 

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

96 return None # This should never happen. 

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

98 if not isLeo: 

99 return None 

100 # Get *position* to be pasted. 

101 pasted = c.fileCommands.getLeoOutlineFromClipboard(s) 

102 if not pasted: 

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

104 return None 

105 # Validate. 

106 c.validateOutline() 

107 c.checkOutline() 

108 # Handle the "before" data for undo. 

109 if undoFlag: 

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

111 pasteAsClone=False, 

112 copiedBunchList=[], 

113 ) 

114 # Paste the node into the outline. 

115 c.selectPosition(pasted) 

116 pasted.setDirty() 

117 c.setChanged() 

118 back = pasted.back() 

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

120 pasted.moveToNthChildOf(back, 0) 

121 # Finish the command. 

122 if undoFlag: 

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

124 c.redraw(pasted) 

125 c.recolor() 

126 return pasted 

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

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

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

130 """ 

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

132 Nodes *retain* their original identify. 

133 """ 

134 c = self 

135 if s is None: 

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

137 c.endEditing() 

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

139 return None # This should never happen. 

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

141 if not isLeo: 

142 return None 

143 # Get *position* to be pasted. 

144 pasted = c.fileCommands.getLeoOutlineFromClipboardRetainingClones(s) 

145 if not pasted: 

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

147 return None 

148 # Validate. 

149 c.validateOutline() 

150 c.checkOutline() 

151 # Handle the "before" data for undo. 

152 if undoFlag: 

153 vnodeInfoDict = computeVnodeInfoDict(c) 

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

155 pasteAsClone=True, 

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

157 ) 

158 # Paste the node into the outline. 

159 c.selectPosition(pasted) 

160 pasted.setDirty() 

161 c.setChanged() 

162 back = pasted.back() 

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

164 pasted.moveToNthChildOf(back, 0) 

165 pasted.setDirty() 

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

167 for p in pasted.self_and_subtree(): 

168 p.setAllAncestorAtFileNodesDirty() 

169 # Finish the command. 

170 if undoFlag: 

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

172 c.redraw(pasted) 

173 c.recolor() 

174 return pasted 

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

176def computeCopiedBunchList(c, pasted, vnodeInfoDict): 

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

178 d = {} 

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

180 d[p.v] = p.v 

181 aList = [] 

182 for v in vnodeInfoDict: 

183 if d.get(v): 

184 bunch = vnodeInfoDict.get(v) 

185 aList.append(bunch) 

186 return aList 

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

188def computeVnodeInfoDict(c): 

189 """ 

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

191 everything. This is expensive, but foolproof. 

192 

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

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

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

196 """ 

197 d = {} 

198 for v in c.all_unique_nodes(): 

199 if v not in d: 

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

201 return d 

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

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

204def pasteAsTemplate(self, event=None): 

205 c = self 

206 p = c.p 

207 #@+others 

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

209 def skip_root(v): 

210 """ 

211 generates v nodes in the outline order 

212 but skips a subtree of the node with root_gnx 

213 """ 

214 if v.gnx != root_gnx: 

215 yield v 

216 for ch in v.children: 

217 yield from skip_root(ch) 

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

219 def translate_gnx(gnx): 

220 """ 

221 allocates a new gnx for all nodes that 

222 are not found outside copied tree 

223 """ 

224 if gnx in outside: 

225 return gnx 

226 return g.app.nodeIndices.computeNewIndex() 

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

228 def viter(parent_gnx, xv): 

229 """ 

230 iterates <v> nodes generating tuples: 

231 

232 (parent_gnx, child_gnx, headline, body) 

233 

234 skipping the descendants of already seen nodes. 

235 """ 

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

237 b = bodies[chgnx] 

238 gnx = translation.get(chgnx) 

239 if gnx in seen: 

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

241 else: 

242 seen.add(gnx) 

243 h = xv[0].text 

244 heads[gnx] = h 

245 yield parent_gnx, gnx, h, b 

246 for xch in xv[1:]: 

247 yield from viter(gnx, xch) 

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

249 gnx2v = c.fileCommands.gnxDict 

250 def getv(gnx): 

251 """ 

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

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

254 """ 

255 v = gnx2v.get(gnx) 

256 if v is None: 

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

258 return v, False 

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

260 def do_paste(vpar, index): 

261 """ 

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

263 """ 

264 vpargnx = vpar.gnx 

265 # the first node is inserted at the given index 

266 # and the rest are just appended at parents children 

267 # to achieve this we first create a generator object 

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

269 

270 # then we just take first tuple 

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

272 

273 # create vnode 

274 v, _ = getv(gnx) 

275 v.h = h 

276 v.b = b 

277 

278 # and finally insert it at the given index 

279 vpar.children.insert(index, v) 

280 v.parents.append(vpar) 

281 

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

283 

284 # now we iterate the rest of tuples 

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

286 

287 # get or create a child `v` 

288 v, isNew = getv(gnx) 

289 if isNew: 

290 v.h = h 

291 v.b = b 

292 ua = uas.get(gnx) 

293 if ua: 

294 v.unknownAttributes = ua 

295 # get parent node `vpar` 

296 vpar = getv(pgnx)[0] 

297 

298 # and link them 

299 vpar.children.append(v) 

300 v.parents.append(vpar) 

301 

302 return pasted 

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

304 def undoHelper(): 

305 v = vpar.children.pop(index) 

306 v.parents.remove(vpar) 

307 c.redraw(bunch.p) 

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

309 def redoHelper(): 

310 vpar.children.insert(index, pasted) 

311 pasted.parents.append(vpar) 

312 c.redraw(newp) 

313 #@-others 

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

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

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

317 

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

319 

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

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

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

323 

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

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

326 

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

328 

329 heads = {} 

330 

331 bunch = c.undoer.createCommonBunch(p) 

332 #@+<< prepare destination data >> 

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

334 # destination data consists of 

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

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

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

338 # 

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

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

341 if p.isExpanded(): 

342 # paste as a first child of current position 

343 vpar = p.v 

344 index = 0 

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

346 else: 

347 # paste after the current position 

348 parStack = p.stack 

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

350 index = p._childIndex + 1 

351 

352 #@-<< prepare destination data >> 

353 

354 pasted = do_paste(vpar, index) 

355 

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

357 

358 bunch.undoHelper = undoHelper 

359 bunch.redoHelper = redoHelper 

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

361 

362 newp.setDirty() 

363 c.undoer.pushBead(bunch) 

364 c.redraw(newp) 

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

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

367def dumpOutline(self, event=None): 

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

369 c = self 

370 seen = {} 

371 print('') 

372 print('=' * 40) 

373 v = c.hiddenRootNode 

374 v.dump() 

375 seen[v] = True 

376 for p in c.all_positions(): 

377 if p.v not in seen: 

378 seen[p.v] = True 

379 p.v.dump() 

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

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

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

383def contractAllHeadlinesCommand(self, event=None): 

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

385 # The helper does all the work. 

386 c = self 

387 c.contractAllHeadlines() 

388 c.redraw() 

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

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

391def contractAllOtherNodes(self, event=None): 

392 """ 

393 Contract all nodes except those needed to make the 

394 presently selected node visible. 

395 """ 

396 c = self 

397 leaveOpen = c.p 

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

399 contractIfNotCurrent(c, p, leaveOpen) 

400 c.redraw() 

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

402def contractIfNotCurrent(c, p, leaveOpen): 

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

404 p.contract() 

405 for child in p.children(): 

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

407 contractIfNotCurrent(c, child, leaveOpen) 

408 else: 

409 for p2 in child.self_and_subtree(): 

410 p2.contract() 

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

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

413def contractAllSubheads(self, event=None): 

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

415 c, p = self, self.p 

416 if not p: 

417 return 

418 child = p.firstChild() 

419 c.contractSubtree(p) 

420 while child: 

421 c.contractSubtree(child) 

422 child = child.next() 

423 c.redraw(p) 

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

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

426def contractNode(self, event=None): 

427 """Contract the presently selected node.""" 

428 c = self 

429 p = c.p 

430 c.endEditing() 

431 p.contract() 

432 c.redraw_after_contract(p) 

433 c.selectPosition(p) 

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

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

436def contractNodeOrGoToParent(self, event=None): 

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

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

439 parent = p.parent() 

440 redraw = False 

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

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

443 c.contractNode() 

444 elif parent and parent.isVisible(c): 

445 # Contract all children first. 

446 if c.collapse_on_lt_arrow: 

447 for child in parent.children(): 

448 if child.isExpanded(): 

449 child.contract() 

450 if child.hasChildren(): 

451 redraw = True 

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

453 pass 

454 else: 

455 c.goToParent() 

456 if redraw: 

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

458 c.redraw() 

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

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

461def contractParent(self, event=None): 

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

463 c = self 

464 c.endEditing() 

465 p = c.p 

466 parent = p.parent() 

467 if not parent: 

468 return 

469 parent.contract() 

470 c.redraw_after_contract(p=parent) 

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

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

473def expandAllHeadlines(self, event=None): 

474 """Expand all headlines. 

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

476 c = self 

477 c.endEditing() 

478 p = c.rootPosition() 

479 while p: 

480 c.expandSubtree(p) 

481 p.moveToNext() 

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

483 c.expansionLevel = 0 # Reset expansion level. 

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

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

486def expandAllSubheads(self, event=None): 

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

488 c, p = self, self.p 

489 if not p: 

490 return 

491 child = p.firstChild() 

492 c.expandSubtree(p) 

493 while child: 

494 c.expandSubtree(child) 

495 child = child.next() 

496 c.redraw(p) 

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

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

499def expandLevel1(self, event=None): 

500 """Expand the outline to level 1""" 

501 self.expandToLevel(1) 

502 

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

504def expandLevel2(self, event=None): 

505 """Expand the outline to level 2""" 

506 self.expandToLevel(2) 

507 

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

509def expandLevel3(self, event=None): 

510 """Expand the outline to level 3""" 

511 self.expandToLevel(3) 

512 

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

514def expandLevel4(self, event=None): 

515 """Expand the outline to level 4""" 

516 self.expandToLevel(4) 

517 

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

519def expandLevel5(self, event=None): 

520 """Expand the outline to level 5""" 

521 self.expandToLevel(5) 

522 

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

524def expandLevel6(self, event=None): 

525 """Expand the outline to level 6""" 

526 self.expandToLevel(6) 

527 

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

529def expandLevel7(self, event=None): 

530 """Expand the outline to level 7""" 

531 self.expandToLevel(7) 

532 

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

534def expandLevel8(self, event=None): 

535 """Expand the outline to level 8""" 

536 self.expandToLevel(8) 

537 

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

539def expandLevel9(self, event=None): 

540 """Expand the outline to level 9""" 

541 self.expandToLevel(9) 

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

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

544def expandNextLevel(self, event=None): 

545 """ 

546 Increase the expansion level of the outline and 

547 Expand all nodes at that level or lower. 

548 """ 

549 c = self 

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

551 if c.expansionNode != c.p: 

552 c.expansionLevel = 1 

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

554 self.expandToLevel(c.expansionLevel + 1) 

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

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

557def expandNode(self, event=None): 

558 """Expand the presently selected node.""" 

559 c = self 

560 p = c.p 

561 c.endEditing() 

562 p.expand() 

563 c.redraw_after_expand(p) 

564 c.selectPosition(p) 

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

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

567def expandNodeAndGoToFirstChild(self, event=None): 

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

569 c, p = self, self.p 

570 c.endEditing() 

571 if p.hasChildren(): 

572 if not p.isExpanded(): 

573 c.expandNode() 

574 c.selectPosition(p.firstChild()) 

575 c.treeFocusHelper() 

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

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

578def expandNodeOrGoToFirstChild(self, event=None): 

579 """ 

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

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

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

583 Otherwise, expand c.p. 

584 """ 

585 c, p = self, self.p 

586 c.endEditing() 

587 if p.hasChildren(): 

588 if p.isExpanded(): 

589 c.redraw_after_expand(p.firstChild()) 

590 else: 

591 c.expandNode() 

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

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

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

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

596 c = self 

597 level = 1 

598 if p: 

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

600 root = c.p 

601 for p in c.all_unique_positions(): 

602 p.v.expandedPositions = [] 

603 p.v.contract() 

604 for p in root.parents(): 

605 p.expand() 

606 level += 1 

607 c.expansionLevel = level # Reset expansion level. 

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

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

610def expandPrevLevel(self, event=None): 

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

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

613 c = self 

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

615 if c.expansionNode != c.p: 

616 c.expansionLevel = 1 

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

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

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

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

621def fullCheckOutline(self, event=None): 

622 """ 

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

624 

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

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

627 """ 

628 c = self 

629 return c.checkOutline(check_links=True) 

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

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

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

633def findNextClone(self, event=None): 

634 """Select the next cloned node.""" 

635 c, p = self, self.p 

636 cc = c.chapterController 

637 if not p: 

638 return 

639 if p.isCloned(): 

640 p.moveToThreadNext() 

641 flag = False 

642 while p: 

643 if p.isCloned(): 

644 flag = True 

645 break 

646 else: 

647 p.moveToThreadNext() 

648 if flag: 

649 if cc: 

650 cc.selectChapterByName('main') 

651 c.selectPosition(p) 

652 c.redraw_after_select(p) 

653 else: 

654 g.blue('no more clones') 

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

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

657def goNextVisitedNode(self, event=None): 

658 """Select the next visited node.""" 

659 c = self 

660 p = c.nodeHistory.goNext() 

661 if p: 

662 c.nodeHistory.skipBeadUpdate = True 

663 try: 

664 c.selectPosition(p) 

665 finally: 

666 c.nodeHistory.skipBeadUpdate = False 

667 c.redraw_after_select(p) 

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

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

670def goPrevVisitedNode(self, event=None): 

671 """Select the previously visited node.""" 

672 c = self 

673 p = c.nodeHistory.goPrev() 

674 if p: 

675 c.nodeHistory.skipBeadUpdate = True 

676 try: 

677 c.selectPosition(p) 

678 finally: 

679 c.nodeHistory.skipBeadUpdate = False 

680 c.redraw_after_select(p) 

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

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

683def goToFirstNode(self, event=None): 

684 """ 

685 Select the first node of the entire outline. 

686 

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

688 if Leo is hoisted or within a chapter. 

689 """ 

690 c = self 

691 p = c.rootPosition() 

692 c.expandOnlyAncestorsOfNode(p=p) 

693 c.redraw() 

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

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

696def goToFirstSibling(self, event=None): 

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

698 c, p = self, self.p 

699 if p.hasBack(): 

700 while p.hasBack(): 

701 p.moveToBack() 

702 c.treeSelectHelper(p) 

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

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

705def goToFirstVisibleNode(self, event=None): 

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

707 c = self 

708 p = c.firstVisible() 

709 if p: 

710 if c.sparse_goto_visible: 

711 c.expandOnlyAncestorsOfNode(p=p) 

712 else: 

713 c.treeSelectHelper(p) 

714 c.redraw() 

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

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

717def goToLastNode(self, event=None): 

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

719 c = self 

720 p = c.rootPosition() 

721 while p and p.hasThreadNext(): 

722 p.moveToThreadNext() 

723 c.expandOnlyAncestorsOfNode(p=p) 

724 c.redraw() 

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

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

727def goToLastSibling(self, event=None): 

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

729 c, p = self, self.p 

730 if p.hasNext(): 

731 while p.hasNext(): 

732 p.moveToNext() 

733 c.treeSelectHelper(p) 

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

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

736def goToLastVisibleNode(self, event=None): 

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

738 c = self 

739 p = c.lastVisible() 

740 if p: 

741 if c.sparse_goto_visible: 

742 c.expandOnlyAncestorsOfNode(p=p) 

743 else: 

744 c.treeSelectHelper(p) 

745 c.redraw() 

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

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

748def goToNextClone(self, event=None): 

749 """ 

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

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

752 """ 

753 c, p = self, self.p 

754 cc = c.chapterController 

755 if not p: 

756 return 

757 if not p.isCloned(): 

758 c.findNextClone() 

759 return 

760 v = p.v 

761 p.moveToThreadNext() 

762 wrapped = False 

763 while 1: 

764 if p and p.v == v: 

765 break 

766 elif p: 

767 p.moveToThreadNext() 

768 elif wrapped: 

769 break 

770 else: 

771 wrapped = True 

772 p = c.rootPosition() 

773 if p: 

774 c.expandAllAncestors(p) 

775 if cc: 

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

777 chapter = cc.getSelectedChapter() 

778 old_name = chapter and chapter.name 

779 new_name = cc.findChapterNameForPosition(p) 

780 if new_name != old_name: 

781 cc.selectChapterByName(new_name) 

782 # Always do a full redraw. 

783 c.redraw(p) 

784 else: 

785 g.blue('done') 

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

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

788def goToNextDirtyHeadline(self, event=None): 

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

790 c, p = self, self.p 

791 if not p: 

792 return 

793 p.moveToThreadNext() 

794 wrapped = False 

795 while 1: 

796 if p and p.isDirty(): 

797 break 

798 elif p: 

799 p.moveToThreadNext() 

800 elif wrapped: 

801 break 

802 else: 

803 wrapped = True 

804 p = c.rootPosition() 

805 if not p: 

806 g.blue('done') 

807 c.treeSelectHelper(p) # Sets focus. 

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

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

810def goToNextMarkedHeadline(self, event=None): 

811 """Select the next marked node.""" 

812 c, p = self, self.p 

813 if not p: 

814 return 

815 p.moveToThreadNext() 

816 wrapped = False 

817 while 1: 

818 if p and p.isMarked(): 

819 break 

820 elif p: 

821 p.moveToThreadNext() 

822 elif wrapped: 

823 break 

824 else: 

825 wrapped = True 

826 p = c.rootPosition() 

827 if not p: 

828 g.blue('done') 

829 c.treeSelectHelper(p) # Sets focus. 

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

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

832def goToNextSibling(self, event=None): 

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

834 c, p = self, self.p 

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

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

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

838def goToParent(self, event=None): 

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

840 c, p = self, self.p 

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

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

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

844def goToPrevMarkedHeadline(self, event=None): 

845 """Select the next marked node.""" 

846 c, p = self, self.p 

847 if not p: 

848 return 

849 p.moveToThreadBack() 

850 wrapped = False 

851 while 1: 

852 if p and p.isMarked(): 

853 break 

854 elif p: 

855 p.moveToThreadBack() 

856 elif wrapped: 

857 break 

858 else: 

859 wrapped = True 

860 p = c.rootPosition() 

861 if not p: 

862 g.blue('done') 

863 c.treeSelectHelper(p) # Sets focus. 

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

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

866def goToPrevSibling(self, event=None): 

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

868 c, p = self, self.p 

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

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

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

872def selectThreadBack(self, event=None): 

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

874 c, p = self, self.p 

875 if not p: 

876 return 

877 p.moveToThreadBack() 

878 c.treeSelectHelper(p) 

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

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

881def selectThreadNext(self, event=None): 

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

883 c, p = self, self.p 

884 if not p: 

885 return 

886 p.moveToThreadNext() 

887 c.treeSelectHelper(p) 

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

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

890def selectVisBack(self, event=None): 

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

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

893 c, p = self, self.p 

894 if not p: 

895 return 

896 if c.canSelectVisBack(): 

897 p.moveToVisBack(c) 

898 c.treeSelectHelper(p) 

899 else: 

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

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

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

903def selectVisNext(self, event=None): 

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

905 c, p = self, self.p 

906 if not p: 

907 return 

908 if c.canSelectVisNext(): 

909 p.moveToVisNext(c) 

910 c.treeSelectHelper(p) 

911 else: 

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

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

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

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

916@g.commander_command('dehoist') 

917def dehoist(self, event=None): 

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

919 c = self 

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

921 return 

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

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

924 if not g.unitTesting: 

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

926 return 

927 bunch = c.hoistStack.pop() 

928 p = bunch.p 

929 # Checks 'expanded' property, which was preserved by 'hoist' method 

930 if bunch.expanded: 

931 p.expand() 

932 else: 

933 p.contract() 

934 c.setCurrentPosition(p) 

935 c.redraw() 

936 c.frame.clearStatusLine() 

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

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

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

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

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

942def clearAllHoists(self, event=None): 

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

944 c = self 

945 c.hoistStack = [] 

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

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

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

949@g.commander_command('hoist') 

950def hoist(self, event=None): 

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

952 c = self 

953 p = c.p 

954 if not p: 

955 return 

956 # Don't hoist an @chapter node. 

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

958 if not g.unitTesting: 

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

960 return 

961 # Remember the expansion state. 

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

963 c.hoistStack.append(bunch) 

964 p.expand() 

965 c.redraw(p) 

966 c.frame.clearStatusLine() 

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

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

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

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

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

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

973def clone(self, event=None): 

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

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

976 if not p: 

977 return None 

978 undoData = c.undoer.beforeCloneNode(p) 

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

980 clone = p.clone() 

981 clone.setDirty() 

982 c.setChanged() 

983 if c.validateOutline(): 

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

985 c.redraw(clone) 

986 c.treeWantsFocus() 

987 return clone # For mod_labels and chapters plugins. 

988 clone.doDelete() 

989 c.setCurrentPosition(p) 

990 return None 

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

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

993def cloneToAtSpot(self, event=None): 

994 """ 

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

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

997 """ 

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

999 if not p: 

1000 return 

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

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

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

1004 return 

1005 last_spot = None 

1006 for p2 in c.all_positions(): 

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

1008 last_spot = p2.copy() 

1009 if not last_spot: 

1010 last = c.lastTopLevel() 

1011 last_spot = last.insertAfter() 

1012 last_spot.h = '@spot' 

1013 undoData = c.undoer.beforeCloneNode(p) 

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

1015 clone = p.copy() 

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

1017 clone.setDirty() 

1018 c.setChanged() 

1019 if c.validateOutline(): 

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

1021 c.contractAllHeadlines() 

1022 c.redraw(clone) 

1023 else: 

1024 clone.doDelete() 

1025 c.setCurrentPosition(p) 

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

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

1028def cloneToLastNode(self, event=None): 

1029 """ 

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

1031 Do *not* change the selected node. 

1032 """ 

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

1034 if not p: 

1035 return 

1036 prev = p.copy() 

1037 undoData = c.undoer.beforeCloneNode(p) 

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

1039 clone = p.clone() 

1040 last = c.rootPosition() 

1041 while last and last.hasNext(): 

1042 last.moveToNext() 

1043 clone.moveAfter(last) 

1044 clone.setDirty() 

1045 c.setChanged() 

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

1047 c.redraw(prev) 

1048 # return clone # For mod_labels and chapters plugins. 

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

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

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

1052 """Deletes the selected outline.""" 

1053 c, u = self, self.undoer 

1054 p = c.p 

1055 if not p: 

1056 return 

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

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

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

1060 if p.hasVisNext(c): 

1061 newNode = p.visNext(c) 

1062 elif p.hasParent(): 

1063 newNode = p.parent() 

1064 else: 

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

1066 else: 

1067 # Legacy: select previous node if possible. 

1068 if p.hasVisBack(c): 

1069 newNode = p.visBack(c) 

1070 else: 

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

1072 if not newNode: 

1073 return 

1074 undoData = u.beforeDeleteNode(p) 

1075 p.setDirty() 

1076 p.doDelete(newNode) 

1077 c.setChanged() 

1078 u.afterDeleteNode(newNode, op_name, undoData) 

1079 c.redraw(newNode) 

1080 c.validateOutline() 

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

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

1083def insertChild(self, event=None): 

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

1085 c = self 

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

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

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

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

1090 """ 

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

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

1093 

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

1095 """ 

1096 c = self 

1097 # Fix #600. 

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

1099 

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

1101def insertNodeAsFirstChild(self, event=None): 

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

1103 c = self 

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

1105 

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

1107def insertNodeAsLastChild(self, event=None): 

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

1109 c = self 

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

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

1112def insertHeadlineHelper(c, 

1113 event=None, 

1114 op_name="Insert Node", 

1115 as_child=False, 

1116 as_first_child=False, 

1117 as_last_child=False, 

1118): 

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

1120 u = c.undoer 

1121 current = c.p 

1122 if not current: 

1123 return None 

1124 c.endEditing() 

1125 undoData = c.undoer.beforeInsertNode(current) 

1126 if as_first_child: 

1127 p = current.insertAsNthChild(0) 

1128 elif as_last_child: 

1129 p = current.insertAsLastChild() 

1130 elif ( 

1131 as_child or 

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

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

1134 ): 

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

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

1137 p = current.insertAsLastChild() 

1138 else: 

1139 p = current.insertAsNthChild(0) 

1140 else: 

1141 p = current.insertAfter() 

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

1143 p.setDirty() 

1144 c.setChanged() 

1145 u.afterInsertNode(p, op_name, undoData) 

1146 c.redrawAndEdit(p, selectAll=True) 

1147 return p 

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

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

1150def insertHeadlineBefore(self, event=None): 

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

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

1153 op_name = 'Insert Node Before' 

1154 if not current: 

1155 return None 

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

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

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

1159 return None 

1160 c.endEditing() 

1161 undoData = u.beforeInsertNode(current) 

1162 p = current.insertBefore() 

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

1164 p.setDirty() 

1165 c.setChanged() 

1166 u.afterInsertNode(p, op_name, undoData) 

1167 c.redrawAndEdit(p, selectAll=True) 

1168 return p 

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

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

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

1172def cloneMarked(self, event=None): 

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

1174 c, u = self, self.undoer 

1175 p1 = c.p.copy() 

1176 # Create a new node to hold clones. 

1177 parent = p1.insertAfter() 

1178 parent.h = 'Clones of marked nodes' 

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

1180 while p: 

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

1182 if p == parent: 

1183 p.moveToNodeAfterTree() 

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

1185 cloned.append(p.v) 

1186 if 0: # old code 

1187 # Calling p.clone would cause problems 

1188 p.clone().moveToLastChildOf(parent) 

1189 else: # New code. 

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

1191 p2 = p.copy() 

1192 n = parent.numberOfChildren() 

1193 p2._linkAsNthChild(parent, n) 

1194 p.moveToNodeAfterTree() 

1195 n += 1 

1196 else: 

1197 p.moveToThreadNext() 

1198 if n: 

1199 c.setChanged() 

1200 parent.expand() 

1201 c.selectPosition(parent) 

1202 u.afterCloneMarkedNodes(p1) 

1203 else: 

1204 parent.doDelete() 

1205 c.selectPosition(p1) 

1206 if not g.unitTesting: 

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

1208 c.redraw() 

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

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

1211def copyMarked(self, event=None): 

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

1213 c, u = self, self.undoer 

1214 p1 = c.p.copy() 

1215 # Create a new node to hold clones. 

1216 parent = p1.insertAfter() 

1217 parent.h = 'Copies of marked nodes' 

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

1219 while p: 

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

1221 if p == parent: 

1222 p.moveToNodeAfterTree() 

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

1224 copied.append(p.v) 

1225 p2 = p.copyWithNewVnodes(copyMarked=True) 

1226 p2._linkAsNthChild(parent, n) 

1227 p.moveToNodeAfterTree() 

1228 n += 1 

1229 else: 

1230 p.moveToThreadNext() 

1231 if n: 

1232 c.setChanged() 

1233 parent.expand() 

1234 c.selectPosition(parent) 

1235 u.afterCopyMarkedNodes(p1) 

1236 else: 

1237 parent.doDelete() 

1238 c.selectPosition(p1) 

1239 if not g.unitTesting: 

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

1241 c.redraw() 

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

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

1244def deleteMarked(self, event=None): 

1245 """Delete all marked nodes.""" 

1246 c, u = self, self.undoer 

1247 p1 = c.p.copy() 

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

1249 while p: 

1250 if p.isMarked(): 

1251 undo_data.append(p.copy()) 

1252 next = p.positionAfterDeletedTree() 

1253 p.doDelete() 

1254 p = next 

1255 else: 

1256 p.moveToThreadNext() 

1257 if undo_data: 

1258 u.afterDeleteMarkedNodes(undo_data, p1) 

1259 if not g.unitTesting: 

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

1261 c.setChanged() 

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

1263 c.contractAllHeadlines() 

1264 c.redraw(c.rootPosition()) 

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

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

1267def moveMarked(self, event=None): 

1268 """ 

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

1270 This command is not undoable. 

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

1272 """ 

1273 c = self 

1274 p1 = c.p.copy() 

1275 # Check for marks. 

1276 for v in c.all_unique_nodes(): 

1277 if v.isMarked(): 

1278 break 

1279 else: 

1280 g.warning('no marked nodes') 

1281 return 

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

1283 'Move Marked Nodes?', 

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

1285 ) 

1286 if result == 'no': 

1287 return 

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

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

1290 parent = createMoveMarkedNode(c) 

1291 assert not parent.isMarked() 

1292 moved = [] 

1293 p = c.rootPosition() 

1294 while p: 

1295 assert parent == c.rootPosition() 

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

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

1298 moved.append(p.copy()) 

1299 next = p.positionAfterDeletedTree() 

1300 p.moveToLastChildOf(parent) # This does not change parent's position. 

1301 p = next 

1302 else: 

1303 p.moveToThreadNext() 

1304 if moved: 

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

1306 # Such a position may not exist. 

1307 p2 = c.rootPosition() 

1308 while p2: 

1309 if p2 == parent: 

1310 p2.moveToNodeAfterTree() 

1311 elif p2.v == p1.v: 

1312 break 

1313 else: 

1314 p2.moveToThreadNext() 

1315 else: 

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

1317 p2 = c.lastTopLevel() 

1318 parent.moveAfter(p2) 

1319 # u.afterMoveMarkedNodes(moved, p1) 

1320 if not g.unitTesting: 

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

1322 c.setChanged() 

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

1324 c.redraw(parent) 

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

1326def createMoveMarkedNode(c): 

1327 oldRoot = c.rootPosition() 

1328 p = oldRoot.insertAfter() 

1329 p.h = 'Moved marked nodes' 

1330 p.moveToRoot() 

1331 return p 

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

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

1334def markChangedHeadlines(self, event=None): 

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

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

1337 undoType = 'Mark Changed' 

1338 c.endEditing() 

1339 u.beforeChangeGroup(current, undoType) 

1340 for p in c.all_unique_positions(): 

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

1342 bunch = u.beforeMark(p, undoType) 

1343 # c.setMarked calls a hook. 

1344 c.setMarked(p) 

1345 p.setDirty() 

1346 c.setChanged() 

1347 u.afterMark(p, undoType, bunch) 

1348 u.afterChangeGroup(current, undoType) 

1349 if not g.unitTesting: 

1350 g.blue('done') 

1351 c.redraw_after_icons_changed() 

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

1353def markChangedRoots(self, event=None): 

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

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

1356 undoType = 'Mark Changed' 

1357 c.endEditing() 

1358 u.beforeChangeGroup(current, undoType) 

1359 for p in c.all_unique_positions(): 

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

1361 s = p.b 

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

1363 if flag: 

1364 bunch = u.beforeMark(p, undoType) 

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

1366 p.setDirty() 

1367 c.setChanged() 

1368 u.afterMark(p, undoType, bunch) 

1369 u.afterChangeGroup(current, undoType) 

1370 if not g.unitTesting: 

1371 g.blue('done') 

1372 c.redraw_after_icons_changed() 

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

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

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

1376def markHeadline(self, event=None): 

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

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

1379 if not p: 

1380 return 

1381 c.endEditing() 

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

1383 bunch = u.beforeMark(p, undoType) 

1384 # c.set/clearMarked call a hook. 

1385 if p.isMarked(): 

1386 c.clearMarked(p) 

1387 else: 

1388 c.setMarked(p) 

1389 p.setDirty() 

1390 c.setChanged() 

1391 u.afterMark(p, undoType, bunch) 

1392 c.redraw_after_icons_changed() 

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

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

1395def markSubheads(self, event=None): 

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

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

1398 undoType = 'Mark Subheads' 

1399 if not current: 

1400 return 

1401 c.endEditing() 

1402 u.beforeChangeGroup(current, undoType) 

1403 for p in current.children(): 

1404 if not p.isMarked(): 

1405 bunch = u.beforeMark(p, undoType) 

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

1407 p.setDirty() 

1408 c.setChanged() 

1409 u.afterMark(p, undoType, bunch) 

1410 u.afterChangeGroup(current, undoType) 

1411 c.redraw_after_icons_changed() 

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

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

1414def unmarkAll(self, event=None): 

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

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

1417 undoType = 'Unmark All' 

1418 if not current: 

1419 return 

1420 c.endEditing() 

1421 u.beforeChangeGroup(current, undoType) 

1422 changed = False 

1423 p = None # To keep pylint happy. 

1424 for p in c.all_unique_positions(): 

1425 if p.isMarked(): 

1426 bunch = u.beforeMark(p, undoType) 

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

1428 p.v.clearMarked() 

1429 p.setDirty() 

1430 u.afterMark(p, undoType, bunch) 

1431 changed = True 

1432 if changed: 

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

1434 c.setChanged() 

1435 u.afterChangeGroup(current, undoType) 

1436 c.redraw_after_icons_changed() 

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

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

1439@g.commander_command('demote') 

1440def demote(self, event=None): 

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

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

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

1444 c.treeFocusHelper() 

1445 return 

1446 # Make sure all the moves will be valid. 

1447 next = p.next() 

1448 while next: 

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

1450 c.treeFocusHelper() 

1451 return 

1452 next.moveToNext() 

1453 c.endEditing() 

1454 parent_v = p._parentVnode() 

1455 n = p.childIndex() 

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

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

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

1459 # Add the moved nodes to p's children 

1460 p.v.children.extend(followingSibs) 

1461 # Adjust the parent links in the moved nodes. 

1462 # There is no need to adjust descendant links. 

1463 for child in followingSibs: 

1464 child.parents.remove(parent_v) 

1465 child.parents.append(p.v) 

1466 p.expand() 

1467 p.setDirty() 

1468 c.setChanged() 

1469 u.afterDemote(p, followingSibs) 

1470 c.redraw(p) 

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

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

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

1474def moveOutlineDown(self, event=None): 

1475 """Move the selected node down.""" 

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

1477 # move p to be a child of itself. 

1478 # 

1479 # An important optimization: 

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

1481 # the moved node remains the same. 

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

1483 if not p: 

1484 return 

1485 if not c.canMoveOutlineDown(): 

1486 if c.hoistStack: 

1487 cantMoveMessage(c) 

1488 c.treeFocusHelper() 

1489 return 

1490 parent = p.parent() 

1491 next = p.visNext(c) 

1492 while next and p.isAncestorOf(next): 

1493 next = next.visNext(c) 

1494 if not next: 

1495 if c.hoistStack: 

1496 cantMoveMessage(c) 

1497 c.treeFocusHelper() 

1498 return 

1499 c.endEditing() 

1500 undoData = u.beforeMoveNode(p) 

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

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

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

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

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

1506 if moved: 

1507 p.setDirty() 

1508 p.moveToNthChildOf(next, 0) 

1509 else: 

1510 # Attempt to move p after next. 

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

1512 if moved: 

1513 p.setDirty() 

1514 p.moveAfter(next) 

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

1516 if ( 

1517 c.collapse_nodes_after_move 

1518 and moved and c.sparse_move 

1519 and parent and not parent.isAncestorOf(p) 

1520 ): 

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

1522 parent.contract() 

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

1524 if moved: 

1525 p.setDirty() 

1526 c.setChanged() 

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

1528 c.redraw(p) 

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

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

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

1532def moveOutlineLeft(self, event=None): 

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

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

1535 if not p: 

1536 return 

1537 if not c.canMoveOutlineLeft(): 

1538 if c.hoistStack: 

1539 cantMoveMessage(c) 

1540 c.treeFocusHelper() 

1541 return 

1542 if not p.hasParent(): 

1543 c.treeFocusHelper() 

1544 return 

1545 parent = p.parent() 

1546 c.endEditing() 

1547 undoData = u.beforeMoveNode(p) 

1548 p.setDirty() 

1549 p.moveAfter(parent) 

1550 p.setDirty() 

1551 c.setChanged() 

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

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

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

1555 parent.contract() 

1556 c.redraw(p) 

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

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

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

1560def moveOutlineRight(self, event=None): 

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

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

1563 if not p: 

1564 return 

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

1566 if c.hoistStack: 

1567 cantMoveMessage(c) 

1568 c.treeFocusHelper() 

1569 return 

1570 back = p.back() 

1571 if not back: 

1572 c.treeFocusHelper() 

1573 return 

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

1575 c.treeFocusHelper() 

1576 return 

1577 c.endEditing() 

1578 undoData = u.beforeMoveNode(p) 

1579 p.setDirty() 

1580 n = back.numberOfChildren() 

1581 p.moveToNthChildOf(back, n) 

1582 p.setDirty() 

1583 c.setChanged() # #2036. 

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

1585 c.redraw(p) 

1586 c.recolor() 

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

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

1589def moveOutlineUp(self, event=None): 

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

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

1592 if not p: 

1593 return 

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

1595 if c.hoistStack: 

1596 cantMoveMessage(c) 

1597 c.treeFocusHelper() 

1598 return 

1599 back = p.visBack(c) 

1600 if not back: 

1601 return 

1602 back2 = back.visBack(c) 

1603 c.endEditing() 

1604 undoData = u.beforeMoveNode(p) 

1605 moved = False 

1606 #@+<< Move p up >> 

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

1608 parent = p.parent() 

1609 if not back2: 

1610 if c.hoistStack: # hoist or chapter. 

1611 limit, limitIsVisible = c.visLimit() 

1612 assert limit 

1613 if limitIsVisible: 

1614 # canMoveOutlineUp should have caught this. 

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

1616 else: 

1617 moved = True 

1618 p.setDirty() 

1619 p.moveToFirstChildOf(limit) 

1620 else: 

1621 # p will be the new root node 

1622 p.setDirty() 

1623 p.moveToRoot() 

1624 moved = True 

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

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

1627 moved = True 

1628 p.setDirty() 

1629 p.moveToNthChildOf(back2, 0) 

1630 else: 

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

1632 moved = True 

1633 p.setDirty() 

1634 p.moveAfter(back2) 

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

1636 if ( 

1637 c.collapse_nodes_after_move 

1638 and moved and c.sparse_move 

1639 and parent and not parent.isAncestorOf(p) 

1640 ): 

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

1642 parent.contract() 

1643 #@-<< Move p up >> 

1644 if moved: 

1645 p.setDirty() 

1646 c.setChanged() 

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

1648 c.redraw(p) 

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

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

1651@g.commander_command('promote') 

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

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

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

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

1656 c.treeFocusHelper() 

1657 return 

1658 c.endEditing() 

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

1660 p.promote() 

1661 c.setChanged() 

1662 if undoFlag: 

1663 p.setDirty() 

1664 u.afterPromote(p, children) 

1665 c.redraw(p) 

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

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

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

1669def toggleSparseMove(self, event=None): 

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

1671 c = self 

1672 c.sparse_move = not c.sparse_move 

1673 if not g.unitTesting: 

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

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

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

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

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

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

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

1681 c, p = self, self.p 

1682 if p and p.hasChildren(): 

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

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

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

1686def sortSiblings(self, event=None, 

1687 # cmp keyword is no longer supported. 

1688 key=None, 

1689 p=None, 

1690 sortChildren=False, 

1691 reverse=False 

1692): 

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

1694 c, u = self, self.undoer 

1695 if not p: 

1696 p = c.p 

1697 if not p: 

1698 return 

1699 c.endEditing() 

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

1701 parent_v = p._parentVnode() 

1702 oldChildren = parent_v.children[:] 

1703 newChildren = parent_v.children[:] 

1704 if key is None: 

1705 

1706 def lowerKey(self): 

1707 return self.h.lower() 

1708 

1709 key = lowerKey 

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

1711 if oldChildren == newChildren: 

1712 return 

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

1714 c.setChanged() 

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

1716 parent_v.children = newChildren 

1717 u.afterSort(p, bunch) 

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

1719 p = c.setPositionAfterSort(sortChildren) 

1720 if p.parent(): 

1721 p.parent().setDirty() 

1722 c.redraw(p) 

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

1724def cantMoveMessage(c): 

1725 h = c.rootPosition().h 

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

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

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

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

1730def count_children(event=None): 

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

1732 if c: 

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

1734#@-others 

1735#@-leo