Coverage for C:\leo.repo\leo-editor\leo\commands\commanderOutlineCommands.py: 42%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1220 statements  

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 = {x.gnx for x in skip_root(c.hiddenRootNode)} 

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

323 

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

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

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 c.expandOnlyAncestorsOfNode(p=p) 

711 c.redraw() 

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

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

714def goToLastNode(self, event=None): 

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

716 c = self 

717 p = c.rootPosition() 

718 while p and p.hasThreadNext(): 

719 p.moveToThreadNext() 

720 c.expandOnlyAncestorsOfNode(p=p) 

721 c.redraw() 

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

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

724def goToLastSibling(self, event=None): 

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

726 c, p = self, self.p 

727 if p.hasNext(): 

728 while p.hasNext(): 

729 p.moveToNext() 

730 c.treeSelectHelper(p) 

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

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

733def goToLastVisibleNode(self, event=None): 

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

735 c = self 

736 p = c.lastVisible() 

737 if p: 

738 c.expandOnlyAncestorsOfNode(p=p) 

739 c.redraw() 

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

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

742def goToNextClone(self, event=None): 

743 """ 

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

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

746 """ 

747 c, p = self, self.p 

748 cc = c.chapterController 

749 if not p: 

750 return 

751 if not p.isCloned(): 

752 c.findNextClone() 

753 return 

754 v = p.v 

755 p.moveToThreadNext() 

756 wrapped = False 

757 while 1: 

758 if p and p.v == v: 

759 break 

760 elif p: 

761 p.moveToThreadNext() 

762 elif wrapped: 

763 break 

764 else: 

765 wrapped = True 

766 p = c.rootPosition() 

767 if p: 

768 c.expandAllAncestors(p) 

769 if cc: 

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

771 chapter = cc.getSelectedChapter() 

772 old_name = chapter and chapter.name 

773 new_name = cc.findChapterNameForPosition(p) 

774 if new_name != old_name: 

775 cc.selectChapterByName(new_name) 

776 # Always do a full redraw. 

777 c.redraw(p) 

778 else: 

779 g.blue('done') 

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

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

782def goToNextDirtyHeadline(self, event=None): 

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

784 c, p = self, self.p 

785 if not p: 

786 return 

787 p.moveToThreadNext() 

788 wrapped = False 

789 while 1: 

790 if p and p.isDirty(): 

791 break 

792 elif p: 

793 p.moveToThreadNext() 

794 elif wrapped: 

795 break 

796 else: 

797 wrapped = True 

798 p = c.rootPosition() 

799 if not p: 

800 g.blue('done') 

801 c.treeSelectHelper(p) # Sets focus. 

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

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

804def goToNextMarkedHeadline(self, event=None): 

805 """Select the next marked node.""" 

806 c, p = self, self.p 

807 if not p: 

808 return 

809 p.moveToThreadNext() 

810 wrapped = False 

811 while 1: 

812 if p and p.isMarked(): 

813 break 

814 elif p: 

815 p.moveToThreadNext() 

816 elif wrapped: 

817 break 

818 else: 

819 wrapped = True 

820 p = c.rootPosition() 

821 if not p: 

822 g.blue('done') 

823 c.treeSelectHelper(p) # Sets focus. 

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

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

826def goToNextSibling(self, event=None): 

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

828 c, p = self, self.p 

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

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

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

832def goToParent(self, event=None): 

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

834 c, p = self, self.p 

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

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

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

838def goToPrevMarkedHeadline(self, event=None): 

839 """Select the next marked node.""" 

840 c, p = self, self.p 

841 if not p: 

842 return 

843 p.moveToThreadBack() 

844 wrapped = False 

845 while 1: 

846 if p and p.isMarked(): 

847 break 

848 elif p: 

849 p.moveToThreadBack() 

850 elif wrapped: 

851 break 

852 else: 

853 wrapped = True 

854 p = c.rootPosition() 

855 if not p: 

856 g.blue('done') 

857 c.treeSelectHelper(p) # Sets focus. 

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

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

860def goToPrevSibling(self, event=None): 

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

862 c, p = self, self.p 

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

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

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

866def selectThreadBack(self, event=None): 

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

868 c, p = self, self.p 

869 if not p: 

870 return 

871 p.moveToThreadBack() 

872 c.treeSelectHelper(p) 

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

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

875def selectThreadNext(self, event=None): 

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

877 c, p = self, self.p 

878 if not p: 

879 return 

880 p.moveToThreadNext() 

881 c.treeSelectHelper(p) 

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

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

884def selectVisBack(self, event=None): 

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

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

887 c, p = self, self.p 

888 if not p: 

889 return 

890 if c.canSelectVisBack(): 

891 p.moveToVisBack(c) 

892 c.treeSelectHelper(p) 

893 else: 

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

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

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

897def selectVisNext(self, event=None): 

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

899 c, p = self, self.p 

900 if not p: 

901 return 

902 if c.canSelectVisNext(): 

903 p.moveToVisNext(c) 

904 c.treeSelectHelper(p) 

905 else: 

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

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

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

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

910@g.commander_command('dehoist') 

911def dehoist(self, event=None): 

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

913 c = self 

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

915 return 

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

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

918 if not g.unitTesting: 

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

920 return 

921 bunch = c.hoistStack.pop() 

922 p = bunch.p 

923 if not p: 

924 p.expand() 

925 else: 

926 p.contract() 

927 c.setCurrentPosition(p) 

928 c.redraw() 

929 c.frame.clearStatusLine() 

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

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

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

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

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

935def clearAllHoists(self, event=None): 

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

937 c = self 

938 c.hoistStack = [] 

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

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

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

942@g.commander_command('hoist') 

943def hoist(self, event=None): 

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

945 c = self 

946 p = c.p 

947 if not p: 

948 return 

949 # Don't hoist an @chapter node. 

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

951 if not g.unitTesting: 

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

953 return 

954 # Remember the expansion state. 

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

956 c.hoistStack.append(bunch) 

957 p.expand() 

958 c.redraw(p) 

959 c.frame.clearStatusLine() 

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

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

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

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

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

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

966def clone(self, event=None): 

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

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

969 if not p: 

970 return None 

971 undoData = c.undoer.beforeCloneNode(p) 

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

973 clone = p.clone() 

974 clone.setDirty() 

975 c.setChanged() 

976 if c.validateOutline(): 

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

978 c.redraw(clone) 

979 c.treeWantsFocus() 

980 return clone # For mod_labels and chapters plugins. 

981 clone.doDelete() 

982 c.setCurrentPosition(p) 

983 return None 

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

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

986def cloneToAtSpot(self, event=None): 

987 """ 

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

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

990 """ 

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

992 if not p: 

993 return 

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

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

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

997 return 

998 last_spot = None 

999 for p2 in c.all_positions(): 

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

1001 last_spot = p2.copy() 

1002 if not last_spot: 

1003 last = c.lastTopLevel() 

1004 last_spot = last.insertAfter() 

1005 last_spot.h = '@spot' 

1006 undoData = c.undoer.beforeCloneNode(p) 

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

1008 clone = p.copy() 

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

1010 clone.setDirty() 

1011 c.setChanged() 

1012 if c.validateOutline(): 

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

1014 c.contractAllHeadlines() 

1015 c.redraw(clone) 

1016 else: 

1017 clone.doDelete() 

1018 c.setCurrentPosition(p) 

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

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

1021def cloneToLastNode(self, event=None): 

1022 """ 

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

1024 Do *not* change the selected node. 

1025 """ 

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

1027 if not p: 

1028 return 

1029 prev = p.copy() 

1030 undoData = c.undoer.beforeCloneNode(p) 

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

1032 clone = p.clone() 

1033 last = c.rootPosition() 

1034 while last and last.hasNext(): 

1035 last.moveToNext() 

1036 clone.moveAfter(last) 

1037 clone.setDirty() 

1038 c.setChanged() 

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

1040 c.redraw(prev) 

1041 # return clone # For mod_labels and chapters plugins. 

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

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

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

1045 """Deletes the selected outline.""" 

1046 c, u = self, self.undoer 

1047 p = c.p 

1048 if not p: 

1049 return 

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

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

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

1053 if p.hasVisNext(c): 

1054 newNode = p.visNext(c) 

1055 elif p.hasParent(): 

1056 newNode = p.parent() 

1057 else: 

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

1059 else: 

1060 # Legacy: select previous node if possible. 

1061 if p.hasVisBack(c): 

1062 newNode = p.visBack(c) 

1063 else: 

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

1065 if not newNode: 

1066 return 

1067 undoData = u.beforeDeleteNode(p) 

1068 p.setDirty() 

1069 p.doDelete(newNode) 

1070 c.setChanged() 

1071 u.afterDeleteNode(newNode, op_name, undoData) 

1072 c.redraw(newNode) 

1073 c.validateOutline() 

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

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

1076def insertChild(self, event=None): 

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

1078 c = self 

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

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

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

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

1083 """ 

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

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

1086  

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

1088 """ 

1089 c = self 

1090 # Fix #600. 

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

1092 

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

1094def insertNodeAsFirstChild(self, event=None): 

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

1096 c = self 

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

1098 

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

1100def insertNodeAsLastChild(self, event=None): 

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

1102 c = self 

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

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

1105def insertHeadlineHelper(c, 

1106 event=None, 

1107 op_name="Insert Node", 

1108 as_child=False, 

1109 as_first_child=False, 

1110 as_last_child=False, 

1111): 

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

1113 u = c.undoer 

1114 current = c.p 

1115 if not current: 

1116 return None 

1117 c.endEditing() 

1118 undoData = c.undoer.beforeInsertNode(current) 

1119 if as_first_child: 

1120 p = current.insertAsNthChild(0) 

1121 elif as_last_child: 

1122 p = current.insertAsLastChild() 

1123 elif ( 

1124 as_child or 

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

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

1127 ): 

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

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

1130 p = current.insertAsLastChild() 

1131 else: 

1132 p = current.insertAsNthChild(0) 

1133 else: 

1134 p = current.insertAfter() 

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

1136 p.setDirty() 

1137 c.setChanged() 

1138 u.afterInsertNode(p, op_name, undoData) 

1139 c.redrawAndEdit(p, selectAll=True) 

1140 return p 

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

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

1143def insertHeadlineBefore(self, event=None): 

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

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

1146 op_name = 'Insert Node Before' 

1147 if not current: 

1148 return None 

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

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

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

1152 return None 

1153 c.endEditing() 

1154 undoData = u.beforeInsertNode(current) 

1155 p = current.insertBefore() 

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

1157 p.setDirty() 

1158 c.setChanged() 

1159 u.afterInsertNode(p, op_name, undoData) 

1160 c.redrawAndEdit(p, selectAll=True) 

1161 return p 

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

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

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

1165def cloneMarked(self, event=None): 

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

1167 c, u = self, self.undoer 

1168 p1 = c.p.copy() 

1169 # Create a new node to hold clones. 

1170 parent = p1.insertAfter() 

1171 parent.h = 'Clones of marked nodes' 

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

1173 while p: 

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

1175 if p == parent: 

1176 p.moveToNodeAfterTree() 

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

1178 cloned.append(p.v) 

1179 if 0: # old code 

1180 # Calling p.clone would cause problems 

1181 p.clone().moveToLastChildOf(parent) 

1182 else: # New code. 

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

1184 p2 = p.copy() 

1185 n = parent.numberOfChildren() 

1186 p2._linkAsNthChild(parent, n) 

1187 p.moveToNodeAfterTree() 

1188 n += 1 

1189 else: 

1190 p.moveToThreadNext() 

1191 if n: 

1192 c.setChanged() 

1193 parent.expand() 

1194 c.selectPosition(parent) 

1195 u.afterCloneMarkedNodes(p1) 

1196 else: 

1197 parent.doDelete() 

1198 c.selectPosition(p1) 

1199 if not g.unitTesting: 

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

1201 c.redraw() 

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

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

1204def copyMarked(self, event=None): 

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

1206 c, u = self, self.undoer 

1207 p1 = c.p.copy() 

1208 # Create a new node to hold clones. 

1209 parent = p1.insertAfter() 

1210 parent.h = 'Copies of marked nodes' 

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

1212 while p: 

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

1214 if p == parent: 

1215 p.moveToNodeAfterTree() 

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

1217 copied.append(p.v) 

1218 p2 = p.copyWithNewVnodes(copyMarked=True) 

1219 p2._linkAsNthChild(parent, n) 

1220 p.moveToNodeAfterTree() 

1221 n += 1 

1222 else: 

1223 p.moveToThreadNext() 

1224 if n: 

1225 c.setChanged() 

1226 parent.expand() 

1227 c.selectPosition(parent) 

1228 u.afterCopyMarkedNodes(p1) 

1229 else: 

1230 parent.doDelete() 

1231 c.selectPosition(p1) 

1232 if not g.unitTesting: 

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

1234 c.redraw() 

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

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

1237def deleteMarked(self, event=None): 

1238 """Delete all marked nodes.""" 

1239 c, u = self, self.undoer 

1240 p1 = c.p.copy() 

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

1242 while p: 

1243 if p.isMarked(): 

1244 undo_data.append(p.copy()) 

1245 next = p.positionAfterDeletedTree() 

1246 p.doDelete() 

1247 p = next 

1248 else: 

1249 p.moveToThreadNext() 

1250 if undo_data: 

1251 u.afterDeleteMarkedNodes(undo_data, p1) 

1252 if not g.unitTesting: 

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

1254 c.setChanged() 

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

1256 c.contractAllHeadlines() 

1257 c.redraw(c.rootPosition()) 

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

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

1260def moveMarked(self, event=None): 

1261 """ 

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

1263 This command is not undoable. 

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

1265 """ 

1266 c = self 

1267 p1 = c.p.copy() 

1268 # Check for marks. 

1269 for v in c.all_unique_nodes(): 

1270 if v.isMarked(): 

1271 break 

1272 else: 

1273 g.warning('no marked nodes') 

1274 return 

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

1276 'Move Marked Nodes?', 

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

1278 ) 

1279 if result == 'no': 

1280 return 

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

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

1283 parent = createMoveMarkedNode(c) 

1284 assert not parent.isMarked() 

1285 moved = [] 

1286 p = c.rootPosition() 

1287 while p: 

1288 assert parent == c.rootPosition() 

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

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

1291 moved.append(p.copy()) 

1292 next = p.positionAfterDeletedTree() 

1293 p.moveToLastChildOf(parent) 

1294 # This does not change parent's position. 

1295 p = next 

1296 else: 

1297 p.moveToThreadNext() 

1298 if moved: 

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

1300 # Such a position may not exist. 

1301 p2 = c.rootPosition() 

1302 while p2: 

1303 if p2 == parent: 

1304 p2.moveToNodeAfterTree() 

1305 elif p2.v == p1.v: 

1306 break 

1307 else: 

1308 p2.moveToThreadNext() 

1309 else: 

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

1311 p2 = c.lastTopLevel() 

1312 parent.moveAfter(p2) 

1313 # u.afterMoveMarkedNodes(moved, p1) 

1314 if not g.unitTesting: 

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

1316 c.setChanged() 

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

1318 c.redraw(parent) 

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

1320def createMoveMarkedNode(c): 

1321 oldRoot = c.rootPosition() 

1322 p = oldRoot.insertAfter() 

1323 p.h = 'Moved marked nodes' 

1324 p.moveToRoot() 

1325 return p 

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

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

1328def markChangedHeadlines(self, event=None): 

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

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

1331 undoType = 'Mark Changed' 

1332 c.endEditing() 

1333 u.beforeChangeGroup(current, undoType) 

1334 for p in c.all_unique_positions(): 

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

1336 bunch = u.beforeMark(p, undoType) 

1337 # c.setMarked calls a hook. 

1338 c.setMarked(p) 

1339 p.setDirty() 

1340 c.setChanged() 

1341 u.afterMark(p, undoType, bunch) 

1342 u.afterChangeGroup(current, undoType) 

1343 if not g.unitTesting: 

1344 g.blue('done') 

1345 c.redraw_after_icons_changed() 

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

1347def markChangedRoots(self, event=None): 

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

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

1350 undoType = 'Mark Changed' 

1351 c.endEditing() 

1352 u.beforeChangeGroup(current, undoType) 

1353 for p in c.all_unique_positions(): 

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

1355 s = p.b 

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

1357 if flag: 

1358 bunch = u.beforeMark(p, undoType) 

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

1360 p.setDirty() 

1361 c.setChanged() 

1362 u.afterMark(p, undoType, bunch) 

1363 u.afterChangeGroup(current, undoType) 

1364 if not g.unitTesting: 

1365 g.blue('done') 

1366 c.redraw_after_icons_changed() 

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

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

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

1370def markHeadline(self, event=None): 

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

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

1373 if not p: 

1374 return 

1375 c.endEditing() 

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

1377 bunch = u.beforeMark(p, undoType) 

1378 # c.set/clearMarked call a hook. 

1379 if p.isMarked(): 

1380 c.clearMarked(p) 

1381 else: 

1382 c.setMarked(p) 

1383 p.setDirty() 

1384 c.setChanged() 

1385 u.afterMark(p, undoType, bunch) 

1386 c.redraw_after_icons_changed() 

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

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

1389def markSubheads(self, event=None): 

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

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

1392 undoType = 'Mark Subheads' 

1393 if not current: 

1394 return 

1395 c.endEditing() 

1396 u.beforeChangeGroup(current, undoType) 

1397 for p in current.children(): 

1398 if not p.isMarked(): 

1399 bunch = u.beforeMark(p, undoType) 

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

1401 p.setDirty() 

1402 c.setChanged() 

1403 u.afterMark(p, undoType, bunch) 

1404 u.afterChangeGroup(current, undoType) 

1405 c.redraw_after_icons_changed() 

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

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

1408def unmarkAll(self, event=None): 

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

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

1411 undoType = 'Unmark All' 

1412 if not current: 

1413 return 

1414 c.endEditing() 

1415 u.beforeChangeGroup(current, undoType) 

1416 changed = False 

1417 p = None # To keep pylint happy. 

1418 for p in c.all_unique_positions(): 

1419 if p.isMarked(): 

1420 bunch = u.beforeMark(p, undoType) 

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

1422 p.v.clearMarked() 

1423 p.setDirty() 

1424 u.afterMark(p, undoType, bunch) 

1425 changed = True 

1426 if changed: 

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

1428 c.setChanged() 

1429 u.afterChangeGroup(current, undoType) 

1430 c.redraw_after_icons_changed() 

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

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

1433@g.commander_command('demote') 

1434def demote(self, event=None): 

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

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

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

1438 c.treeFocusHelper() 

1439 return 

1440 # Make sure all the moves will be valid. 

1441 next = p.next() 

1442 while next: 

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

1444 c.treeFocusHelper() 

1445 return 

1446 next.moveToNext() 

1447 c.endEditing() 

1448 parent_v = p._parentVnode() 

1449 n = p.childIndex() 

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

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

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

1453 # Add the moved nodes to p's children 

1454 p.v.children.extend(followingSibs) 

1455 # Adjust the parent links in the moved nodes. 

1456 # There is no need to adjust descendant links. 

1457 for child in followingSibs: 

1458 child.parents.remove(parent_v) 

1459 child.parents.append(p.v) 

1460 p.expand() 

1461 p.setDirty() 

1462 c.setChanged() 

1463 u.afterDemote(p, followingSibs) 

1464 c.redraw(p) 

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

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

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

1468def moveOutlineDown(self, event=None): 

1469 """Move the selected node down.""" 

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

1471 # move p to be a child of itself. 

1472 # 

1473 # An important optimization: 

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

1475 # the moved node remains the same. 

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

1477 if not p: 

1478 return 

1479 if not c.canMoveOutlineDown(): 

1480 if c.hoistStack: 

1481 cantMoveMessage(c) 

1482 c.treeFocusHelper() 

1483 return 

1484 parent = p.parent() 

1485 next = p.visNext(c) 

1486 while next and p.isAncestorOf(next): 

1487 next = next.visNext(c) 

1488 if not next: 

1489 if c.hoistStack: 

1490 cantMoveMessage(c) 

1491 c.treeFocusHelper() 

1492 return 

1493 c.endEditing() 

1494 undoData = u.beforeMoveNode(p) 

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

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

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

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

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

1500 if moved: 

1501 p.setDirty() 

1502 p.moveToNthChildOf(next, 0) 

1503 else: 

1504 # Attempt to move p after next. 

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

1506 if moved: 

1507 p.setDirty() 

1508 p.moveAfter(next) 

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

1510 if ( 

1511 c.collapse_nodes_after_move 

1512 and moved and c.sparse_move 

1513 and parent and not parent.isAncestorOf(p) 

1514 ): 

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

1516 parent.contract() 

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

1518 if moved: 

1519 p.setDirty() 

1520 c.setChanged() 

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

1522 c.redraw(p) 

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

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

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

1526def moveOutlineLeft(self, event=None): 

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

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

1529 if not p: 

1530 return 

1531 if not c.canMoveOutlineLeft(): 

1532 if c.hoistStack: 

1533 cantMoveMessage(c) 

1534 c.treeFocusHelper() 

1535 return 

1536 if not p.hasParent(): 

1537 c.treeFocusHelper() 

1538 return 

1539 parent = p.parent() 

1540 c.endEditing() 

1541 undoData = u.beforeMoveNode(p) 

1542 p.setDirty() 

1543 p.moveAfter(parent) 

1544 p.setDirty() 

1545 c.setChanged() 

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

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

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

1549 parent.contract() 

1550 c.redraw(p) 

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

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

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

1554def moveOutlineRight(self, event=None): 

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

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

1557 if not p: 

1558 return 

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

1560 if c.hoistStack: 

1561 cantMoveMessage(c) 

1562 c.treeFocusHelper() 

1563 return 

1564 back = p.back() 

1565 if not back: 

1566 c.treeFocusHelper() 

1567 return 

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

1569 c.treeFocusHelper() 

1570 return 

1571 c.endEditing() 

1572 undoData = u.beforeMoveNode(p) 

1573 p.setDirty() 

1574 n = back.numberOfChildren() 

1575 p.moveToNthChildOf(back, n) 

1576 p.setDirty() 

1577 c.setChanged() # #2036. 

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

1579 c.redraw(p) 

1580 c.recolor() 

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

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

1583def moveOutlineUp(self, event=None): 

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

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

1586 if not p: 

1587 return 

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

1589 if c.hoistStack: 

1590 cantMoveMessage(c) 

1591 c.treeFocusHelper() 

1592 return 

1593 back = p.visBack(c) 

1594 if not back: 

1595 return 

1596 back2 = back.visBack(c) 

1597 c.endEditing() 

1598 undoData = u.beforeMoveNode(p) 

1599 moved = False 

1600 #@+<< Move p up >> 

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

1602 parent = p.parent() 

1603 if not back2: 

1604 if c.hoistStack: # hoist or chapter. 

1605 limit, limitIsVisible = c.visLimit() 

1606 assert limit 

1607 if limitIsVisible: 

1608 # canMoveOutlineUp should have caught this. 

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

1610 else: 

1611 moved = True 

1612 p.setDirty() 

1613 p.moveToFirstChildOf(limit) 

1614 else: 

1615 # p will be the new root node 

1616 p.setDirty() 

1617 p.moveToRoot() 

1618 moved = True 

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

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

1621 moved = True 

1622 p.setDirty() 

1623 p.moveToNthChildOf(back2, 0) 

1624 else: 

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

1626 moved = True 

1627 p.setDirty() 

1628 p.moveAfter(back2) 

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

1630 if ( 

1631 c.collapse_nodes_after_move 

1632 and moved and c.sparse_move 

1633 and parent and not parent.isAncestorOf(p) 

1634 ): 

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

1636 parent.contract() 

1637 #@-<< Move p up >> 

1638 if moved: 

1639 p.setDirty() 

1640 c.setChanged() 

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

1642 c.redraw(p) 

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

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

1645@g.commander_command('promote') 

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

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

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

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

1650 c.treeFocusHelper() 

1651 return 

1652 c.endEditing() 

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

1654 p.promote() 

1655 c.setChanged() 

1656 if undoFlag: 

1657 p.setDirty() 

1658 u.afterPromote(p, children) 

1659 c.redraw(p) 

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

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

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

1663def toggleSparseMove(self, event=None): 

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

1665 c = self 

1666 c.sparse_move = not c.sparse_move 

1667 if not g.unitTesting: 

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

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

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

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

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

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

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

1675 c, p = self, self.p 

1676 if p and p.hasChildren(): 

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

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

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

1680def sortSiblings(self, event=None, 

1681 # cmp keyword is no longer supported. 

1682 key=None, 

1683 p=None, 

1684 sortChildren=False, 

1685 reverse=False 

1686): 

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

1688 c, u = self, self.undoer 

1689 if not p: 

1690 p = c.p 

1691 if not p: 

1692 return 

1693 c.endEditing() 

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

1695 parent_v = p._parentVnode() 

1696 oldChildren = parent_v.children[:] 

1697 newChildren = parent_v.children[:] 

1698 if key is None: 

1699 

1700 def lowerKey(self): 

1701 return self.h.lower() 

1702 

1703 key = lowerKey 

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

1705 if oldChildren == newChildren: 

1706 return 

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

1708 c.setChanged() 

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

1710 parent_v.children = newChildren 

1711 u.afterSort(p, bunch) 

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

1713 p = c.setPositionAfterSort(sortChildren) 

1714 if p.parent(): 

1715 p.parent().setDirty() 

1716 c.redraw(p) 

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

1718def cantMoveMessage(c): 

1719 h = c.rootPosition().h 

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

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

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

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

1724def count_children(event=None): 

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

1726 if c: 

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

1728#@-others 

1729#@-leo