Coverage for C:\Repos\leo-editor\leo\commands\commanderOutlineCommands.py: 41%
1224 statements
« prev ^ index » next coverage.py v6.4, created at 2022-05-24 10:21 -0500
« prev ^ index » next coverage.py v6.4, created at 2022-05-24 10:21 -0500
1# -*- coding: utf-8 -*-
2#@+leo-ver=5-thin
3#@+node:ekr.20171124080430.1: * @file ../commands/commanderOutlineCommands.py
4#@@first
5"""Outline commands that used to be defined in leoCommands.py"""
6import xml.etree.ElementTree as ElementTree
7from leo.core import leoGlobals as g
8from leo.core import leoNodes
9from leo.core import leoFileCommands
10#@+others
11#@+node:ekr.20031218072017.1548: ** c_oc.Cut & Paste Outlines
12#@+node:ekr.20031218072017.1550: *3* c_oc.copyOutline
13@g.commander_command('copy-node')
14def copyOutline(self, event=None):
15 """Copy the selected outline to the clipboard."""
16 # Copying an outline has no undo consequences.
17 c = self
18 c.endEditing()
19 s = c.fileCommands.outline_to_clipboard_string()
20 g.app.paste_c = c
21 g.app.gui.replaceClipboardWith(s)
22#@+node:ekr.20220314071523.1: *3* c_oc.copyOutlineAsJson & helpers
23@g.commander_command('copy-node-as-json')
24def copyOutlineAsJSON(self, event=None):
25 """Copy the selected outline to the clipboard in json format."""
26 # Copying an outline has no undo consequences.
27 import json
28 #@+others # Define helper functions
29 #@+node:ekr.20220314072801.1: *4* function: json_globals
30 def json_globals(c):
31 """Put json representation of Leo's cached globals."""
32 width, height, left, top = c.frame.get_window_info()
33 return {
34 'body_outline_ratio': c.frame.ratio,
35 'body_secondary_ratio': c.frame.secondary_ratio,
36 'globalWindowPosition': {
37 'height': height,
38 'left': left,
39 'top': top,
40 'width': width,
41 },
42 }
43 #@+node:ekr.20220314073155.1: *4* function: json_vnode
44 def json_vnode(v):
45 return {
46 'gnx': v.fileIndex,
47 'vh': v._headString,
48 'status': v.statusBits,
49 'children': [json_vnode(child) for child in v.children]
50 }
51 #@+node:ekr.20220314071805.1: *4* function: outline_to_json
52 def outline_to_json(c):
53 """Return the JSON representation of c."""
54 positions = list(c.p.self_and_subtree())
55 d = {
56 'leoHeader': {'fileFormat': 2},
57 'globals': json_globals(c),
58 'tnodes': {
59 p.v.gnx: p.v._bodyString for p in positions
60 },
61 'uas': {
62 p.v.gnx: json.dumps(p.u, skipkeys=True) for p in positions if p.u
63 },
64 'vnodes': [
65 json_vnode(c.p.v)
66 ],
67 }
68 return json.dumps(d, indent=2, sort_keys=False)
69 #@-others
70 c = self
71 c.endEditing()
72 s = outline_to_json(c)
73 g.app.paste_c = c
74 g.app.gui.replaceClipboardWith(s)
75#@+node:ekr.20031218072017.1549: *3* c_oc.cutOutline
76@g.commander_command('cut-node')
77def cutOutline(self, event=None):
78 """Delete the selected outline and send it to the clipboard."""
79 c = self
80 if c.canDeleteHeadline():
81 c.copyOutline()
82 c.deleteOutline(op_name="Cut Node")
83 c.recolor()
84#@+node:ekr.20031218072017.1551: *3* c_oc.pasteOutline
85@g.commander_command('paste-node')
86def pasteOutline(self, event=None, s=None, undoFlag=True):
87 """
88 Paste an outline into the present outline from the clipboard.
89 Nodes do *not* retain their original identify.
90 """
91 c = self
92 if s is None:
93 s = g.app.gui.getTextFromClipboard()
94 c.endEditing()
95 if not s or not c.canPasteOutline(s):
96 return None # This should never happen.
97 isLeo = g.match(s, 0, g.app.prolog_prefix_string)
98 if not isLeo:
99 return None
100 # Get *position* to be pasted.
101 pasted = c.fileCommands.getLeoOutlineFromClipboard(s)
102 if not pasted:
103 # Leo no longer supports MORE outlines. Use import-MORE-files instead.
104 return None
105 # Validate.
106 c.validateOutline()
107 c.checkOutline()
108 # Handle the "before" data for undo.
109 if undoFlag:
110 undoData = c.undoer.beforeInsertNode(c.p,
111 pasteAsClone=False,
112 copiedBunchList=[],
113 )
114 # Paste the node into the outline.
115 c.selectPosition(pasted)
116 pasted.setDirty()
117 c.setChanged()
118 back = pasted.back()
119 if back and back.hasChildren() and back.isExpanded():
120 pasted.moveToNthChildOf(back, 0)
121 # Finish the command.
122 if undoFlag:
123 c.undoer.afterInsertNode(pasted, 'Paste Node', undoData)
124 c.redraw(pasted)
125 c.recolor()
126 return pasted
127#@+node:EKR.20040610130943: *3* c_oc.pasteOutlineRetainingClones & helpers
128@g.commander_command('paste-retaining-clones')
129def pasteOutlineRetainingClones(self, event=None, s=None, undoFlag=True):
130 """
131 Paste an outline into the present outline from the clipboard.
132 Nodes *retain* their original identify.
133 """
134 c = self
135 if s is None:
136 s = g.app.gui.getTextFromClipboard()
137 c.endEditing()
138 if not s or not c.canPasteOutline(s):
139 return None # This should never happen.
140 isLeo = g.match(s, 0, g.app.prolog_prefix_string)
141 if not isLeo:
142 return None
143 # Get *position* to be pasted.
144 pasted = c.fileCommands.getLeoOutlineFromClipboardRetainingClones(s)
145 if not pasted:
146 # Leo no longer supports MORE outlines. Use import-MORE-files instead.
147 return None
148 # Validate.
149 c.validateOutline()
150 c.checkOutline()
151 # Handle the "before" data for undo.
152 if undoFlag:
153 vnodeInfoDict = computeVnodeInfoDict(c)
154 undoData = c.undoer.beforeInsertNode(c.p,
155 pasteAsClone=True,
156 copiedBunchList=computeCopiedBunchList(c, pasted, vnodeInfoDict),
157 )
158 # Paste the node into the outline.
159 c.selectPosition(pasted)
160 pasted.setDirty()
161 c.setChanged()
162 back = pasted.back()
163 if back and back.hasChildren() and back.isExpanded():
164 pasted.moveToNthChildOf(back, 0)
165 pasted.setDirty()
166 # Set dirty bits for ancestors of *all* pasted nodes.
167 for p in pasted.self_and_subtree():
168 p.setAllAncestorAtFileNodesDirty()
169 # Finish the command.
170 if undoFlag:
171 c.undoer.afterInsertNode(pasted, 'Paste As Clone', undoData)
172 c.redraw(pasted)
173 c.recolor()
174 return pasted
175#@+node:ekr.20050418084539.2: *4* def computeCopiedBunchList
176def computeCopiedBunchList(c, pasted, vnodeInfoDict):
177 """Create a dict containing only copied vnodes."""
178 d = {}
179 for p in pasted.self_and_subtree(copy=False):
180 d[p.v] = p.v
181 aList = []
182 for v in vnodeInfoDict:
183 if d.get(v):
184 bunch = vnodeInfoDict.get(v)
185 aList.append(bunch)
186 return aList
187#@+node:ekr.20050418084539: *4* def computeVnodeInfoDict
188def computeVnodeInfoDict(c):
189 """
190 We don't know yet which nodes will be affected by the paste, so we remember
191 everything. This is expensive, but foolproof.
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:
232 (parent_gnx, child_gnx, headline, body)
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])
270 # then we just take first tuple
271 pgnx, gnx, h, b = next(rows)
273 # create vnode
274 v, _ = getv(gnx)
275 v.h = h
276 v.b = b
278 # and finally insert it at the given index
279 vpar.children.insert(index, v)
280 v.parents.append(vpar)
282 pasted = v # remember the first node as a return value
284 # now we iterate the rest of tuples
285 for pgnx, gnx, h, b in rows:
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]
298 # and link them
299 vpar.children.append(v)
300 v.parents.append(vpar)
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.
318 bodies, uas = leoFileCommands.FastRead(c, {}).scanTnodes(xtelements)
320 root_gnx = xvelements[0].attrib.get('t') # the gnx of copied node
321 # outside will contain gnxes of nodes that are outside the copied tree
322 outside = {x.gnx for x in skip_root(c.hiddenRootNode)}
324 # we generate new gnx for each node in the copied tree
325 translation = {x: translate_gnx(x) for x in bodies}
327 seen = set(outside) # required for the treatment of local clones inside the copied tree
329 heads = {}
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
352 #@-<< prepare destination data >>
354 pasted = do_paste(vpar, index)
356 newp = leoNodes.Position(pasted, index, parStack)
358 bunch.undoHelper = undoHelper
359 bunch.redoHelper = redoHelper
360 bunch.undoType = 'paste-retaining-outside-clones'
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)
503@g.commander_command('expand-to-level-2')
504def expandLevel2(self, event=None):
505 """Expand the outline to level 2"""
506 self.expandToLevel(2)
508@g.commander_command('expand-to-level-3')
509def expandLevel3(self, event=None):
510 """Expand the outline to level 3"""
511 self.expandToLevel(3)
513@g.commander_command('expand-to-level-4')
514def expandLevel4(self, event=None):
515 """Expand the outline to level 4"""
516 self.expandToLevel(4)
518@g.commander_command('expand-to-level-5')
519def expandLevel5(self, event=None):
520 """Expand the outline to level 5"""
521 self.expandToLevel(5)
523@g.commander_command('expand-to-level-6')
524def expandLevel6(self, event=None):
525 """Expand the outline to level 6"""
526 self.expandToLevel(6)
528@g.commander_command('expand-to-level-7')
529def expandLevel7(self, event=None):
530 """Expand the outline to level 7"""
531 self.expandToLevel(7)
533@g.commander_command('expand-to-level-8')
534def expandLevel8(self, event=None):
535 """Expand the outline to level 8"""
536 self.expandToLevel(8)
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.
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.
687 But (#2167), go to the first node of a chapter or hoist
688 if Leo is hoisted or within a chapter.
689 """
690 c = self
691 p = c.rootPosition()
692 c.expandOnlyAncestorsOfNode(p=p)
693 c.redraw()
694#@+node:ekr.20051012092453: *3* c_oc.goToFirstSibling
695@g.commander_command('goto-first-sibling')
696def goToFirstSibling(self, event=None):
697 """Select the first sibling of the selected node."""
698 c, p = self, self.p
699 if p.hasBack():
700 while p.hasBack():
701 p.moveToBack()
702 c.treeSelectHelper(p)
703#@+node:ekr.20070615070925: *3* c_oc.goToFirstVisibleNode
704@g.commander_command('goto-first-visible-node')
705def goToFirstVisibleNode(self, event=None):
706 """Select the first visible node of the selected chapter or hoist."""
707 c = self
708 p = c.firstVisible()
709 if p:
710 if c.sparse_goto_visible:
711 c.expandOnlyAncestorsOfNode(p=p)
712 else:
713 c.treeSelectHelper(p)
714 c.redraw()
715#@+node:ekr.20031218072017.2915: *3* c_oc.goToLastNode
716@g.commander_command('goto-last-node')
717def goToLastNode(self, event=None):
718 """Select the last node in the entire tree."""
719 c = self
720 p = c.rootPosition()
721 while p and p.hasThreadNext():
722 p.moveToThreadNext()
723 c.expandOnlyAncestorsOfNode(p=p)
724 c.redraw()
725#@+node:ekr.20051012092847.1: *3* c_oc.goToLastSibling
726@g.commander_command('goto-last-sibling')
727def goToLastSibling(self, event=None):
728 """Select the last sibling of the selected node."""
729 c, p = self, self.p
730 if p.hasNext():
731 while p.hasNext():
732 p.moveToNext()
733 c.treeSelectHelper(p)
734#@+node:ekr.20050711153537: *3* c_oc.goToLastVisibleNode
735@g.commander_command('goto-last-visible-node')
736def goToLastVisibleNode(self, event=None):
737 """Select the last visible node of selected chapter or hoist."""
738 c = self
739 p = c.lastVisible()
740 if p:
741 if c.sparse_goto_visible:
742 c.expandOnlyAncestorsOfNode(p=p)
743 else:
744 c.treeSelectHelper(p)
745 c.redraw()
746#@+node:ekr.20031218072017.2916: *3* c_oc.goToNextClone
747@g.commander_command('goto-next-clone')
748def goToNextClone(self, event=None):
749 """
750 Select the next node that is a clone of the selected node.
751 If the selected node is not a clone, do find-next-clone.
752 """
753 c, p = self, self.p
754 cc = c.chapterController
755 if not p:
756 return
757 if not p.isCloned():
758 c.findNextClone()
759 return
760 v = p.v
761 p.moveToThreadNext()
762 wrapped = False
763 while 1:
764 if p and p.v == v:
765 break
766 elif p:
767 p.moveToThreadNext()
768 elif wrapped:
769 break
770 else:
771 wrapped = True
772 p = c.rootPosition()
773 if p:
774 c.expandAllAncestors(p)
775 if cc:
776 # #252: goto-next clone activate chapter.
777 chapter = cc.getSelectedChapter()
778 old_name = chapter and chapter.name
779 new_name = cc.findChapterNameForPosition(p)
780 if new_name != old_name:
781 cc.selectChapterByName(new_name)
782 # Always do a full redraw.
783 c.redraw(p)
784 else:
785 g.blue('done')
786#@+node:ekr.20031218072017.2917: *3* c_oc.goToNextDirtyHeadline
787@g.commander_command('goto-next-changed')
788def goToNextDirtyHeadline(self, event=None):
789 """Select the node that is marked as changed."""
790 c, p = self, self.p
791 if not p:
792 return
793 p.moveToThreadNext()
794 wrapped = False
795 while 1:
796 if p and p.isDirty():
797 break
798 elif p:
799 p.moveToThreadNext()
800 elif wrapped:
801 break
802 else:
803 wrapped = True
804 p = c.rootPosition()
805 if not p:
806 g.blue('done')
807 c.treeSelectHelper(p) # Sets focus.
808#@+node:ekr.20031218072017.2918: *3* c_oc.goToNextMarkedHeadline
809@g.commander_command('goto-next-marked')
810def goToNextMarkedHeadline(self, event=None):
811 """Select the next marked node."""
812 c, p = self, self.p
813 if not p:
814 return
815 p.moveToThreadNext()
816 wrapped = False
817 while 1:
818 if p and p.isMarked():
819 break
820 elif p:
821 p.moveToThreadNext()
822 elif wrapped:
823 break
824 else:
825 wrapped = True
826 p = c.rootPosition()
827 if not p:
828 g.blue('done')
829 c.treeSelectHelper(p) # Sets focus.
830#@+node:ekr.20031218072017.2919: *3* c_oc.goToNextSibling
831@g.commander_command('goto-next-sibling')
832def goToNextSibling(self, event=None):
833 """Select the next sibling of the selected node."""
834 c, p = self, self.p
835 c.treeSelectHelper(p and p.next())
836#@+node:ekr.20031218072017.2920: *3* c_oc.goToParent
837@g.commander_command('goto-parent')
838def goToParent(self, event=None):
839 """Select the parent of the selected node."""
840 c, p = self, self.p
841 c.treeSelectHelper(p and p.parent())
842#@+node:ekr.20190211104913.1: *3* c_oc.goToPrevMarkedHeadline
843@g.commander_command('goto-prev-marked')
844def goToPrevMarkedHeadline(self, event=None):
845 """Select the next marked node."""
846 c, p = self, self.p
847 if not p:
848 return
849 p.moveToThreadBack()
850 wrapped = False
851 while 1:
852 if p and p.isMarked():
853 break
854 elif p:
855 p.moveToThreadBack()
856 elif wrapped:
857 break
858 else:
859 wrapped = True
860 p = c.rootPosition()
861 if not p:
862 g.blue('done')
863 c.treeSelectHelper(p) # Sets focus.
864#@+node:ekr.20031218072017.2921: *3* c_oc.goToPrevSibling
865@g.commander_command('goto-prev-sibling')
866def goToPrevSibling(self, event=None):
867 """Select the previous sibling of the selected node."""
868 c, p = self, self.p
869 c.treeSelectHelper(p and p.back())
870#@+node:ekr.20031218072017.2993: *3* c_oc.selectThreadBack
871@g.commander_command('goto-prev-node')
872def selectThreadBack(self, event=None):
873 """Select the node preceding the selected node in outline order."""
874 c, p = self, self.p
875 if not p:
876 return
877 p.moveToThreadBack()
878 c.treeSelectHelper(p)
879#@+node:ekr.20031218072017.2994: *3* c_oc.selectThreadNext
880@g.commander_command('goto-next-node')
881def selectThreadNext(self, event=None):
882 """Select the node following the selected node in outline order."""
883 c, p = self, self.p
884 if not p:
885 return
886 p.moveToThreadNext()
887 c.treeSelectHelper(p)
888#@+node:ekr.20031218072017.2995: *3* c_oc.selectVisBack
889@g.commander_command('goto-prev-visible')
890def selectVisBack(self, event=None):
891 """Select the visible node preceding the presently selected node."""
892 # This has an up arrow for a control key.
893 c, p = self, self.p
894 if not p:
895 return
896 if c.canSelectVisBack():
897 p.moveToVisBack(c)
898 c.treeSelectHelper(p)
899 else:
900 c.endEditing() # 2011/05/28: A special case.
901#@+node:ekr.20031218072017.2996: *3* c_oc.selectVisNext
902@g.commander_command('goto-next-visible')
903def selectVisNext(self, event=None):
904 """Select the visible node following the presently selected node."""
905 c, p = self, self.p
906 if not p:
907 return
908 if c.canSelectVisNext():
909 p.moveToVisNext(c)
910 c.treeSelectHelper(p)
911 else:
912 c.endEditing() # 2011/05/28: A special case.
913#@+node:ekr.20031218072017.2028: ** c_oc.hoist/dehoist/clearAllHoists
914#@+node:ekr.20120308061112.9865: *3* c_oc.deHoist
915@g.commander_command('de-hoist')
916@g.commander_command('dehoist')
917def dehoist(self, event=None):
918 """Undo a previous hoist of an outline."""
919 c = self
920 if not c.p or not c.hoistStack:
921 return
922 # Don't de-hoist an @chapter node.
923 if c.chapterController and c.p.h.startswith('@chapter '):
924 if not g.unitTesting:
925 g.es('can not de-hoist an @chapter node.', color='blue')
926 return
927 bunch = c.hoistStack.pop()
928 p = bunch.p
929 # Checks 'expanded' property, which was preserved by 'hoist' method
930 if bunch.expanded:
931 p.expand()
932 else:
933 p.contract()
934 c.setCurrentPosition(p)
935 c.redraw()
936 c.frame.clearStatusLine()
937 c.frame.putStatusLine("De-Hoist: " + p.h)
938 c.undoer.afterDehoist(p, 'DeHoist')
939 g.doHook('hoist-changed', c=c)
940#@+node:ekr.20120308061112.9866: *3* c_oc.clearAllHoists
941@g.commander_command('clear-all-hoists')
942def clearAllHoists(self, event=None):
943 """Undo a previous hoist of an outline."""
944 c = self
945 c.hoistStack = []
946 c.frame.putStatusLine("Hoists cleared")
947 g.doHook('hoist-changed', c=c)
948#@+node:ekr.20120308061112.9867: *3* c_oc.hoist
949@g.commander_command('hoist')
950def hoist(self, event=None):
951 """Make only the selected outline visible."""
952 c = self
953 p = c.p
954 if not p:
955 return
956 # Don't hoist an @chapter node.
957 if c.chapterController and p.h.startswith('@chapter '):
958 if not g.unitTesting:
959 g.es('can not hoist an @chapter node.', color='blue')
960 return
961 # Remember the expansion state.
962 bunch = g.Bunch(p=p.copy(), expanded=p.isExpanded())
963 c.hoistStack.append(bunch)
964 p.expand()
965 c.redraw(p)
966 c.frame.clearStatusLine()
967 c.frame.putStatusLine("Hoist: " + p.h)
968 c.undoer.afterHoist(p, 'Hoist')
969 g.doHook('hoist-changed', c=c)
970#@+node:ekr.20031218072017.1759: ** c_oc.Insert, Delete & Clone commands
971#@+node:ekr.20031218072017.1762: *3* c_oc.clone
972@g.commander_command('clone-node')
973def clone(self, event=None):
974 """Create a clone of the selected outline."""
975 c, p, u = self, self.p, self.undoer
976 if not p:
977 return None
978 undoData = c.undoer.beforeCloneNode(p)
979 c.endEditing() # Capture any changes to the headline.
980 clone = p.clone()
981 clone.setDirty()
982 c.setChanged()
983 if c.validateOutline():
984 u.afterCloneNode(clone, 'Clone Node', undoData)
985 c.redraw(clone)
986 c.treeWantsFocus()
987 return clone # For mod_labels and chapters plugins.
988 clone.doDelete()
989 c.setCurrentPosition(p)
990 return None
991#@+node:ekr.20150630152607.1: *3* c_oc.cloneToAtSpot
992@g.commander_command('clone-to-at-spot')
993def cloneToAtSpot(self, event=None):
994 """
995 Create a clone of the selected node and move it to the last @spot node
996 of the outline. Create the @spot node if necessary.
997 """
998 c, p, u = self, self.p, self.undoer
999 if not p:
1000 return
1001 # 2015/12/27: fix bug 220: do not allow clone-to-at-spot on @spot node.
1002 if p.h.startswith('@spot'):
1003 g.es("can not clone @spot node", color='red')
1004 return
1005 last_spot = None
1006 for p2 in c.all_positions():
1007 if g.match_word(p2.h, 0, '@spot'):
1008 last_spot = p2.copy()
1009 if not last_spot:
1010 last = c.lastTopLevel()
1011 last_spot = last.insertAfter()
1012 last_spot.h = '@spot'
1013 undoData = c.undoer.beforeCloneNode(p)
1014 c.endEditing() # Capture any changes to the headline.
1015 clone = p.copy()
1016 clone._linkAsNthChild(last_spot, n=last_spot.numberOfChildren())
1017 clone.setDirty()
1018 c.setChanged()
1019 if c.validateOutline():
1020 u.afterCloneNode(clone, 'Clone Node', undoData)
1021 c.contractAllHeadlines()
1022 c.redraw(clone)
1023 else:
1024 clone.doDelete()
1025 c.setCurrentPosition(p)
1026#@+node:ekr.20141023154408.5: *3* c_oc.cloneToLastNode
1027@g.commander_command('clone-node-to-last-node')
1028def cloneToLastNode(self, event=None):
1029 """
1030 Clone the selected node and move it to the last node.
1031 Do *not* change the selected node.
1032 """
1033 c, p, u = self, self.p, self.undoer
1034 if not p:
1035 return
1036 prev = p.copy()
1037 undoData = c.undoer.beforeCloneNode(p)
1038 c.endEditing() # Capture any changes to the headline.
1039 clone = p.clone()
1040 last = c.rootPosition()
1041 while last and last.hasNext():
1042 last.moveToNext()
1043 clone.moveAfter(last)
1044 clone.setDirty()
1045 c.setChanged()
1046 u.afterCloneNode(clone, 'Clone Node To Last', undoData)
1047 c.redraw(prev)
1048 # return clone # For mod_labels and chapters plugins.
1049#@+node:ekr.20031218072017.1193: *3* c_oc.deleteOutline
1050@g.commander_command('delete-node')
1051def deleteOutline(self, event=None, op_name="Delete Node"):
1052 """Deletes the selected outline."""
1053 c, u = self, self.undoer
1054 p = c.p
1055 if not p:
1056 return
1057 c.endEditing() # Make sure we capture the headline for Undo.
1058 if False: # c.config.getBool('select-next-after-delete'):
1059 # #721: Optionally select next node after delete.
1060 if p.hasVisNext(c):
1061 newNode = p.visNext(c)
1062 elif p.hasParent():
1063 newNode = p.parent()
1064 else:
1065 newNode = p.back() # _not_ p.visBack(): we are at the top level.
1066 else:
1067 # Legacy: select previous node if possible.
1068 if p.hasVisBack(c):
1069 newNode = p.visBack(c)
1070 else:
1071 newNode = p.next() # _not_ p.visNext(): we are at the top level.
1072 if not newNode:
1073 return
1074 undoData = u.beforeDeleteNode(p)
1075 p.setDirty()
1076 p.doDelete(newNode)
1077 c.setChanged()
1078 u.afterDeleteNode(newNode, op_name, undoData)
1079 c.redraw(newNode)
1080 c.validateOutline()
1081#@+node:ekr.20071005173203.1: *3* c_oc.insertChild
1082@g.commander_command('insert-child')
1083def insertChild(self, event=None):
1084 """Insert a node after the presently selected node."""
1085 c = self
1086 return c.insertHeadline(event=event, op_name='Insert Child', as_child=True)
1087#@+node:ekr.20031218072017.1761: *3* c_oc.insertHeadline (insert-*)
1088@g.commander_command('insert-node')
1089def insertHeadline(self, event=None, op_name="Insert Node", as_child=False):
1090 """
1091 If c.p is expanded, insert a new node as the first or last child of c.p,
1092 depending on @bool insert-new-nodes-at-end.
1094 If c.p is not expanded, insert a new node after c.p.
1095 """
1096 c = self
1097 # Fix #600.
1098 return insertHeadlineHelper(c, event=event, as_child=as_child)
1100@g.commander_command('insert-as-first-child')
1101def insertNodeAsFirstChild(self, event=None):
1102 """Insert a node as the last last of the previous node."""
1103 c = self
1104 return insertHeadlineHelper(c, event=event, as_first_child=True)
1106@g.commander_command('insert-as-last-child')
1107def insertNodeAsLastChild(self, event=None):
1108 """Insert a node as the last child of the previous node."""
1109 c = self
1110 return insertHeadlineHelper(c, event=event, as_last_child=True)
1111#@+node:ekr.20171124091846.1: *4* function: insertHeadlineHelper
1112def insertHeadlineHelper(c,
1113 event=None,
1114 op_name="Insert Node",
1115 as_child=False,
1116 as_first_child=False,
1117 as_last_child=False,
1118):
1119 """Insert a node after the presently selected node."""
1120 u = c.undoer
1121 current = c.p
1122 if not current:
1123 return None
1124 c.endEditing()
1125 undoData = c.undoer.beforeInsertNode(current)
1126 if as_first_child:
1127 p = current.insertAsNthChild(0)
1128 elif as_last_child:
1129 p = current.insertAsLastChild()
1130 elif (
1131 as_child or
1132 (current.hasChildren() and current.isExpanded()) or
1133 (c.hoistStack and current == c.hoistStack[-1].p)
1134 ):
1135 # Make sure the new node is visible when hoisting.
1136 if c.config.getBool('insert-new-nodes-at-end'):
1137 p = current.insertAsLastChild()
1138 else:
1139 p = current.insertAsNthChild(0)
1140 else:
1141 p = current.insertAfter()
1142 g.doHook('create-node', c=c, p=p)
1143 p.setDirty()
1144 c.setChanged()
1145 u.afterInsertNode(p, op_name, undoData)
1146 c.redrawAndEdit(p, selectAll=True)
1147 return p
1148#@+node:ekr.20130922133218.11540: *3* c_oc.insertHeadlineBefore
1149@g.commander_command('insert-node-before')
1150def insertHeadlineBefore(self, event=None):
1151 """Insert a node before the presently selected node."""
1152 c, current, u = self, self.p, self.undoer
1153 op_name = 'Insert Node Before'
1154 if not current:
1155 return None
1156 # Can not insert before the base of a hoist.
1157 if c.hoistStack and current == c.hoistStack[-1].p:
1158 g.warning('can not insert a node before the base of a hoist')
1159 return None
1160 c.endEditing()
1161 undoData = u.beforeInsertNode(current)
1162 p = current.insertBefore()
1163 g.doHook('create-node', c=c, p=p)
1164 p.setDirty()
1165 c.setChanged()
1166 u.afterInsertNode(p, op_name, undoData)
1167 c.redrawAndEdit(p, selectAll=True)
1168 return p
1169#@+node:ekr.20031218072017.2922: ** c_oc.Mark commands
1170#@+node:ekr.20090905110447.6098: *3* c_oc.cloneMarked
1171@g.commander_command('clone-marked-nodes')
1172def cloneMarked(self, event=None):
1173 """Clone all marked nodes as children of a new node."""
1174 c, u = self, self.undoer
1175 p1 = c.p.copy()
1176 # Create a new node to hold clones.
1177 parent = p1.insertAfter()
1178 parent.h = 'Clones of marked nodes'
1179 cloned, n, p = [], 0, c.rootPosition()
1180 while p:
1181 # Careful: don't clone already-cloned nodes.
1182 if p == parent:
1183 p.moveToNodeAfterTree()
1184 elif p.isMarked() and p.v not in cloned:
1185 cloned.append(p.v)
1186 if 0: # old code
1187 # Calling p.clone would cause problems
1188 p.clone().moveToLastChildOf(parent)
1189 else: # New code.
1190 # Create the clone directly as a child of parent.
1191 p2 = p.copy()
1192 n = parent.numberOfChildren()
1193 p2._linkAsNthChild(parent, n)
1194 p.moveToNodeAfterTree()
1195 n += 1
1196 else:
1197 p.moveToThreadNext()
1198 if n:
1199 c.setChanged()
1200 parent.expand()
1201 c.selectPosition(parent)
1202 u.afterCloneMarkedNodes(p1)
1203 else:
1204 parent.doDelete()
1205 c.selectPosition(p1)
1206 if not g.unitTesting:
1207 g.blue(f"cloned {n} nodes")
1208 c.redraw()
1209#@+node:ekr.20160502090456.1: *3* c_oc.copyMarked
1210@g.commander_command('copy-marked-nodes')
1211def copyMarked(self, event=None):
1212 """Copy all marked nodes as children of a new node."""
1213 c, u = self, self.undoer
1214 p1 = c.p.copy()
1215 # Create a new node to hold clones.
1216 parent = p1.insertAfter()
1217 parent.h = 'Copies of marked nodes'
1218 copied, n, p = [], 0, c.rootPosition()
1219 while p:
1220 # Careful: don't clone already-cloned nodes.
1221 if p == parent:
1222 p.moveToNodeAfterTree()
1223 elif p.isMarked() and p.v not in copied:
1224 copied.append(p.v)
1225 p2 = p.copyWithNewVnodes(copyMarked=True)
1226 p2._linkAsNthChild(parent, n)
1227 p.moveToNodeAfterTree()
1228 n += 1
1229 else:
1230 p.moveToThreadNext()
1231 if n:
1232 c.setChanged()
1233 parent.expand()
1234 c.selectPosition(parent)
1235 u.afterCopyMarkedNodes(p1)
1236 else:
1237 parent.doDelete()
1238 c.selectPosition(p1)
1239 if not g.unitTesting:
1240 g.blue(f"copied {n} nodes")
1241 c.redraw()
1242#@+node:ekr.20111005081134.15540: *3* c_oc.deleteMarked
1243@g.commander_command('delete-marked-nodes')
1244def deleteMarked(self, event=None):
1245 """Delete all marked nodes."""
1246 c, u = self, self.undoer
1247 p1 = c.p.copy()
1248 undo_data, p = [], c.rootPosition()
1249 while p:
1250 if p.isMarked():
1251 undo_data.append(p.copy())
1252 next = p.positionAfterDeletedTree()
1253 p.doDelete()
1254 p = next
1255 else:
1256 p.moveToThreadNext()
1257 if undo_data:
1258 u.afterDeleteMarkedNodes(undo_data, p1)
1259 if not g.unitTesting:
1260 g.blue(f"deleted {len(undo_data)} nodes")
1261 c.setChanged()
1262 # Don't even *think* about restoring the old position.
1263 c.contractAllHeadlines()
1264 c.redraw(c.rootPosition())
1265#@+node:ekr.20111005081134.15539: *3* c_oc.moveMarked & helper
1266@g.commander_command('move-marked-nodes')
1267def moveMarked(self, event=None):
1268 """
1269 Move all marked nodes as children of a new node.
1270 This command is not undoable.
1271 Consider using clone-marked-nodes, followed by copy/paste instead.
1272 """
1273 c = self
1274 p1 = c.p.copy()
1275 # Check for marks.
1276 for v in c.all_unique_nodes():
1277 if v.isMarked():
1278 break
1279 else:
1280 g.warning('no marked nodes')
1281 return
1282 result = g.app.gui.runAskYesNoDialog(c,
1283 'Move Marked Nodes?',
1284 message='move-marked-nodes is not undoable\nProceed?',
1285 )
1286 if result == 'no':
1287 return
1288 # Create a new *root* node to hold the moved nodes.
1289 # This node's position remains stable while other nodes move.
1290 parent = createMoveMarkedNode(c)
1291 assert not parent.isMarked()
1292 moved = []
1293 p = c.rootPosition()
1294 while p:
1295 assert parent == c.rootPosition()
1296 # Careful: don't move already-moved nodes.
1297 if p.isMarked() and not parent.isAncestorOf(p):
1298 moved.append(p.copy())
1299 next = p.positionAfterDeletedTree()
1300 p.moveToLastChildOf(parent) # This does not change parent's position.
1301 p = next
1302 else:
1303 p.moveToThreadNext()
1304 if moved:
1305 # Find a position p2 outside of parent's tree with p2.v == p1.v.
1306 # Such a position may not exist.
1307 p2 = c.rootPosition()
1308 while p2:
1309 if p2 == parent:
1310 p2.moveToNodeAfterTree()
1311 elif p2.v == p1.v:
1312 break
1313 else:
1314 p2.moveToThreadNext()
1315 else:
1316 # Not found. Move to last top-level.
1317 p2 = c.lastTopLevel()
1318 parent.moveAfter(p2)
1319 # u.afterMoveMarkedNodes(moved, p1)
1320 if not g.unitTesting:
1321 g.blue(f"moved {len(moved)} nodes")
1322 c.setChanged()
1323 # Calling c.contractAllHeadlines() causes problems when in a chapter.
1324 c.redraw(parent)
1325#@+node:ekr.20111005081134.15543: *4* def createMoveMarkedNode
1326def createMoveMarkedNode(c):
1327 oldRoot = c.rootPosition()
1328 p = oldRoot.insertAfter()
1329 p.h = 'Moved marked nodes'
1330 p.moveToRoot()
1331 return p
1332#@+node:ekr.20031218072017.2923: *3* c_oc.markChangedHeadlines
1333@g.commander_command('mark-changed-items')
1334def markChangedHeadlines(self, event=None):
1335 """Mark all nodes that have been changed."""
1336 c, current, u = self, self.p, self.undoer
1337 undoType = 'Mark Changed'
1338 c.endEditing()
1339 u.beforeChangeGroup(current, undoType)
1340 for p in c.all_unique_positions():
1341 if p.isDirty() and not p.isMarked():
1342 bunch = u.beforeMark(p, undoType)
1343 # c.setMarked calls a hook.
1344 c.setMarked(p)
1345 p.setDirty()
1346 c.setChanged()
1347 u.afterMark(p, undoType, bunch)
1348 u.afterChangeGroup(current, undoType)
1349 if not g.unitTesting:
1350 g.blue('done')
1351 c.redraw_after_icons_changed()
1352#@+node:ekr.20031218072017.2924: *3* c_oc.markChangedRoots
1353def markChangedRoots(self, event=None):
1354 """Mark all changed @root nodes."""
1355 c, current, u = self, self.p, self.undoer
1356 undoType = 'Mark Changed'
1357 c.endEditing()
1358 u.beforeChangeGroup(current, undoType)
1359 for p in c.all_unique_positions():
1360 if p.isDirty() and not p.isMarked():
1361 s = p.b
1362 flag, i = g.is_special(s, "@root")
1363 if flag:
1364 bunch = u.beforeMark(p, undoType)
1365 c.setMarked(p) # Calls a hook.
1366 p.setDirty()
1367 c.setChanged()
1368 u.afterMark(p, undoType, bunch)
1369 u.afterChangeGroup(current, undoType)
1370 if not g.unitTesting:
1371 g.blue('done')
1372 c.redraw_after_icons_changed()
1373#@+node:ekr.20031218072017.2928: *3* c_oc.markHeadline
1374@g.commander_command('mark') # Compatibility
1375@g.commander_command('toggle-mark')
1376def markHeadline(self, event=None):
1377 """Toggle the mark of the selected node."""
1378 c, p, u = self, self.p, self.undoer
1379 if not p:
1380 return
1381 c.endEditing()
1382 undoType = 'Unmark' if p.isMarked() else 'Mark'
1383 bunch = u.beforeMark(p, undoType)
1384 # c.set/clearMarked call a hook.
1385 if p.isMarked():
1386 c.clearMarked(p)
1387 else:
1388 c.setMarked(p)
1389 p.setDirty()
1390 c.setChanged()
1391 u.afterMark(p, undoType, bunch)
1392 c.redraw_after_icons_changed()
1393#@+node:ekr.20031218072017.2929: *3* c_oc.markSubheads
1394@g.commander_command('mark-subheads')
1395def markSubheads(self, event=None):
1396 """Mark all children of the selected node as changed."""
1397 c, current, u = self, self.p, self.undoer
1398 undoType = 'Mark Subheads'
1399 if not current:
1400 return
1401 c.endEditing()
1402 u.beforeChangeGroup(current, undoType)
1403 for p in current.children():
1404 if not p.isMarked():
1405 bunch = u.beforeMark(p, undoType)
1406 c.setMarked(p) # Calls a hook.
1407 p.setDirty()
1408 c.setChanged()
1409 u.afterMark(p, undoType, bunch)
1410 u.afterChangeGroup(current, undoType)
1411 c.redraw_after_icons_changed()
1412#@+node:ekr.20031218072017.2930: *3* c_oc.unmarkAll
1413@g.commander_command('unmark-all')
1414def unmarkAll(self, event=None):
1415 """Unmark all nodes in the entire outline."""
1416 c, current, u = self, self.p, self.undoer
1417 undoType = 'Unmark All'
1418 if not current:
1419 return
1420 c.endEditing()
1421 u.beforeChangeGroup(current, undoType)
1422 changed = False
1423 p = None # To keep pylint happy.
1424 for p in c.all_unique_positions():
1425 if p.isMarked():
1426 bunch = u.beforeMark(p, undoType)
1427 # c.clearMarked(p) # Very slow: calls a hook.
1428 p.v.clearMarked()
1429 p.setDirty()
1430 u.afterMark(p, undoType, bunch)
1431 changed = True
1432 if changed:
1433 g.doHook("clear-all-marks", c=c, p=p)
1434 c.setChanged()
1435 u.afterChangeGroup(current, undoType)
1436 c.redraw_after_icons_changed()
1437#@+node:ekr.20031218072017.1766: ** c_oc.Move commands
1438#@+node:ekr.20031218072017.1767: *3* c_oc.demote
1439@g.commander_command('demote')
1440def demote(self, event=None):
1441 """Make all following siblings children of the selected node."""
1442 c, p, u = self, self.p, self.undoer
1443 if not p or not p.hasNext():
1444 c.treeFocusHelper()
1445 return
1446 # Make sure all the moves will be valid.
1447 next = p.next()
1448 while next:
1449 if not c.checkMoveWithParentWithWarning(next, p, True):
1450 c.treeFocusHelper()
1451 return
1452 next.moveToNext()
1453 c.endEditing()
1454 parent_v = p._parentVnode()
1455 n = p.childIndex()
1456 followingSibs = parent_v.children[n + 1 :]
1457 # Remove the moved nodes from the parent's children.
1458 parent_v.children = parent_v.children[: n + 1]
1459 # Add the moved nodes to p's children
1460 p.v.children.extend(followingSibs)
1461 # Adjust the parent links in the moved nodes.
1462 # There is no need to adjust descendant links.
1463 for child in followingSibs:
1464 child.parents.remove(parent_v)
1465 child.parents.append(p.v)
1466 p.expand()
1467 p.setDirty()
1468 c.setChanged()
1469 u.afterDemote(p, followingSibs)
1470 c.redraw(p)
1471 c.updateSyntaxColorer(p) # Moving can change syntax coloring.
1472#@+node:ekr.20031218072017.1768: *3* c_oc.moveOutlineDown
1473@g.commander_command('move-outline-down')
1474def moveOutlineDown(self, event=None):
1475 """Move the selected node down."""
1476 # Moving down is more tricky than moving up because we can't
1477 # move p to be a child of itself.
1478 #
1479 # An important optimization:
1480 # we don't have to call checkMoveWithParentWithWarning() if the parent of
1481 # the moved node remains the same.
1482 c, p, u = self, self.p, self.undoer
1483 if not p:
1484 return
1485 if not c.canMoveOutlineDown():
1486 if c.hoistStack:
1487 cantMoveMessage(c)
1488 c.treeFocusHelper()
1489 return
1490 parent = p.parent()
1491 next = p.visNext(c)
1492 while next and p.isAncestorOf(next):
1493 next = next.visNext(c)
1494 if not next:
1495 if c.hoistStack:
1496 cantMoveMessage(c)
1497 c.treeFocusHelper()
1498 return
1499 c.endEditing()
1500 undoData = u.beforeMoveNode(p)
1501 #@+<< Move p down & set moved if successful >>
1502 #@+node:ekr.20031218072017.1769: *4* << Move p down & set moved if successful >>
1503 if next.hasChildren() and next.isExpanded():
1504 # Attempt to move p to the first child of next.
1505 moved = c.checkMoveWithParentWithWarning(p, next, True)
1506 if moved:
1507 p.setDirty()
1508 p.moveToNthChildOf(next, 0)
1509 else:
1510 # Attempt to move p after next.
1511 moved = c.checkMoveWithParentWithWarning(p, next.parent(), True)
1512 if moved:
1513 p.setDirty()
1514 p.moveAfter(next)
1515 # Patch by nh2: 0004-Add-bool-collapse_nodes_after_move-option.patch
1516 if (
1517 c.collapse_nodes_after_move
1518 and moved and c.sparse_move
1519 and parent and not parent.isAncestorOf(p)
1520 ):
1521 # New in Leo 4.4.2: contract the old parent if it is no longer the parent of p.
1522 parent.contract()
1523 #@-<< Move p down & set moved if successful >>
1524 if moved:
1525 p.setDirty()
1526 c.setChanged()
1527 u.afterMoveNode(p, 'Move Down', undoData)
1528 c.redraw(p)
1529 c.updateSyntaxColorer(p) # Moving can change syntax coloring.
1530#@+node:ekr.20031218072017.1770: *3* c_oc.moveOutlineLeft
1531@g.commander_command('move-outline-left')
1532def moveOutlineLeft(self, event=None):
1533 """Move the selected node left if possible."""
1534 c, p, u = self, self.p, self.undoer
1535 if not p:
1536 return
1537 if not c.canMoveOutlineLeft():
1538 if c.hoistStack:
1539 cantMoveMessage(c)
1540 c.treeFocusHelper()
1541 return
1542 if not p.hasParent():
1543 c.treeFocusHelper()
1544 return
1545 parent = p.parent()
1546 c.endEditing()
1547 undoData = u.beforeMoveNode(p)
1548 p.setDirty()
1549 p.moveAfter(parent)
1550 p.setDirty()
1551 c.setChanged()
1552 u.afterMoveNode(p, 'Move Left', undoData)
1553 # Patch by nh2: 0004-Add-bool-collapse_nodes_after_move-option.patch
1554 if c.collapse_nodes_after_move and c.sparse_move: # New in Leo 4.4.2
1555 parent.contract()
1556 c.redraw(p)
1557 c.recolor() # Moving can change syntax coloring.
1558#@+node:ekr.20031218072017.1771: *3* c_oc.moveOutlineRight
1559@g.commander_command('move-outline-right')
1560def moveOutlineRight(self, event=None):
1561 """Move the selected node right if possible."""
1562 c, p, u = self, self.p, self.undoer
1563 if not p:
1564 return
1565 if not c.canMoveOutlineRight(): # 11/4/03: Support for hoist.
1566 if c.hoistStack:
1567 cantMoveMessage(c)
1568 c.treeFocusHelper()
1569 return
1570 back = p.back()
1571 if not back:
1572 c.treeFocusHelper()
1573 return
1574 if not c.checkMoveWithParentWithWarning(p, back, True):
1575 c.treeFocusHelper()
1576 return
1577 c.endEditing()
1578 undoData = u.beforeMoveNode(p)
1579 p.setDirty()
1580 n = back.numberOfChildren()
1581 p.moveToNthChildOf(back, n)
1582 p.setDirty()
1583 c.setChanged() # #2036.
1584 u.afterMoveNode(p, 'Move Right', undoData)
1585 c.redraw(p)
1586 c.recolor()
1587#@+node:ekr.20031218072017.1772: *3* c_oc.moveOutlineUp
1588@g.commander_command('move-outline-up')
1589def moveOutlineUp(self, event=None):
1590 """Move the selected node up if possible."""
1591 c, p, u = self, self.p, self.undoer
1592 if not p:
1593 return
1594 if not c.canMoveOutlineUp(): # Support for hoist.
1595 if c.hoistStack:
1596 cantMoveMessage(c)
1597 c.treeFocusHelper()
1598 return
1599 back = p.visBack(c)
1600 if not back:
1601 return
1602 back2 = back.visBack(c)
1603 c.endEditing()
1604 undoData = u.beforeMoveNode(p)
1605 moved = False
1606 #@+<< Move p up >>
1607 #@+node:ekr.20031218072017.1773: *4* << Move p up >>
1608 parent = p.parent()
1609 if not back2:
1610 if c.hoistStack: # hoist or chapter.
1611 limit, limitIsVisible = c.visLimit()
1612 assert limit
1613 if limitIsVisible:
1614 # canMoveOutlineUp should have caught this.
1615 g.trace('can not happen. In hoist')
1616 else:
1617 moved = True
1618 p.setDirty()
1619 p.moveToFirstChildOf(limit)
1620 else:
1621 # p will be the new root node
1622 p.setDirty()
1623 p.moveToRoot()
1624 moved = True
1625 elif back2.hasChildren() and back2.isExpanded():
1626 if c.checkMoveWithParentWithWarning(p, back2, True):
1627 moved = True
1628 p.setDirty()
1629 p.moveToNthChildOf(back2, 0)
1630 else:
1631 if c.checkMoveWithParentWithWarning(p, back2.parent(), True):
1632 moved = True
1633 p.setDirty()
1634 p.moveAfter(back2)
1635 # Patch by nh2: 0004-Add-bool-collapse_nodes_after_move-option.patch
1636 if (
1637 c.collapse_nodes_after_move
1638 and moved and c.sparse_move
1639 and parent and not parent.isAncestorOf(p)
1640 ):
1641 # New in Leo 4.4.2: contract the old parent if it is no longer the parent of p.
1642 parent.contract()
1643 #@-<< Move p up >>
1644 if moved:
1645 p.setDirty()
1646 c.setChanged()
1647 u.afterMoveNode(p, 'Move Up', undoData)
1648 c.redraw(p)
1649 c.updateSyntaxColorer(p) # Moving can change syntax coloring.
1650#@+node:ekr.20031218072017.1774: *3* c_oc.promote
1651@g.commander_command('promote')
1652def promote(self, event=None, undoFlag=True):
1653 """Make all children of the selected nodes siblings of the selected node."""
1654 c, p, u = self, self.p, self.undoer
1655 if not p or not p.hasChildren():
1656 c.treeFocusHelper()
1657 return
1658 c.endEditing()
1659 children = p.v.children # First, for undo.
1660 p.promote()
1661 c.setChanged()
1662 if undoFlag:
1663 p.setDirty()
1664 u.afterPromote(p, children)
1665 c.redraw(p)
1666 c.updateSyntaxColorer(p) # Moving can change syntax coloring.
1667#@+node:ekr.20071213185710: *3* c_oc.toggleSparseMove
1668@g.commander_command('toggle-sparse-move')
1669def toggleSparseMove(self, event=None):
1670 """Toggle whether moves collapse the outline."""
1671 c = self
1672 c.sparse_move = not c.sparse_move
1673 if not g.unitTesting:
1674 g.blue(f"sparse-move: {c.sparse_move}")
1675#@+node:ekr.20080425060424.1: ** c_oc.Sort commands
1676#@+node:ekr.20050415134809: *3* c_oc.sortChildren
1677@g.commander_command('sort-children')
1678def sortChildren(self, event=None, key=None, reverse=False):
1679 """Sort the children of a node."""
1680 # This method no longer supports the 'cmp' keyword arg.
1681 c, p = self, self.p
1682 if p and p.hasChildren():
1683 c.sortSiblings(p=p.firstChild(), sortChildren=True, key=key, reverse=reverse)
1684#@+node:ekr.20050415134809.1: *3* c_oc.sortSiblings
1685@g.commander_command('sort-siblings')
1686def sortSiblings(self, event=None,
1687 # cmp keyword is no longer supported.
1688 key=None,
1689 p=None,
1690 sortChildren=False,
1691 reverse=False
1692):
1693 """Sort the siblings of a node."""
1694 c, u = self, self.undoer
1695 if not p:
1696 p = c.p
1697 if not p:
1698 return
1699 c.endEditing()
1700 undoType = 'Sort Children' if sortChildren else 'Sort Siblings'
1701 parent_v = p._parentVnode()
1702 oldChildren = parent_v.children[:]
1703 newChildren = parent_v.children[:]
1704 if key is None:
1706 def lowerKey(self):
1707 return self.h.lower()
1709 key = lowerKey
1710 newChildren.sort(key=key, reverse=reverse)
1711 if oldChildren == newChildren:
1712 return
1713 # 2010/01/20. Fix bug 510148.
1714 c.setChanged()
1715 bunch = u.beforeSort(p, undoType, oldChildren, newChildren, sortChildren)
1716 parent_v.children = newChildren
1717 u.afterSort(p, bunch)
1718 # Sorting destroys position p, and possibly the root position.
1719 p = c.setPositionAfterSort(sortChildren)
1720 if p.parent():
1721 p.parent().setDirty()
1722 c.redraw(p)
1723#@+node:ekr.20070420092425: ** def cantMoveMessage
1724def cantMoveMessage(c):
1725 h = c.rootPosition().h
1726 kind = 'chapter' if h.startswith('@chapter') else 'hoist'
1727 g.warning("can't move node out of", kind)
1728#@+node:ekr.20180201040936.1: ** count-children
1729@g.command('count-children')
1730def count_children(event=None):
1731 c = event and event.get('c')
1732 if c:
1733 g.es_print(f"{c.p.numberOfChildren()} children")
1734#@-others
1735#@-leo