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

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# -*- coding: utf-8 -*-
2#@+leo-ver=5-thin
3#@+node:ekr.20171124080430.1: * @file ../commands/commanderOutlineCommands.py
4#@@first
5"""Outline commands that used to be defined in leoCommands.py"""
6import xml.etree.ElementTree as ElementTree
7from leo.core import leoGlobals as g
8from leo.core import leoNodes
9from leo.core import leoFileCommands
10#@+others
11#@+node:ekr.20031218072017.1548: ** c_oc.Cut & Paste Outlines
12#@+node:ekr.20031218072017.1550: *3* c_oc.copyOutline
13@g.commander_command('copy-node')
14def copyOutline(self, event=None):
15 """Copy the selected outline to the clipboard."""
16 # Copying an outline has no undo consequences.
17 c = self
18 c.endEditing()
19 s = c.fileCommands.outline_to_clipboard_string()
20 g.app.paste_c = c
21 g.app.gui.replaceClipboardWith(s)
22#@+node:ekr.20031218072017.1549: *3* c_oc.cutOutline
23@g.commander_command('cut-node')
24def cutOutline(self, event=None):
25 """Delete the selected outline and send it to the clipboard."""
26 c = self
27 if c.canDeleteHeadline():
28 c.copyOutline()
29 c.deleteOutline(op_name="Cut Node")
30 c.recolor()
31#@+node:ekr.20031218072017.1551: *3* c_oc.pasteOutline
32@g.commander_command('paste-node')
33def pasteOutline(self, event=None, s=None, undoFlag=True):
34 """
35 Paste an outline into the present outline from the clipboard.
36 Nodes do *not* retain their original identify.
37 """
38 c = self
39 if s is None:
40 s = g.app.gui.getTextFromClipboard()
41 c.endEditing()
42 if not s or not c.canPasteOutline(s):
43 return None # This should never happen.
44 isLeo = g.match(s, 0, g.app.prolog_prefix_string)
45 if not isLeo:
46 return None
47 # Get *position* to be pasted.
48 pasted = c.fileCommands.getLeoOutlineFromClipboard(s)
49 if not pasted:
50 # Leo no longer supports MORE outlines. Use import-MORE-files instead.
51 return None
52 # Validate.
53 c.validateOutline()
54 c.checkOutline()
55 # Handle the "before" data for undo.
56 if undoFlag:
57 undoData = c.undoer.beforeInsertNode(c.p,
58 pasteAsClone=False,
59 copiedBunchList=[],
60 )
61 # Paste the node into the outline.
62 c.selectPosition(pasted)
63 pasted.setDirty()
64 c.setChanged()
65 back = pasted.back()
66 if back and back.hasChildren() and back.isExpanded():
67 pasted.moveToNthChildOf(back, 0)
68 # Finish the command.
69 if undoFlag:
70 c.undoer.afterInsertNode(pasted, 'Paste Node', undoData)
71 c.redraw(pasted)
72 c.recolor()
73 return pasted
74#@+node:EKR.20040610130943: *3* c_oc.pasteOutlineRetainingClones & helpers
75@g.commander_command('paste-retaining-clones')
76def pasteOutlineRetainingClones(self, event=None, s=None, undoFlag=True):
77 """
78 Paste an outline into the present outline from the clipboard.
79 Nodes *retain* their original identify.
80 """
81 c = self
82 if s is None:
83 s = g.app.gui.getTextFromClipboard()
84 c.endEditing()
85 if not s or not c.canPasteOutline(s):
86 return None # This should never happen.
87 isLeo = g.match(s, 0, g.app.prolog_prefix_string)
88 if not isLeo:
89 return None
90 # Get *position* to be pasted.
91 pasted = c.fileCommands.getLeoOutlineFromClipboardRetainingClones(s)
92 if not pasted:
93 # Leo no longer supports MORE outlines. Use import-MORE-files instead.
94 return None
95 # Validate.
96 c.validateOutline()
97 c.checkOutline()
98 # Handle the "before" data for undo.
99 if undoFlag:
100 vnodeInfoDict = computeVnodeInfoDict(c)
101 undoData = c.undoer.beforeInsertNode(c.p,
102 pasteAsClone=True,
103 copiedBunchList=computeCopiedBunchList(c, pasted, vnodeInfoDict),
104 )
105 # Paste the node into the outline.
106 c.selectPosition(pasted)
107 pasted.setDirty()
108 c.setChanged()
109 back = pasted.back()
110 if back and back.hasChildren() and back.isExpanded():
111 pasted.moveToNthChildOf(back, 0)
112 pasted.setDirty()
113 # Set dirty bits for ancestors of *all* pasted nodes.
114 for p in pasted.self_and_subtree():
115 p.setAllAncestorAtFileNodesDirty()
116 # Finish the command.
117 if undoFlag:
118 c.undoer.afterInsertNode(pasted, 'Paste As Clone', undoData)
119 c.redraw(pasted)
120 c.recolor()
121 return pasted
122#@+node:ekr.20050418084539.2: *4* def computeCopiedBunchList
123def computeCopiedBunchList(c, pasted, vnodeInfoDict):
124 """Create a dict containing only copied vnodes."""
125 d = {}
126 for p in pasted.self_and_subtree(copy=False):
127 d[p.v] = p.v
128 aList = []
129 for v in vnodeInfoDict:
130 if d.get(v):
131 bunch = vnodeInfoDict.get(v)
132 aList.append(bunch)
133 return aList
134#@+node:ekr.20050418084539: *4* def computeVnodeInfoDict
135def computeVnodeInfoDict(c):
136 """
137 We don't know yet which nodes will be affected by the paste, so we remember
138 everything. This is expensive, but foolproof.
140 The alternative is to try to remember the 'before' values of nodes in the
141 FileCommands read logic. Several experiments failed, and the code is very ugly.
142 In short, it seems wise to do things the foolproof way.
143 """
144 d = {}
145 for v in c.all_unique_nodes():
146 if v not in d:
147 d[v] = g.Bunch(v=v, head=v.h, body=v.b)
148 return d
149#@+node:vitalije.20200529105105.1: *3* c_oc.pasteAsTemplate
150@g.commander_command('paste-as-template')
151def pasteAsTemplate(self, event=None):
152 c = self
153 p = c.p
154 #@+others
155 #@+node:vitalije.20200529112224.1: *4* skip_root
156 def skip_root(v):
157 """
158 generates v nodes in the outline order
159 but skips a subtree of the node with root_gnx
160 """
161 if v.gnx != root_gnx:
162 yield v
163 for ch in v.children:
164 yield from skip_root(ch)
165 #@+node:vitalije.20200529112459.1: *4* translate_gnx
166 def translate_gnx(gnx):
167 """
168 allocates a new gnx for all nodes that
169 are not found outside copied tree
170 """
171 if gnx in outside:
172 return gnx
173 return g.app.nodeIndices.computeNewIndex()
174 #@+node:vitalije.20200529115141.1: *4* viter
175 def viter(parent_gnx, xv):
176 """
177 iterates <v> nodes generating tuples:
179 (parent_gnx, child_gnx, headline, body)
181 skipping the descendants of already seen nodes.
182 """
183 chgnx = xv.attrib.get('t')
184 b = bodies[chgnx]
185 gnx = translation.get(chgnx)
186 if gnx in seen:
187 yield parent_gnx, gnx, heads.get(gnx), b
188 else:
189 seen.add(gnx)
190 h = xv[0].text
191 heads[gnx] = h
192 yield parent_gnx, gnx, h, b
193 for xch in xv[1:]:
194 yield from viter(gnx, xch)
195 #@+node:vitalije.20200529114857.1: *4* getv
196 gnx2v = c.fileCommands.gnxDict
197 def getv(gnx):
198 """
199 returns a pair (vnode, is_new) for the given gnx.
200 if node doesn't exist, creates a new one.
201 """
202 v = gnx2v.get(gnx)
203 if v is None:
204 return leoNodes.VNode(c, gnx), True
205 return v, False
206 #@+node:vitalije.20200529115539.1: *4* do_paste
207 def do_paste(vpar, index):
208 """
209 pastes a new node as a child of vpar at given index
210 """
211 vpargnx = vpar.gnx
212 # the first node is inserted at the given index
213 # and the rest are just appended at parents children
214 # to achieve this we first create a generator object
215 rows = viter(vpargnx, xvelements[0])
217 # then we just take first tuple
218 pgnx, gnx, h, b = next(rows)
220 # create vnode
221 v, _ = getv(gnx)
222 v.h = h
223 v.b = b
225 # and finally insert it at the given index
226 vpar.children.insert(index, v)
227 v.parents.append(vpar)
229 pasted = v # remember the first node as a return value
231 # now we iterate the rest of tuples
232 for pgnx, gnx, h, b in rows:
234 # get or create a child `v`
235 v, isNew = getv(gnx)
236 if isNew:
237 v.h = h
238 v.b = b
239 ua = uas.get(gnx)
240 if ua:
241 v.unknownAttributes = ua
242 # get parent node `vpar`
243 vpar = getv(pgnx)[0]
245 # and link them
246 vpar.children.append(v)
247 v.parents.append(vpar)
249 return pasted
250 #@+node:vitalije.20200529120440.1: *4* undoHelper
251 def undoHelper():
252 v = vpar.children.pop(index)
253 v.parents.remove(vpar)
254 c.redraw(bunch.p)
255 #@+node:vitalije.20200529120537.1: *4* redoHelper
256 def redoHelper():
257 vpar.children.insert(index, pasted)
258 pasted.parents.append(vpar)
259 c.redraw(newp)
260 #@-others
261 xroot = ElementTree.fromstring(g.app.gui.getTextFromClipboard())
262 xvelements = xroot.find('vnodes') # <v> elements.
263 xtelements = xroot.find('tnodes') # <t> elements.
265 bodies, uas = leoFileCommands.FastRead(c, {}).scanTnodes(xtelements)
267 root_gnx = xvelements[0].attrib.get('t') # the gnx of copied node
268 outside = {x.gnx for x in skip_root(c.hiddenRootNode)}
269 # outside will contain gnxes of nodes that are outside the copied tree
271 translation = {x: translate_gnx(x) for x in bodies}
272 # we generate new gnx for each node in the copied tree
274 seen = set(outside) # required for the treatment of local clones inside the copied tree
276 heads = {}
278 bunch = c.undoer.createCommonBunch(p)
279 #@+<< prepare destination data >>
280 #@+node:vitalije.20200529111500.1: *4* << prepare destination data >>
281 # destination data consists of
282 # 1. vpar --- parent v node that should receive pasted child
283 # 2. index --- at which pasted child will be
284 # 3. parStack --- a stack for creating new position of the pasted node
285 #
286 # the new position will be: Position(vpar.children[index], index, parStack)
287 # but it can't be calculated yet, before actual paste is done
288 if p.isExpanded():
289 # paste as a first child of current position
290 vpar = p.v
291 index = 0
292 parStack = p.stack + [(p.v, p._childIndex)]
293 else:
294 # paste after the current position
295 parStack = p.stack
296 vpar = p.stack[-1][0] if p.stack else c.hiddenRootNode
297 index = p._childIndex + 1
299 #@-<< prepare destination data >>
301 pasted = do_paste(vpar, index)
303 newp = leoNodes.Position(pasted, index, parStack)
305 bunch.undoHelper = undoHelper
306 bunch.redoHelper = redoHelper
307 bunch.undoType = 'paste-retaining-outside-clones'
309 newp.setDirty()
310 c.undoer.pushBead(bunch)
311 c.redraw(newp)
312#@+node:ekr.20040412060927: ** c_oc.dumpOutline
313@g.commander_command('dump-outline')
314def dumpOutline(self, event=None):
315 """ Dump all nodes in the outline."""
316 c = self
317 seen = {}
318 print('')
319 print('=' * 40)
320 v = c.hiddenRootNode
321 v.dump()
322 seen[v] = True
323 for p in c.all_positions():
324 if p.v not in seen:
325 seen[p.v] = True
326 p.v.dump()
327#@+node:ekr.20031218072017.2898: ** c_oc.Expand & contract commands
328#@+node:ekr.20031218072017.2900: *3* c_oc.contract-all
329@g.commander_command('contract-all')
330def contractAllHeadlinesCommand(self, event=None):
331 """Contract all nodes in the outline."""
332 # The helper does all the work.
333 c = self
334 c.contractAllHeadlines()
335 c.redraw()
336#@+node:ekr.20080819075811.3: *3* c_oc.contractAllOtherNodes & helper
337@g.commander_command('contract-all-other-nodes')
338def contractAllOtherNodes(self, event=None):
339 """
340 Contract all nodes except those needed to make the
341 presently selected node visible.
342 """
343 c = self
344 leaveOpen = c.p
345 for p in c.rootPosition().self_and_siblings():
346 contractIfNotCurrent(c, p, leaveOpen)
347 c.redraw()
348#@+node:ekr.20080819075811.7: *4* def contractIfNotCurrent
349def contractIfNotCurrent(c, p, leaveOpen):
350 if p == leaveOpen or not p.isAncestorOf(leaveOpen):
351 p.contract()
352 for child in p.children():
353 if child != leaveOpen and child.isAncestorOf(leaveOpen):
354 contractIfNotCurrent(c, child, leaveOpen)
355 else:
356 for p2 in child.self_and_subtree():
357 p2.contract()
358#@+node:ekr.20200824130837.1: *3* c_oc.contractAllSubheads (new)
359@g.commander_command('contract-all-subheads')
360def contractAllSubheads(self, event=None):
361 """Contract all children of the presently selected node."""
362 c, p = self, self.p
363 if not p:
364 return
365 child = p.firstChild()
366 c.contractSubtree(p)
367 while child:
368 c.contractSubtree(child)
369 child = child.next()
370 c.redraw(p)
371#@+node:ekr.20031218072017.2901: *3* c_oc.contractNode
372@g.commander_command('contract-node')
373def contractNode(self, event=None):
374 """Contract the presently selected node."""
375 c = self
376 p = c.p
377 c.endEditing()
378 p.contract()
379 c.redraw_after_contract(p)
380 c.selectPosition(p)
381#@+node:ekr.20040930064232: *3* c_oc.contractNodeOrGoToParent
382@g.commander_command('contract-or-go-left')
383def contractNodeOrGoToParent(self, event=None):
384 """Simulate the left Arrow Key in folder of Windows Explorer."""
385 c, cc, p = self, self.chapterController, self.p
386 parent = p.parent()
387 redraw = False
388 # Bug fix: 2016/04/19: test p.v.isExpanded().
389 if p.hasChildren() and (p.v.isExpanded() or p.isExpanded()):
390 c.contractNode()
391 elif parent and parent.isVisible(c):
392 # Contract all children first.
393 if c.collapse_on_lt_arrow:
394 for child in parent.children():
395 if child.isExpanded():
396 child.contract()
397 if child.hasChildren():
398 redraw = True
399 if cc and cc.inChapter and parent.h.startswith('@chapter '):
400 pass
401 else:
402 c.goToParent()
403 if redraw:
404 # A *child* should be collapsed. Do a *full* redraw.
405 c.redraw()
406#@+node:ekr.20031218072017.2902: *3* c_oc.contractParent
407@g.commander_command('contract-parent')
408def contractParent(self, event=None):
409 """Contract the parent of the presently selected node."""
410 c = self
411 c.endEditing()
412 p = c.p
413 parent = p.parent()
414 if not parent:
415 return
416 parent.contract()
417 c.redraw_after_contract(p=parent)
418#@+node:ekr.20031218072017.2903: *3* c_oc.expandAllHeadlines
419@g.commander_command('expand-all')
420def expandAllHeadlines(self, event=None):
421 """Expand all headlines.
422 Warning: this can take a long time for large outlines."""
423 c = self
424 c.endEditing()
425 p = c.rootPosition()
426 while p:
427 c.expandSubtree(p)
428 p.moveToNext()
429 c.redraw_after_expand(p=c.rootPosition())
430 c.expansionLevel = 0 # Reset expansion level.
431#@+node:ekr.20031218072017.2904: *3* c_oc.expandAllSubheads
432@g.commander_command('expand-all-subheads')
433def expandAllSubheads(self, event=None):
434 """Expand all children of the presently selected node."""
435 c, p = self, self.p
436 if not p:
437 return
438 child = p.firstChild()
439 c.expandSubtree(p)
440 while child:
441 c.expandSubtree(child)
442 child = child.next()
443 c.redraw(p)
444#@+node:ekr.20031218072017.2905: *3* c_oc.expandLevel1..9
445@g.commander_command('expand-to-level-1')
446def expandLevel1(self, event=None):
447 """Expand the outline to level 1"""
448 self.expandToLevel(1)
450@g.commander_command('expand-to-level-2')
451def expandLevel2(self, event=None):
452 """Expand the outline to level 2"""
453 self.expandToLevel(2)
455@g.commander_command('expand-to-level-3')
456def expandLevel3(self, event=None):
457 """Expand the outline to level 3"""
458 self.expandToLevel(3)
460@g.commander_command('expand-to-level-4')
461def expandLevel4(self, event=None):
462 """Expand the outline to level 4"""
463 self.expandToLevel(4)
465@g.commander_command('expand-to-level-5')
466def expandLevel5(self, event=None):
467 """Expand the outline to level 5"""
468 self.expandToLevel(5)
470@g.commander_command('expand-to-level-6')
471def expandLevel6(self, event=None):
472 """Expand the outline to level 6"""
473 self.expandToLevel(6)
475@g.commander_command('expand-to-level-7')
476def expandLevel7(self, event=None):
477 """Expand the outline to level 7"""
478 self.expandToLevel(7)
480@g.commander_command('expand-to-level-8')
481def expandLevel8(self, event=None):
482 """Expand the outline to level 8"""
483 self.expandToLevel(8)
485@g.commander_command('expand-to-level-9')
486def expandLevel9(self, event=None):
487 """Expand the outline to level 9"""
488 self.expandToLevel(9)
489#@+node:ekr.20031218072017.2906: *3* c_oc.expandNextLevel
490@g.commander_command('expand-next-level')
491def expandNextLevel(self, event=None):
492 """
493 Increase the expansion level of the outline and
494 Expand all nodes at that level or lower.
495 """
496 c = self
497 # Expansion levels are now local to a particular tree.
498 if c.expansionNode != c.p:
499 c.expansionLevel = 1
500 c.expansionNode = c.p.copy()
501 self.expandToLevel(c.expansionLevel + 1)
502#@+node:ekr.20031218072017.2907: *3* c_oc.expandNode
503@g.commander_command('expand-node')
504def expandNode(self, event=None):
505 """Expand the presently selected node."""
506 c = self
507 p = c.p
508 c.endEditing()
509 p.expand()
510 c.redraw_after_expand(p)
511 c.selectPosition(p)
512#@+node:ekr.20040930064232.1: *3* c_oc.expandNodeAndGoToFirstChild
513@g.commander_command('expand-and-go-right')
514def expandNodeAndGoToFirstChild(self, event=None):
515 """If a node has children, expand it if needed and go to the first child."""
516 c, p = self, self.p
517 c.endEditing()
518 if p.hasChildren():
519 if not p.isExpanded():
520 c.expandNode()
521 c.selectPosition(p.firstChild())
522 c.treeFocusHelper()
523#@+node:ekr.20171125082744.1: *3* c_oc.expandNodeOrGoToFirstChild
524@g.commander_command('expand-or-go-right')
525def expandNodeOrGoToFirstChild(self, event=None):
526 """
527 Simulate the Right Arrow Key in folder of Windows Explorer.
528 if c.p has no children, do nothing.
529 Otherwise, if c.p is expanded, select the first child.
530 Otherwise, expand c.p.
531 """
532 c, p = self, self.p
533 c.endEditing()
534 if p.hasChildren():
535 if p.isExpanded():
536 c.redraw_after_expand(p.firstChild())
537 else:
538 c.expandNode()
539#@+node:ekr.20060928062431: *3* c_oc.expandOnlyAncestorsOfNode
540@g.commander_command('expand-ancestors-only')
541def expandOnlyAncestorsOfNode(self, event=None, p=None):
542 """Contract all nodes in the outline."""
543 c = self
544 level = 1
545 if p:
546 c.selectPosition(p) # 2013/12/25
547 root = c.p
548 for p in c.all_unique_positions():
549 p.v.expandedPositions = []
550 p.v.contract()
551 for p in root.parents():
552 p.expand()
553 level += 1
554 c.expansionLevel = level # Reset expansion level.
555#@+node:ekr.20031218072017.2908: *3* c_oc.expandPrevLevel
556@g.commander_command('expand-prev-level')
557def expandPrevLevel(self, event=None):
558 """Decrease the expansion level of the outline and
559 Expand all nodes at that level or lower."""
560 c = self
561 # Expansion levels are now local to a particular tree.
562 if c.expansionNode != c.p:
563 c.expansionLevel = 1
564 c.expansionNode = c.p.copy()
565 self.expandToLevel(max(1, c.expansionLevel - 1))
566#@+node:ekr.20171124081846.1: ** c_oc.fullCheckOutline
567@g.commander_command('check-outline')
568def fullCheckOutline(self, event=None):
569 """
570 Performs a full check of the consistency of a .leo file.
572 As of Leo 5.1, Leo performs checks of gnx's and outline structure
573 before writes and after reads, pastes and undo/redo.
574 """
575 c = self
576 return c.checkOutline(check_links=True)
577#@+node:ekr.20031218072017.2913: ** c_oc.Goto commands
578#@+node:ekr.20071213123942: *3* c_oc.findNextClone
579@g.commander_command('find-next-clone')
580def findNextClone(self, event=None):
581 """Select the next cloned node."""
582 c, p = self, self.p
583 cc = c.chapterController
584 if not p:
585 return
586 if p.isCloned():
587 p.moveToThreadNext()
588 flag = False
589 while p:
590 if p.isCloned():
591 flag = True
592 break
593 else:
594 p.moveToThreadNext()
595 if flag:
596 if cc:
597 cc.selectChapterByName('main')
598 c.selectPosition(p)
599 c.redraw_after_select(p)
600 else:
601 g.blue('no more clones')
602#@+node:ekr.20031218072017.1628: *3* c_oc.goNextVisitedNode
603@g.commander_command('go-forward')
604def goNextVisitedNode(self, event=None):
605 """Select the next visited node."""
606 c = self
607 p = c.nodeHistory.goNext()
608 if p:
609 c.nodeHistory.skipBeadUpdate = True
610 try:
611 c.selectPosition(p)
612 finally:
613 c.nodeHistory.skipBeadUpdate = False
614 c.redraw_after_select(p)
615#@+node:ekr.20031218072017.1627: *3* c_oc.goPrevVisitedNode
616@g.commander_command('go-back')
617def goPrevVisitedNode(self, event=None):
618 """Select the previously visited node."""
619 c = self
620 p = c.nodeHistory.goPrev()
621 if p:
622 c.nodeHistory.skipBeadUpdate = True
623 try:
624 c.selectPosition(p)
625 finally:
626 c.nodeHistory.skipBeadUpdate = False
627 c.redraw_after_select(p)
628#@+node:ekr.20031218072017.2914: *3* c_oc.goToFirstNode
629@g.commander_command('goto-first-node')
630def goToFirstNode(self, event=None):
631 """
632 Select the first node of the entire outline.
634 But (#2167), go to the first node of a chapter or hoist
635 if Leo is hoisted or within a chapter.
636 """
637 c = self
638 p = c.rootPosition()
639 c.expandOnlyAncestorsOfNode(p=p)
640 c.redraw()
641#@+node:ekr.20051012092453: *3* c_oc.goToFirstSibling
642@g.commander_command('goto-first-sibling')
643def goToFirstSibling(self, event=None):
644 """Select the first sibling of the selected node."""
645 c, p = self, self.p
646 if p.hasBack():
647 while p.hasBack():
648 p.moveToBack()
649 c.treeSelectHelper(p)
650#@+node:ekr.20070615070925: *3* c_oc.goToFirstVisibleNode
651@g.commander_command('goto-first-visible-node')
652def goToFirstVisibleNode(self, event=None):
653 """Select the first visible node of the selected chapter or hoist."""
654 c = self
655 p = c.firstVisible()
656 if p:
657 c.expandOnlyAncestorsOfNode(p=p)
658 c.redraw()
659#@+node:ekr.20031218072017.2915: *3* c_oc.goToLastNode
660@g.commander_command('goto-last-node')
661def goToLastNode(self, event=None):
662 """Select the last node in the entire tree."""
663 c = self
664 p = c.rootPosition()
665 while p and p.hasThreadNext():
666 p.moveToThreadNext()
667 c.expandOnlyAncestorsOfNode(p=p)
668 c.redraw()
669#@+node:ekr.20051012092847.1: *3* c_oc.goToLastSibling
670@g.commander_command('goto-last-sibling')
671def goToLastSibling(self, event=None):
672 """Select the last sibling of the selected node."""
673 c, p = self, self.p
674 if p.hasNext():
675 while p.hasNext():
676 p.moveToNext()
677 c.treeSelectHelper(p)
678#@+node:ekr.20050711153537: *3* c_oc.goToLastVisibleNode
679@g.commander_command('goto-last-visible-node')
680def goToLastVisibleNode(self, event=None):
681 """Select the last visible node of selected chapter or hoist."""
682 c = self
683 p = c.lastVisible()
684 if p:
685 c.expandOnlyAncestorsOfNode(p=p)
686 c.redraw()
687#@+node:ekr.20031218072017.2916: *3* c_oc.goToNextClone
688@g.commander_command('goto-next-clone')
689def goToNextClone(self, event=None):
690 """
691 Select the next node that is a clone of the selected node.
692 If the selected node is not a clone, do find-next-clone.
693 """
694 c, p = self, self.p
695 cc = c.chapterController
696 if not p:
697 return
698 if not p.isCloned():
699 c.findNextClone()
700 return
701 v = p.v
702 p.moveToThreadNext()
703 wrapped = False
704 while 1:
705 if p and p.v == v:
706 break
707 elif p:
708 p.moveToThreadNext()
709 elif wrapped:
710 break
711 else:
712 wrapped = True
713 p = c.rootPosition()
714 if p:
715 c.expandAllAncestors(p)
716 if cc:
717 # #252: goto-next clone activate chapter.
718 chapter = cc.getSelectedChapter()
719 old_name = chapter and chapter.name
720 new_name = cc.findChapterNameForPosition(p)
721 if new_name != old_name:
722 cc.selectChapterByName(new_name)
723 # Always do a full redraw.
724 c.redraw(p)
725 else:
726 g.blue('done')
727#@+node:ekr.20031218072017.2917: *3* c_oc.goToNextDirtyHeadline
728@g.commander_command('goto-next-changed')
729def goToNextDirtyHeadline(self, event=None):
730 """Select the node that is marked as changed."""
731 c, p = self, self.p
732 if not p:
733 return
734 p.moveToThreadNext()
735 wrapped = False
736 while 1:
737 if p and p.isDirty():
738 break
739 elif p:
740 p.moveToThreadNext()
741 elif wrapped:
742 break
743 else:
744 wrapped = True
745 p = c.rootPosition()
746 if not p:
747 g.blue('done')
748 c.treeSelectHelper(p) # Sets focus.
749#@+node:ekr.20031218072017.2918: *3* c_oc.goToNextMarkedHeadline
750@g.commander_command('goto-next-marked')
751def goToNextMarkedHeadline(self, event=None):
752 """Select the next marked node."""
753 c, p = self, self.p
754 if not p:
755 return
756 p.moveToThreadNext()
757 wrapped = False
758 while 1:
759 if p and p.isMarked():
760 break
761 elif p:
762 p.moveToThreadNext()
763 elif wrapped:
764 break
765 else:
766 wrapped = True
767 p = c.rootPosition()
768 if not p:
769 g.blue('done')
770 c.treeSelectHelper(p) # Sets focus.
771#@+node:ekr.20031218072017.2919: *3* c_oc.goToNextSibling
772@g.commander_command('goto-next-sibling')
773def goToNextSibling(self, event=None):
774 """Select the next sibling of the selected node."""
775 c, p = self, self.p
776 c.treeSelectHelper(p and p.next())
777#@+node:ekr.20031218072017.2920: *3* c_oc.goToParent
778@g.commander_command('goto-parent')
779def goToParent(self, event=None):
780 """Select the parent of the selected node."""
781 c, p = self, self.p
782 c.treeSelectHelper(p and p.parent())
783#@+node:ekr.20190211104913.1: *3* c_oc.goToPrevMarkedHeadline
784@g.commander_command('goto-prev-marked')
785def goToPrevMarkedHeadline(self, event=None):
786 """Select the next marked node."""
787 c, p = self, self.p
788 if not p:
789 return
790 p.moveToThreadBack()
791 wrapped = False
792 while 1:
793 if p and p.isMarked():
794 break
795 elif p:
796 p.moveToThreadBack()
797 elif wrapped:
798 break
799 else:
800 wrapped = True
801 p = c.rootPosition()
802 if not p:
803 g.blue('done')
804 c.treeSelectHelper(p) # Sets focus.
805#@+node:ekr.20031218072017.2921: *3* c_oc.goToPrevSibling
806@g.commander_command('goto-prev-sibling')
807def goToPrevSibling(self, event=None):
808 """Select the previous sibling of the selected node."""
809 c, p = self, self.p
810 c.treeSelectHelper(p and p.back())
811#@+node:ekr.20031218072017.2993: *3* c_oc.selectThreadBack
812@g.commander_command('goto-prev-node')
813def selectThreadBack(self, event=None):
814 """Select the node preceding the selected node in outline order."""
815 c, p = self, self.p
816 if not p:
817 return
818 p.moveToThreadBack()
819 c.treeSelectHelper(p)
820#@+node:ekr.20031218072017.2994: *3* c_oc.selectThreadNext
821@g.commander_command('goto-next-node')
822def selectThreadNext(self, event=None):
823 """Select the node following the selected node in outline order."""
824 c, p = self, self.p
825 if not p:
826 return
827 p.moveToThreadNext()
828 c.treeSelectHelper(p)
829#@+node:ekr.20031218072017.2995: *3* c_oc.selectVisBack
830@g.commander_command('goto-prev-visible')
831def selectVisBack(self, event=None):
832 """Select the visible node preceding the presently selected node."""
833 # This has an up arrow for a control key.
834 c, p = self, self.p
835 if not p:
836 return
837 if c.canSelectVisBack():
838 p.moveToVisBack(c)
839 c.treeSelectHelper(p)
840 else:
841 c.endEditing() # 2011/05/28: A special case.
842#@+node:ekr.20031218072017.2996: *3* c_oc.selectVisNext
843@g.commander_command('goto-next-visible')
844def selectVisNext(self, event=None):
845 """Select the visible node following the presently selected node."""
846 c, p = self, self.p
847 if not p:
848 return
849 if c.canSelectVisNext():
850 p.moveToVisNext(c)
851 c.treeSelectHelper(p)
852 else:
853 c.endEditing() # 2011/05/28: A special case.
854#@+node:ekr.20031218072017.2028: ** c_oc.hoist/dehoist/clearAllHoists
855#@+node:ekr.20120308061112.9865: *3* c_oc.deHoist
856@g.commander_command('de-hoist')
857@g.commander_command('dehoist')
858def dehoist(self, event=None):
859 """Undo a previous hoist of an outline."""
860 c = self
861 if not c.p or not c.hoistStack:
862 return
863 # Don't de-hoist an @chapter node.
864 if c.chapterController and c.p.h.startswith('@chapter '):
865 if not g.unitTesting:
866 g.es('can not de-hoist an @chapter node.', color='blue')
867 return
868 bunch = c.hoistStack.pop()
869 p = bunch.p
870 if not p:
871 p.expand()
872 else:
873 p.contract()
874 c.setCurrentPosition(p)
875 c.redraw()
876 c.frame.clearStatusLine()
877 c.frame.putStatusLine("De-Hoist: " + p.h)
878 c.undoer.afterDehoist(p, 'DeHoist')
879 g.doHook('hoist-changed', c=c)
880#@+node:ekr.20120308061112.9866: *3* c_oc.clearAllHoists
881@g.commander_command('clear-all-hoists')
882def clearAllHoists(self, event=None):
883 """Undo a previous hoist of an outline."""
884 c = self
885 c.hoistStack = []
886 c.frame.putStatusLine("Hoists cleared")
887 g.doHook('hoist-changed', c=c)
888#@+node:ekr.20120308061112.9867: *3* c_oc.hoist
889@g.commander_command('hoist')
890def hoist(self, event=None):
891 """Make only the selected outline visible."""
892 c = self
893 p = c.p
894 if not p:
895 return
896 # Don't hoist an @chapter node.
897 if c.chapterController and p.h.startswith('@chapter '):
898 if not g.unitTesting:
899 g.es('can not hoist an @chapter node.', color='blue')
900 return
901 # Remember the expansion state.
902 bunch = g.Bunch(p=p.copy(), expanded=p.isExpanded())
903 c.hoistStack.append(bunch)
904 p.expand()
905 c.redraw(p)
906 c.frame.clearStatusLine()
907 c.frame.putStatusLine("Hoist: " + p.h)
908 c.undoer.afterHoist(p, 'Hoist')
909 g.doHook('hoist-changed', c=c)
910#@+node:ekr.20031218072017.1759: ** c_oc.Insert, Delete & Clone commands
911#@+node:ekr.20031218072017.1762: *3* c_oc.clone
912@g.commander_command('clone-node')
913def clone(self, event=None):
914 """Create a clone of the selected outline."""
915 c, p, u = self, self.p, self.undoer
916 if not p:
917 return None
918 undoData = c.undoer.beforeCloneNode(p)
919 c.endEditing() # Capture any changes to the headline.
920 clone = p.clone()
921 clone.setDirty()
922 c.setChanged()
923 if c.validateOutline():
924 u.afterCloneNode(clone, 'Clone Node', undoData)
925 c.redraw(clone)
926 c.treeWantsFocus()
927 return clone # For mod_labels and chapters plugins.
928 clone.doDelete()
929 c.setCurrentPosition(p)
930 return None
931#@+node:ekr.20150630152607.1: *3* c_oc.cloneToAtSpot
932@g.commander_command('clone-to-at-spot')
933def cloneToAtSpot(self, event=None):
934 """
935 Create a clone of the selected node and move it to the last @spot node
936 of the outline. Create the @spot node if necessary.
937 """
938 c, p, u = self, self.p, self.undoer
939 if not p:
940 return
941 # 2015/12/27: fix bug 220: do not allow clone-to-at-spot on @spot node.
942 if p.h.startswith('@spot'):
943 g.es("can not clone @spot node", color='red')
944 return
945 last_spot = None
946 for p2 in c.all_positions():
947 if g.match_word(p2.h, 0, '@spot'):
948 last_spot = p2.copy()
949 if not last_spot:
950 last = c.lastTopLevel()
951 last_spot = last.insertAfter()
952 last_spot.h = '@spot'
953 undoData = c.undoer.beforeCloneNode(p)
954 c.endEditing() # Capture any changes to the headline.
955 clone = p.copy()
956 clone._linkAsNthChild(last_spot, n=last_spot.numberOfChildren())
957 clone.setDirty()
958 c.setChanged()
959 if c.validateOutline():
960 u.afterCloneNode(clone, 'Clone Node', undoData)
961 c.contractAllHeadlines()
962 c.redraw(clone)
963 else:
964 clone.doDelete()
965 c.setCurrentPosition(p)
966#@+node:ekr.20141023154408.5: *3* c_oc.cloneToLastNode
967@g.commander_command('clone-node-to-last-node')
968def cloneToLastNode(self, event=None):
969 """
970 Clone the selected node and move it to the last node.
971 Do *not* change the selected node.
972 """
973 c, p, u = self, self.p, self.undoer
974 if not p:
975 return
976 prev = p.copy()
977 undoData = c.undoer.beforeCloneNode(p)
978 c.endEditing() # Capture any changes to the headline.
979 clone = p.clone()
980 last = c.rootPosition()
981 while last and last.hasNext():
982 last.moveToNext()
983 clone.moveAfter(last)
984 clone.setDirty()
985 c.setChanged()
986 u.afterCloneNode(clone, 'Clone Node To Last', undoData)
987 c.redraw(prev)
988 # return clone # For mod_labels and chapters plugins.
989#@+node:ekr.20031218072017.1193: *3* c_oc.deleteOutline
990@g.commander_command('delete-node')
991def deleteOutline(self, event=None, op_name="Delete Node"):
992 """Deletes the selected outline."""
993 c, u = self, self.undoer
994 p = c.p
995 if not p:
996 return
997 c.endEditing() # Make sure we capture the headline for Undo.
998 if False: # c.config.getBool('select-next-after-delete'):
999 # #721: Optionally select next node after delete.
1000 if p.hasVisNext(c):
1001 newNode = p.visNext(c)
1002 elif p.hasParent():
1003 newNode = p.parent()
1004 else:
1005 newNode = p.back() # _not_ p.visBack(): we are at the top level.
1006 else:
1007 # Legacy: select previous node if possible.
1008 if p.hasVisBack(c):
1009 newNode = p.visBack(c)
1010 else:
1011 newNode = p.next() # _not_ p.visNext(): we are at the top level.
1012 if not newNode:
1013 return
1014 undoData = u.beforeDeleteNode(p)
1015 p.setDirty()
1016 p.doDelete(newNode)
1017 c.setChanged()
1018 u.afterDeleteNode(newNode, op_name, undoData)
1019 c.redraw(newNode)
1020 c.validateOutline()
1021#@+node:ekr.20071005173203.1: *3* c_oc.insertChild
1022@g.commander_command('insert-child')
1023def insertChild(self, event=None):
1024 """Insert a node after the presently selected node."""
1025 c = self
1026 return c.insertHeadline(event=event, op_name='Insert Child', as_child=True)
1027#@+node:ekr.20031218072017.1761: *3* c_oc.insertHeadline (insert-*)
1028@g.commander_command('insert-node')
1029def insertHeadline(self, event=None, op_name="Insert Node", as_child=False):
1030 """
1031 If c.p is expanded, insert a new node as the first or last child of c.p,
1032 depending on @bool insert-new-nodes-at-end.
1034 If c.p is not expanded, insert a new node after c.p.
1035 """
1036 c = self
1037 # Fix #600.
1038 return insertHeadlineHelper(c, event=event, as_child=as_child)
1040@g.commander_command('insert-as-first-child')
1041def insertNodeAsFirstChild(self, event=None):
1042 """Insert a node as the last last of the previous node."""
1043 c = self
1044 return insertHeadlineHelper(c, event=event, as_first_child=True)
1046@g.commander_command('insert-as-last-child')
1047def insertNodeAsLastChild(self, event=None):
1048 """Insert a node as the last child of the previous node."""
1049 c = self
1050 return insertHeadlineHelper(c, event=event, as_last_child=True)
1051#@+node:ekr.20171124091846.1: *4* function: insertHeadlineHelper
1052def insertHeadlineHelper(c,
1053 event=None,
1054 op_name="Insert Node",
1055 as_child=False,
1056 as_first_child=False,
1057 as_last_child=False,
1058):
1059 """Insert a node after the presently selected node."""
1060 u = c.undoer
1061 current = c.p
1062 if not current:
1063 return None
1064 c.endEditing()
1065 undoData = c.undoer.beforeInsertNode(current)
1066 if as_first_child:
1067 p = current.insertAsNthChild(0)
1068 elif as_last_child:
1069 p = current.insertAsLastChild()
1070 elif (
1071 as_child or
1072 (current.hasChildren() and current.isExpanded()) or
1073 (c.hoistStack and current == c.hoistStack[-1].p)
1074 ):
1075 # Make sure the new node is visible when hoisting.
1076 if c.config.getBool('insert-new-nodes-at-end'):
1077 p = current.insertAsLastChild()
1078 else:
1079 p = current.insertAsNthChild(0)
1080 else:
1081 p = current.insertAfter()
1082 g.doHook('create-node', c=c, p=p)
1083 p.setDirty()
1084 c.setChanged()
1085 u.afterInsertNode(p, op_name, undoData)
1086 c.redrawAndEdit(p, selectAll=True)
1087 return p
1088#@+node:ekr.20130922133218.11540: *3* c_oc.insertHeadlineBefore
1089@g.commander_command('insert-node-before')
1090def insertHeadlineBefore(self, event=None):
1091 """Insert a node before the presently selected node."""
1092 c, current, u = self, self.p, self.undoer
1093 op_name = 'Insert Node Before'
1094 if not current:
1095 return None
1096 # Can not insert before the base of a hoist.
1097 if c.hoistStack and current == c.hoistStack[-1].p:
1098 g.warning('can not insert a node before the base of a hoist')
1099 return None
1100 c.endEditing()
1101 undoData = u.beforeInsertNode(current)
1102 p = current.insertBefore()
1103 g.doHook('create-node', c=c, p=p)
1104 p.setDirty()
1105 c.setChanged()
1106 u.afterInsertNode(p, op_name, undoData)
1107 c.redrawAndEdit(p, selectAll=True)
1108 return p
1109#@+node:ekr.20031218072017.2922: ** c_oc.Mark commands
1110#@+node:ekr.20090905110447.6098: *3* c_oc.cloneMarked
1111@g.commander_command('clone-marked-nodes')
1112def cloneMarked(self, event=None):
1113 """Clone all marked nodes as children of a new node."""
1114 c, u = self, self.undoer
1115 p1 = c.p.copy()
1116 # Create a new node to hold clones.
1117 parent = p1.insertAfter()
1118 parent.h = 'Clones of marked nodes'
1119 cloned, n, p = [], 0, c.rootPosition()
1120 while p:
1121 # Careful: don't clone already-cloned nodes.
1122 if p == parent:
1123 p.moveToNodeAfterTree()
1124 elif p.isMarked() and p.v not in cloned:
1125 cloned.append(p.v)
1126 if 0: # old code
1127 # Calling p.clone would cause problems
1128 p.clone().moveToLastChildOf(parent)
1129 else: # New code.
1130 # Create the clone directly as a child of parent.
1131 p2 = p.copy()
1132 n = parent.numberOfChildren()
1133 p2._linkAsNthChild(parent, n)
1134 p.moveToNodeAfterTree()
1135 n += 1
1136 else:
1137 p.moveToThreadNext()
1138 if n:
1139 c.setChanged()
1140 parent.expand()
1141 c.selectPosition(parent)
1142 u.afterCloneMarkedNodes(p1)
1143 else:
1144 parent.doDelete()
1145 c.selectPosition(p1)
1146 if not g.unitTesting:
1147 g.blue(f"cloned {n} nodes")
1148 c.redraw()
1149#@+node:ekr.20160502090456.1: *3* c_oc.copyMarked
1150@g.commander_command('copy-marked-nodes')
1151def copyMarked(self, event=None):
1152 """Copy all marked nodes as children of a new node."""
1153 c, u = self, self.undoer
1154 p1 = c.p.copy()
1155 # Create a new node to hold clones.
1156 parent = p1.insertAfter()
1157 parent.h = 'Copies of marked nodes'
1158 copied, n, p = [], 0, c.rootPosition()
1159 while p:
1160 # Careful: don't clone already-cloned nodes.
1161 if p == parent:
1162 p.moveToNodeAfterTree()
1163 elif p.isMarked() and p.v not in copied:
1164 copied.append(p.v)
1165 p2 = p.copyWithNewVnodes(copyMarked=True)
1166 p2._linkAsNthChild(parent, n)
1167 p.moveToNodeAfterTree()
1168 n += 1
1169 else:
1170 p.moveToThreadNext()
1171 if n:
1172 c.setChanged()
1173 parent.expand()
1174 c.selectPosition(parent)
1175 u.afterCopyMarkedNodes(p1)
1176 else:
1177 parent.doDelete()
1178 c.selectPosition(p1)
1179 if not g.unitTesting:
1180 g.blue(f"copied {n} nodes")
1181 c.redraw()
1182#@+node:ekr.20111005081134.15540: *3* c_oc.deleteMarked
1183@g.commander_command('delete-marked-nodes')
1184def deleteMarked(self, event=None):
1185 """Delete all marked nodes."""
1186 c, u = self, self.undoer
1187 p1 = c.p.copy()
1188 undo_data, p = [], c.rootPosition()
1189 while p:
1190 if p.isMarked():
1191 undo_data.append(p.copy())
1192 next = p.positionAfterDeletedTree()
1193 p.doDelete()
1194 p = next
1195 else:
1196 p.moveToThreadNext()
1197 if undo_data:
1198 u.afterDeleteMarkedNodes(undo_data, p1)
1199 if not g.unitTesting:
1200 g.blue(f"deleted {len(undo_data)} nodes")
1201 c.setChanged()
1202 # Don't even *think* about restoring the old position.
1203 c.contractAllHeadlines()
1204 c.redraw(c.rootPosition())
1205#@+node:ekr.20111005081134.15539: *3* c_oc.moveMarked & helper
1206@g.commander_command('move-marked-nodes')
1207def moveMarked(self, event=None):
1208 """
1209 Move all marked nodes as children of a new node.
1210 This command is not undoable.
1211 Consider using clone-marked-nodes, followed by copy/paste instead.
1212 """
1213 c = self
1214 p1 = c.p.copy()
1215 # Check for marks.
1216 for v in c.all_unique_nodes():
1217 if v.isMarked():
1218 break
1219 else:
1220 g.warning('no marked nodes')
1221 return
1222 result = g.app.gui.runAskYesNoDialog(c,
1223 'Move Marked Nodes?',
1224 message='move-marked-nodes is not undoable\nProceed?',
1225 )
1226 if result == 'no':
1227 return
1228 # Create a new *root* node to hold the moved nodes.
1229 # This node's position remains stable while other nodes move.
1230 parent = createMoveMarkedNode(c)
1231 assert not parent.isMarked()
1232 moved = []
1233 p = c.rootPosition()
1234 while p:
1235 assert parent == c.rootPosition()
1236 # Careful: don't move already-moved nodes.
1237 if p.isMarked() and not parent.isAncestorOf(p):
1238 moved.append(p.copy())
1239 next = p.positionAfterDeletedTree()
1240 p.moveToLastChildOf(parent)
1241 # This does not change parent's position.
1242 p = next
1243 else:
1244 p.moveToThreadNext()
1245 if moved:
1246 # Find a position p2 outside of parent's tree with p2.v == p1.v.
1247 # Such a position may not exist.
1248 p2 = c.rootPosition()
1249 while p2:
1250 if p2 == parent:
1251 p2.moveToNodeAfterTree()
1252 elif p2.v == p1.v:
1253 break
1254 else:
1255 p2.moveToThreadNext()
1256 else:
1257 # Not found. Move to last top-level.
1258 p2 = c.lastTopLevel()
1259 parent.moveAfter(p2)
1260 # u.afterMoveMarkedNodes(moved, p1)
1261 if not g.unitTesting:
1262 g.blue(f"moved {len(moved)} nodes")
1263 c.setChanged()
1264 # Calling c.contractAllHeadlines() causes problems when in a chapter.
1265 c.redraw(parent)
1266#@+node:ekr.20111005081134.15543: *4* def createMoveMarkedNode
1267def createMoveMarkedNode(c):
1268 oldRoot = c.rootPosition()
1269 p = oldRoot.insertAfter()
1270 p.h = 'Moved marked nodes'
1271 p.moveToRoot()
1272 return p
1273#@+node:ekr.20031218072017.2923: *3* c_oc.markChangedHeadlines
1274@g.commander_command('mark-changed-items')
1275def markChangedHeadlines(self, event=None):
1276 """Mark all nodes that have been changed."""
1277 c, current, u = self, self.p, self.undoer
1278 undoType = 'Mark Changed'
1279 c.endEditing()
1280 u.beforeChangeGroup(current, undoType)
1281 for p in c.all_unique_positions():
1282 if p.isDirty() and not p.isMarked():
1283 bunch = u.beforeMark(p, undoType)
1284 # c.setMarked calls a hook.
1285 c.setMarked(p)
1286 p.setDirty()
1287 c.setChanged()
1288 u.afterMark(p, undoType, bunch)
1289 u.afterChangeGroup(current, undoType)
1290 if not g.unitTesting:
1291 g.blue('done')
1292 c.redraw_after_icons_changed()
1293#@+node:ekr.20031218072017.2924: *3* c_oc.markChangedRoots
1294def markChangedRoots(self, event=None):
1295 """Mark all changed @root nodes."""
1296 c, current, u = self, self.p, self.undoer
1297 undoType = 'Mark Changed'
1298 c.endEditing()
1299 u.beforeChangeGroup(current, undoType)
1300 for p in c.all_unique_positions():
1301 if p.isDirty() and not p.isMarked():
1302 s = p.b
1303 flag, i = g.is_special(s, "@root")
1304 if flag:
1305 bunch = u.beforeMark(p, undoType)
1306 c.setMarked(p) # Calls a hook.
1307 p.setDirty()
1308 c.setChanged()
1309 u.afterMark(p, undoType, bunch)
1310 u.afterChangeGroup(current, undoType)
1311 if not g.unitTesting:
1312 g.blue('done')
1313 c.redraw_after_icons_changed()
1314#@+node:ekr.20031218072017.2928: *3* c_oc.markHeadline
1315@g.commander_command('mark') # Compatibility
1316@g.commander_command('toggle-mark')
1317def markHeadline(self, event=None):
1318 """Toggle the mark of the selected node."""
1319 c, p, u = self, self.p, self.undoer
1320 if not p:
1321 return
1322 c.endEditing()
1323 undoType = 'Unmark' if p.isMarked() else 'Mark'
1324 bunch = u.beforeMark(p, undoType)
1325 # c.set/clearMarked call a hook.
1326 if p.isMarked():
1327 c.clearMarked(p)
1328 else:
1329 c.setMarked(p)
1330 p.setDirty()
1331 c.setChanged()
1332 u.afterMark(p, undoType, bunch)
1333 c.redraw_after_icons_changed()
1334#@+node:ekr.20031218072017.2929: *3* c_oc.markSubheads
1335@g.commander_command('mark-subheads')
1336def markSubheads(self, event=None):
1337 """Mark all children of the selected node as changed."""
1338 c, current, u = self, self.p, self.undoer
1339 undoType = 'Mark Subheads'
1340 if not current:
1341 return
1342 c.endEditing()
1343 u.beforeChangeGroup(current, undoType)
1344 for p in current.children():
1345 if not p.isMarked():
1346 bunch = u.beforeMark(p, undoType)
1347 c.setMarked(p) # Calls a hook.
1348 p.setDirty()
1349 c.setChanged()
1350 u.afterMark(p, undoType, bunch)
1351 u.afterChangeGroup(current, undoType)
1352 c.redraw_after_icons_changed()
1353#@+node:ekr.20031218072017.2930: *3* c_oc.unmarkAll
1354@g.commander_command('unmark-all')
1355def unmarkAll(self, event=None):
1356 """Unmark all nodes in the entire outline."""
1357 c, current, u = self, self.p, self.undoer
1358 undoType = 'Unmark All'
1359 if not current:
1360 return
1361 c.endEditing()
1362 u.beforeChangeGroup(current, undoType)
1363 changed = False
1364 p = None # To keep pylint happy.
1365 for p in c.all_unique_positions():
1366 if p.isMarked():
1367 bunch = u.beforeMark(p, undoType)
1368 # c.clearMarked(p) # Very slow: calls a hook.
1369 p.v.clearMarked()
1370 p.setDirty()
1371 u.afterMark(p, undoType, bunch)
1372 changed = True
1373 if changed:
1374 g.doHook("clear-all-marks", c=c, p=p)
1375 c.setChanged()
1376 u.afterChangeGroup(current, undoType)
1377 c.redraw_after_icons_changed()
1378#@+node:ekr.20031218072017.1766: ** c_oc.Move commands
1379#@+node:ekr.20031218072017.1767: *3* c_oc.demote
1380@g.commander_command('demote')
1381def demote(self, event=None):
1382 """Make all following siblings children of the selected node."""
1383 c, p, u = self, self.p, self.undoer
1384 if not p or not p.hasNext():
1385 c.treeFocusHelper()
1386 return
1387 # Make sure all the moves will be valid.
1388 next = p.next()
1389 while next:
1390 if not c.checkMoveWithParentWithWarning(next, p, True):
1391 c.treeFocusHelper()
1392 return
1393 next.moveToNext()
1394 c.endEditing()
1395 parent_v = p._parentVnode()
1396 n = p.childIndex()
1397 followingSibs = parent_v.children[n + 1 :]
1398 # Remove the moved nodes from the parent's children.
1399 parent_v.children = parent_v.children[: n + 1]
1400 # Add the moved nodes to p's children
1401 p.v.children.extend(followingSibs)
1402 # Adjust the parent links in the moved nodes.
1403 # There is no need to adjust descendant links.
1404 for child in followingSibs:
1405 child.parents.remove(parent_v)
1406 child.parents.append(p.v)
1407 p.expand()
1408 p.setDirty()
1409 c.setChanged()
1410 u.afterDemote(p, followingSibs)
1411 c.redraw(p)
1412 c.updateSyntaxColorer(p) # Moving can change syntax coloring.
1413#@+node:ekr.20031218072017.1768: *3* c_oc.moveOutlineDown
1414@g.commander_command('move-outline-down')
1415def moveOutlineDown(self, event=None):
1416 """Move the selected node down."""
1417 # Moving down is more tricky than moving up because we can't
1418 # move p to be a child of itself.
1419 #
1420 # An important optimization:
1421 # we don't have to call checkMoveWithParentWithWarning() if the parent of
1422 # the moved node remains the same.
1423 c, p, u = self, self.p, self.undoer
1424 if not p:
1425 return
1426 if not c.canMoveOutlineDown():
1427 if c.hoistStack:
1428 cantMoveMessage(c)
1429 c.treeFocusHelper()
1430 return
1431 parent = p.parent()
1432 next = p.visNext(c)
1433 while next and p.isAncestorOf(next):
1434 next = next.visNext(c)
1435 if not next:
1436 if c.hoistStack:
1437 cantMoveMessage(c)
1438 c.treeFocusHelper()
1439 return
1440 c.endEditing()
1441 undoData = u.beforeMoveNode(p)
1442 #@+<< Move p down & set moved if successful >>
1443 #@+node:ekr.20031218072017.1769: *4* << Move p down & set moved if successful >>
1444 if next.hasChildren() and next.isExpanded():
1445 # Attempt to move p to the first child of next.
1446 moved = c.checkMoveWithParentWithWarning(p, next, True)
1447 if moved:
1448 p.setDirty()
1449 p.moveToNthChildOf(next, 0)
1450 else:
1451 # Attempt to move p after next.
1452 moved = c.checkMoveWithParentWithWarning(p, next.parent(), True)
1453 if moved:
1454 p.setDirty()
1455 p.moveAfter(next)
1456 # Patch by nh2: 0004-Add-bool-collapse_nodes_after_move-option.patch
1457 if (
1458 c.collapse_nodes_after_move
1459 and moved and c.sparse_move
1460 and parent and not parent.isAncestorOf(p)
1461 ):
1462 # New in Leo 4.4.2: contract the old parent if it is no longer the parent of p.
1463 parent.contract()
1464 #@-<< Move p down & set moved if successful >>
1465 if moved:
1466 p.setDirty()
1467 c.setChanged()
1468 u.afterMoveNode(p, 'Move Down', undoData)
1469 c.redraw(p)
1470 c.updateSyntaxColorer(p) # Moving can change syntax coloring.
1471#@+node:ekr.20031218072017.1770: *3* c_oc.moveOutlineLeft
1472@g.commander_command('move-outline-left')
1473def moveOutlineLeft(self, event=None):
1474 """Move the selected node left if possible."""
1475 c, p, u = self, self.p, self.undoer
1476 if not p:
1477 return
1478 if not c.canMoveOutlineLeft():
1479 if c.hoistStack:
1480 cantMoveMessage(c)
1481 c.treeFocusHelper()
1482 return
1483 if not p.hasParent():
1484 c.treeFocusHelper()
1485 return
1486 parent = p.parent()
1487 c.endEditing()
1488 undoData = u.beforeMoveNode(p)
1489 p.setDirty()
1490 p.moveAfter(parent)
1491 p.setDirty()
1492 c.setChanged()
1493 u.afterMoveNode(p, 'Move Left', undoData)
1494 # Patch by nh2: 0004-Add-bool-collapse_nodes_after_move-option.patch
1495 if c.collapse_nodes_after_move and c.sparse_move: # New in Leo 4.4.2
1496 parent.contract()
1497 c.redraw(p)
1498 c.recolor() # Moving can change syntax coloring.
1499#@+node:ekr.20031218072017.1771: *3* c_oc.moveOutlineRight
1500@g.commander_command('move-outline-right')
1501def moveOutlineRight(self, event=None):
1502 """Move the selected node right if possible."""
1503 c, p, u = self, self.p, self.undoer
1504 if not p:
1505 return
1506 if not c.canMoveOutlineRight(): # 11/4/03: Support for hoist.
1507 if c.hoistStack:
1508 cantMoveMessage(c)
1509 c.treeFocusHelper()
1510 return
1511 back = p.back()
1512 if not back:
1513 c.treeFocusHelper()
1514 return
1515 if not c.checkMoveWithParentWithWarning(p, back, True):
1516 c.treeFocusHelper()
1517 return
1518 c.endEditing()
1519 undoData = u.beforeMoveNode(p)
1520 p.setDirty()
1521 n = back.numberOfChildren()
1522 p.moveToNthChildOf(back, n)
1523 p.setDirty()
1524 c.setChanged() # #2036.
1525 u.afterMoveNode(p, 'Move Right', undoData)
1526 c.redraw(p)
1527 c.recolor()
1528#@+node:ekr.20031218072017.1772: *3* c_oc.moveOutlineUp
1529@g.commander_command('move-outline-up')
1530def moveOutlineUp(self, event=None):
1531 """Move the selected node up if possible."""
1532 c, p, u = self, self.p, self.undoer
1533 if not p:
1534 return
1535 if not c.canMoveOutlineUp(): # Support for hoist.
1536 if c.hoistStack:
1537 cantMoveMessage(c)
1538 c.treeFocusHelper()
1539 return
1540 back = p.visBack(c)
1541 if not back:
1542 return
1543 back2 = back.visBack(c)
1544 c.endEditing()
1545 undoData = u.beforeMoveNode(p)
1546 moved = False
1547 #@+<< Move p up >>
1548 #@+node:ekr.20031218072017.1773: *4* << Move p up >>
1549 parent = p.parent()
1550 if not back2:
1551 if c.hoistStack: # hoist or chapter.
1552 limit, limitIsVisible = c.visLimit()
1553 assert limit
1554 if limitIsVisible:
1555 # canMoveOutlineUp should have caught this.
1556 g.trace('can not happen. In hoist')
1557 else:
1558 moved = True
1559 p.setDirty()
1560 p.moveToFirstChildOf(limit)
1561 else:
1562 # p will be the new root node
1563 p.setDirty()
1564 p.moveToRoot()
1565 moved = True
1566 elif back2.hasChildren() and back2.isExpanded():
1567 if c.checkMoveWithParentWithWarning(p, back2, True):
1568 moved = True
1569 p.setDirty()
1570 p.moveToNthChildOf(back2, 0)
1571 else:
1572 if c.checkMoveWithParentWithWarning(p, back2.parent(), True):
1573 moved = True
1574 p.setDirty()
1575 p.moveAfter(back2)
1576 # Patch by nh2: 0004-Add-bool-collapse_nodes_after_move-option.patch
1577 if (
1578 c.collapse_nodes_after_move
1579 and moved and c.sparse_move
1580 and parent and not parent.isAncestorOf(p)
1581 ):
1582 # New in Leo 4.4.2: contract the old parent if it is no longer the parent of p.
1583 parent.contract()
1584 #@-<< Move p up >>
1585 if moved:
1586 p.setDirty()
1587 c.setChanged()
1588 u.afterMoveNode(p, 'Move Up', undoData)
1589 c.redraw(p)
1590 c.updateSyntaxColorer(p) # Moving can change syntax coloring.
1591#@+node:ekr.20031218072017.1774: *3* c_oc.promote
1592@g.commander_command('promote')
1593def promote(self, event=None, undoFlag=True):
1594 """Make all children of the selected nodes siblings of the selected node."""
1595 c, p, u = self, self.p, self.undoer
1596 if not p or not p.hasChildren():
1597 c.treeFocusHelper()
1598 return
1599 c.endEditing()
1600 children = p.v.children # First, for undo.
1601 p.promote()
1602 c.setChanged()
1603 if undoFlag:
1604 p.setDirty()
1605 u.afterPromote(p, children)
1606 c.redraw(p)
1607 c.updateSyntaxColorer(p) # Moving can change syntax coloring.
1608#@+node:ekr.20071213185710: *3* c_oc.toggleSparseMove
1609@g.commander_command('toggle-sparse-move')
1610def toggleSparseMove(self, event=None):
1611 """Toggle whether moves collapse the outline."""
1612 c = self
1613 c.sparse_move = not c.sparse_move
1614 if not g.unitTesting:
1615 g.blue(f"sparse-move: {c.sparse_move}")
1616#@+node:ekr.20080425060424.1: ** c_oc.Sort commands
1617#@+node:ekr.20050415134809: *3* c_oc.sortChildren
1618@g.commander_command('sort-children')
1619def sortChildren(self, event=None, key=None, reverse=False):
1620 """Sort the children of a node."""
1621 # This method no longer supports the 'cmp' keyword arg.
1622 c, p = self, self.p
1623 if p and p.hasChildren():
1624 c.sortSiblings(p=p.firstChild(), sortChildren=True, key=key, reverse=reverse)
1625#@+node:ekr.20050415134809.1: *3* c_oc.sortSiblings
1626@g.commander_command('sort-siblings')
1627def sortSiblings(self, event=None,
1628 # cmp keyword is no longer supported.
1629 key=None,
1630 p=None,
1631 sortChildren=False,
1632 reverse=False
1633):
1634 """Sort the siblings of a node."""
1635 c, u = self, self.undoer
1636 if not p:
1637 p = c.p
1638 if not p:
1639 return
1640 c.endEditing()
1641 undoType = 'Sort Children' if sortChildren else 'Sort Siblings'
1642 parent_v = p._parentVnode()
1643 oldChildren = parent_v.children[:]
1644 newChildren = parent_v.children[:]
1645 if key is None:
1647 def lowerKey(self):
1648 return self.h.lower()
1650 key = lowerKey
1651 newChildren.sort(key=key, reverse=reverse)
1652 if oldChildren == newChildren:
1653 return
1654 # 2010/01/20. Fix bug 510148.
1655 c.setChanged()
1656 bunch = u.beforeSort(p, undoType, oldChildren, newChildren, sortChildren)
1657 parent_v.children = newChildren
1658 u.afterSort(p, bunch)
1659 # Sorting destroys position p, and possibly the root position.
1660 p = c.setPositionAfterSort(sortChildren)
1661 if p.parent():
1662 p.parent().setDirty()
1663 c.redraw(p)
1664#@+node:ekr.20070420092425: ** def cantMoveMessage
1665def cantMoveMessage(c):
1666 h = c.rootPosition().h
1667 kind = 'chapter' if h.startswith('@chapter') else 'hoist'
1668 g.warning("can't move node out of", kind)
1669#@+node:ekr.20180201040936.1: ** count-children
1670@g.command('count-children')
1671def count_children(event=None):
1672 c = event and event.get('c')
1673 if c:
1674 g.es_print(f"{c.p.numberOfChildren()} children")
1675#@-others
1676#@-leo