Coverage for C:\leo.repo\leo-editor\leo\commands\commanderOutlineCommands.py: 42%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
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 = {x.gnx for x in skip_root(c.hiddenRootNode)}
322 # outside will contain gnxes of nodes that are outside the copied tree
324 translation = {x: translate_gnx(x) for x in bodies}
325 # we generate new gnx for each node in the copied tree
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 c.expandOnlyAncestorsOfNode(p=p)
711 c.redraw()
712#@+node:ekr.20031218072017.2915: *3* c_oc.goToLastNode
713@g.commander_command('goto-last-node')
714def goToLastNode(self, event=None):
715 """Select the last node in the entire tree."""
716 c = self
717 p = c.rootPosition()
718 while p and p.hasThreadNext():
719 p.moveToThreadNext()
720 c.expandOnlyAncestorsOfNode(p=p)
721 c.redraw()
722#@+node:ekr.20051012092847.1: *3* c_oc.goToLastSibling
723@g.commander_command('goto-last-sibling')
724def goToLastSibling(self, event=None):
725 """Select the last sibling of the selected node."""
726 c, p = self, self.p
727 if p.hasNext():
728 while p.hasNext():
729 p.moveToNext()
730 c.treeSelectHelper(p)
731#@+node:ekr.20050711153537: *3* c_oc.goToLastVisibleNode
732@g.commander_command('goto-last-visible-node')
733def goToLastVisibleNode(self, event=None):
734 """Select the last visible node of selected chapter or hoist."""
735 c = self
736 p = c.lastVisible()
737 if p:
738 c.expandOnlyAncestorsOfNode(p=p)
739 c.redraw()
740#@+node:ekr.20031218072017.2916: *3* c_oc.goToNextClone
741@g.commander_command('goto-next-clone')
742def goToNextClone(self, event=None):
743 """
744 Select the next node that is a clone of the selected node.
745 If the selected node is not a clone, do find-next-clone.
746 """
747 c, p = self, self.p
748 cc = c.chapterController
749 if not p:
750 return
751 if not p.isCloned():
752 c.findNextClone()
753 return
754 v = p.v
755 p.moveToThreadNext()
756 wrapped = False
757 while 1:
758 if p and p.v == v:
759 break
760 elif p:
761 p.moveToThreadNext()
762 elif wrapped:
763 break
764 else:
765 wrapped = True
766 p = c.rootPosition()
767 if p:
768 c.expandAllAncestors(p)
769 if cc:
770 # #252: goto-next clone activate chapter.
771 chapter = cc.getSelectedChapter()
772 old_name = chapter and chapter.name
773 new_name = cc.findChapterNameForPosition(p)
774 if new_name != old_name:
775 cc.selectChapterByName(new_name)
776 # Always do a full redraw.
777 c.redraw(p)
778 else:
779 g.blue('done')
780#@+node:ekr.20031218072017.2917: *3* c_oc.goToNextDirtyHeadline
781@g.commander_command('goto-next-changed')
782def goToNextDirtyHeadline(self, event=None):
783 """Select the node that is marked as changed."""
784 c, p = self, self.p
785 if not p:
786 return
787 p.moveToThreadNext()
788 wrapped = False
789 while 1:
790 if p and p.isDirty():
791 break
792 elif p:
793 p.moveToThreadNext()
794 elif wrapped:
795 break
796 else:
797 wrapped = True
798 p = c.rootPosition()
799 if not p:
800 g.blue('done')
801 c.treeSelectHelper(p) # Sets focus.
802#@+node:ekr.20031218072017.2918: *3* c_oc.goToNextMarkedHeadline
803@g.commander_command('goto-next-marked')
804def goToNextMarkedHeadline(self, event=None):
805 """Select the next marked node."""
806 c, p = self, self.p
807 if not p:
808 return
809 p.moveToThreadNext()
810 wrapped = False
811 while 1:
812 if p and p.isMarked():
813 break
814 elif p:
815 p.moveToThreadNext()
816 elif wrapped:
817 break
818 else:
819 wrapped = True
820 p = c.rootPosition()
821 if not p:
822 g.blue('done')
823 c.treeSelectHelper(p) # Sets focus.
824#@+node:ekr.20031218072017.2919: *3* c_oc.goToNextSibling
825@g.commander_command('goto-next-sibling')
826def goToNextSibling(self, event=None):
827 """Select the next sibling of the selected node."""
828 c, p = self, self.p
829 c.treeSelectHelper(p and p.next())
830#@+node:ekr.20031218072017.2920: *3* c_oc.goToParent
831@g.commander_command('goto-parent')
832def goToParent(self, event=None):
833 """Select the parent of the selected node."""
834 c, p = self, self.p
835 c.treeSelectHelper(p and p.parent())
836#@+node:ekr.20190211104913.1: *3* c_oc.goToPrevMarkedHeadline
837@g.commander_command('goto-prev-marked')
838def goToPrevMarkedHeadline(self, event=None):
839 """Select the next marked node."""
840 c, p = self, self.p
841 if not p:
842 return
843 p.moveToThreadBack()
844 wrapped = False
845 while 1:
846 if p and p.isMarked():
847 break
848 elif p:
849 p.moveToThreadBack()
850 elif wrapped:
851 break
852 else:
853 wrapped = True
854 p = c.rootPosition()
855 if not p:
856 g.blue('done')
857 c.treeSelectHelper(p) # Sets focus.
858#@+node:ekr.20031218072017.2921: *3* c_oc.goToPrevSibling
859@g.commander_command('goto-prev-sibling')
860def goToPrevSibling(self, event=None):
861 """Select the previous sibling of the selected node."""
862 c, p = self, self.p
863 c.treeSelectHelper(p and p.back())
864#@+node:ekr.20031218072017.2993: *3* c_oc.selectThreadBack
865@g.commander_command('goto-prev-node')
866def selectThreadBack(self, event=None):
867 """Select the node preceding the selected node in outline order."""
868 c, p = self, self.p
869 if not p:
870 return
871 p.moveToThreadBack()
872 c.treeSelectHelper(p)
873#@+node:ekr.20031218072017.2994: *3* c_oc.selectThreadNext
874@g.commander_command('goto-next-node')
875def selectThreadNext(self, event=None):
876 """Select the node following the selected node in outline order."""
877 c, p = self, self.p
878 if not p:
879 return
880 p.moveToThreadNext()
881 c.treeSelectHelper(p)
882#@+node:ekr.20031218072017.2995: *3* c_oc.selectVisBack
883@g.commander_command('goto-prev-visible')
884def selectVisBack(self, event=None):
885 """Select the visible node preceding the presently selected node."""
886 # This has an up arrow for a control key.
887 c, p = self, self.p
888 if not p:
889 return
890 if c.canSelectVisBack():
891 p.moveToVisBack(c)
892 c.treeSelectHelper(p)
893 else:
894 c.endEditing() # 2011/05/28: A special case.
895#@+node:ekr.20031218072017.2996: *3* c_oc.selectVisNext
896@g.commander_command('goto-next-visible')
897def selectVisNext(self, event=None):
898 """Select the visible node following the presently selected node."""
899 c, p = self, self.p
900 if not p:
901 return
902 if c.canSelectVisNext():
903 p.moveToVisNext(c)
904 c.treeSelectHelper(p)
905 else:
906 c.endEditing() # 2011/05/28: A special case.
907#@+node:ekr.20031218072017.2028: ** c_oc.hoist/dehoist/clearAllHoists
908#@+node:ekr.20120308061112.9865: *3* c_oc.deHoist
909@g.commander_command('de-hoist')
910@g.commander_command('dehoist')
911def dehoist(self, event=None):
912 """Undo a previous hoist of an outline."""
913 c = self
914 if not c.p or not c.hoistStack:
915 return
916 # Don't de-hoist an @chapter node.
917 if c.chapterController and c.p.h.startswith('@chapter '):
918 if not g.unitTesting:
919 g.es('can not de-hoist an @chapter node.', color='blue')
920 return
921 bunch = c.hoistStack.pop()
922 p = bunch.p
923 if not p:
924 p.expand()
925 else:
926 p.contract()
927 c.setCurrentPosition(p)
928 c.redraw()
929 c.frame.clearStatusLine()
930 c.frame.putStatusLine("De-Hoist: " + p.h)
931 c.undoer.afterDehoist(p, 'DeHoist')
932 g.doHook('hoist-changed', c=c)
933#@+node:ekr.20120308061112.9866: *3* c_oc.clearAllHoists
934@g.commander_command('clear-all-hoists')
935def clearAllHoists(self, event=None):
936 """Undo a previous hoist of an outline."""
937 c = self
938 c.hoistStack = []
939 c.frame.putStatusLine("Hoists cleared")
940 g.doHook('hoist-changed', c=c)
941#@+node:ekr.20120308061112.9867: *3* c_oc.hoist
942@g.commander_command('hoist')
943def hoist(self, event=None):
944 """Make only the selected outline visible."""
945 c = self
946 p = c.p
947 if not p:
948 return
949 # Don't hoist an @chapter node.
950 if c.chapterController and p.h.startswith('@chapter '):
951 if not g.unitTesting:
952 g.es('can not hoist an @chapter node.', color='blue')
953 return
954 # Remember the expansion state.
955 bunch = g.Bunch(p=p.copy(), expanded=p.isExpanded())
956 c.hoistStack.append(bunch)
957 p.expand()
958 c.redraw(p)
959 c.frame.clearStatusLine()
960 c.frame.putStatusLine("Hoist: " + p.h)
961 c.undoer.afterHoist(p, 'Hoist')
962 g.doHook('hoist-changed', c=c)
963#@+node:ekr.20031218072017.1759: ** c_oc.Insert, Delete & Clone commands
964#@+node:ekr.20031218072017.1762: *3* c_oc.clone
965@g.commander_command('clone-node')
966def clone(self, event=None):
967 """Create a clone of the selected outline."""
968 c, p, u = self, self.p, self.undoer
969 if not p:
970 return None
971 undoData = c.undoer.beforeCloneNode(p)
972 c.endEditing() # Capture any changes to the headline.
973 clone = p.clone()
974 clone.setDirty()
975 c.setChanged()
976 if c.validateOutline():
977 u.afterCloneNode(clone, 'Clone Node', undoData)
978 c.redraw(clone)
979 c.treeWantsFocus()
980 return clone # For mod_labels and chapters plugins.
981 clone.doDelete()
982 c.setCurrentPosition(p)
983 return None
984#@+node:ekr.20150630152607.1: *3* c_oc.cloneToAtSpot
985@g.commander_command('clone-to-at-spot')
986def cloneToAtSpot(self, event=None):
987 """
988 Create a clone of the selected node and move it to the last @spot node
989 of the outline. Create the @spot node if necessary.
990 """
991 c, p, u = self, self.p, self.undoer
992 if not p:
993 return
994 # 2015/12/27: fix bug 220: do not allow clone-to-at-spot on @spot node.
995 if p.h.startswith('@spot'):
996 g.es("can not clone @spot node", color='red')
997 return
998 last_spot = None
999 for p2 in c.all_positions():
1000 if g.match_word(p2.h, 0, '@spot'):
1001 last_spot = p2.copy()
1002 if not last_spot:
1003 last = c.lastTopLevel()
1004 last_spot = last.insertAfter()
1005 last_spot.h = '@spot'
1006 undoData = c.undoer.beforeCloneNode(p)
1007 c.endEditing() # Capture any changes to the headline.
1008 clone = p.copy()
1009 clone._linkAsNthChild(last_spot, n=last_spot.numberOfChildren())
1010 clone.setDirty()
1011 c.setChanged()
1012 if c.validateOutline():
1013 u.afterCloneNode(clone, 'Clone Node', undoData)
1014 c.contractAllHeadlines()
1015 c.redraw(clone)
1016 else:
1017 clone.doDelete()
1018 c.setCurrentPosition(p)
1019#@+node:ekr.20141023154408.5: *3* c_oc.cloneToLastNode
1020@g.commander_command('clone-node-to-last-node')
1021def cloneToLastNode(self, event=None):
1022 """
1023 Clone the selected node and move it to the last node.
1024 Do *not* change the selected node.
1025 """
1026 c, p, u = self, self.p, self.undoer
1027 if not p:
1028 return
1029 prev = p.copy()
1030 undoData = c.undoer.beforeCloneNode(p)
1031 c.endEditing() # Capture any changes to the headline.
1032 clone = p.clone()
1033 last = c.rootPosition()
1034 while last and last.hasNext():
1035 last.moveToNext()
1036 clone.moveAfter(last)
1037 clone.setDirty()
1038 c.setChanged()
1039 u.afterCloneNode(clone, 'Clone Node To Last', undoData)
1040 c.redraw(prev)
1041 # return clone # For mod_labels and chapters plugins.
1042#@+node:ekr.20031218072017.1193: *3* c_oc.deleteOutline
1043@g.commander_command('delete-node')
1044def deleteOutline(self, event=None, op_name="Delete Node"):
1045 """Deletes the selected outline."""
1046 c, u = self, self.undoer
1047 p = c.p
1048 if not p:
1049 return
1050 c.endEditing() # Make sure we capture the headline for Undo.
1051 if False: # c.config.getBool('select-next-after-delete'):
1052 # #721: Optionally select next node after delete.
1053 if p.hasVisNext(c):
1054 newNode = p.visNext(c)
1055 elif p.hasParent():
1056 newNode = p.parent()
1057 else:
1058 newNode = p.back() # _not_ p.visBack(): we are at the top level.
1059 else:
1060 # Legacy: select previous node if possible.
1061 if p.hasVisBack(c):
1062 newNode = p.visBack(c)
1063 else:
1064 newNode = p.next() # _not_ p.visNext(): we are at the top level.
1065 if not newNode:
1066 return
1067 undoData = u.beforeDeleteNode(p)
1068 p.setDirty()
1069 p.doDelete(newNode)
1070 c.setChanged()
1071 u.afterDeleteNode(newNode, op_name, undoData)
1072 c.redraw(newNode)
1073 c.validateOutline()
1074#@+node:ekr.20071005173203.1: *3* c_oc.insertChild
1075@g.commander_command('insert-child')
1076def insertChild(self, event=None):
1077 """Insert a node after the presently selected node."""
1078 c = self
1079 return c.insertHeadline(event=event, op_name='Insert Child', as_child=True)
1080#@+node:ekr.20031218072017.1761: *3* c_oc.insertHeadline (insert-*)
1081@g.commander_command('insert-node')
1082def insertHeadline(self, event=None, op_name="Insert Node", as_child=False):
1083 """
1084 If c.p is expanded, insert a new node as the first or last child of c.p,
1085 depending on @bool insert-new-nodes-at-end.
1087 If c.p is not expanded, insert a new node after c.p.
1088 """
1089 c = self
1090 # Fix #600.
1091 return insertHeadlineHelper(c, event=event, as_child=as_child)
1093@g.commander_command('insert-as-first-child')
1094def insertNodeAsFirstChild(self, event=None):
1095 """Insert a node as the last last of the previous node."""
1096 c = self
1097 return insertHeadlineHelper(c, event=event, as_first_child=True)
1099@g.commander_command('insert-as-last-child')
1100def insertNodeAsLastChild(self, event=None):
1101 """Insert a node as the last child of the previous node."""
1102 c = self
1103 return insertHeadlineHelper(c, event=event, as_last_child=True)
1104#@+node:ekr.20171124091846.1: *4* function: insertHeadlineHelper
1105def insertHeadlineHelper(c,
1106 event=None,
1107 op_name="Insert Node",
1108 as_child=False,
1109 as_first_child=False,
1110 as_last_child=False,
1111):
1112 """Insert a node after the presently selected node."""
1113 u = c.undoer
1114 current = c.p
1115 if not current:
1116 return None
1117 c.endEditing()
1118 undoData = c.undoer.beforeInsertNode(current)
1119 if as_first_child:
1120 p = current.insertAsNthChild(0)
1121 elif as_last_child:
1122 p = current.insertAsLastChild()
1123 elif (
1124 as_child or
1125 (current.hasChildren() and current.isExpanded()) or
1126 (c.hoistStack and current == c.hoistStack[-1].p)
1127 ):
1128 # Make sure the new node is visible when hoisting.
1129 if c.config.getBool('insert-new-nodes-at-end'):
1130 p = current.insertAsLastChild()
1131 else:
1132 p = current.insertAsNthChild(0)
1133 else:
1134 p = current.insertAfter()
1135 g.doHook('create-node', c=c, p=p)
1136 p.setDirty()
1137 c.setChanged()
1138 u.afterInsertNode(p, op_name, undoData)
1139 c.redrawAndEdit(p, selectAll=True)
1140 return p
1141#@+node:ekr.20130922133218.11540: *3* c_oc.insertHeadlineBefore
1142@g.commander_command('insert-node-before')
1143def insertHeadlineBefore(self, event=None):
1144 """Insert a node before the presently selected node."""
1145 c, current, u = self, self.p, self.undoer
1146 op_name = 'Insert Node Before'
1147 if not current:
1148 return None
1149 # Can not insert before the base of a hoist.
1150 if c.hoistStack and current == c.hoistStack[-1].p:
1151 g.warning('can not insert a node before the base of a hoist')
1152 return None
1153 c.endEditing()
1154 undoData = u.beforeInsertNode(current)
1155 p = current.insertBefore()
1156 g.doHook('create-node', c=c, p=p)
1157 p.setDirty()
1158 c.setChanged()
1159 u.afterInsertNode(p, op_name, undoData)
1160 c.redrawAndEdit(p, selectAll=True)
1161 return p
1162#@+node:ekr.20031218072017.2922: ** c_oc.Mark commands
1163#@+node:ekr.20090905110447.6098: *3* c_oc.cloneMarked
1164@g.commander_command('clone-marked-nodes')
1165def cloneMarked(self, event=None):
1166 """Clone all marked nodes as children of a new node."""
1167 c, u = self, self.undoer
1168 p1 = c.p.copy()
1169 # Create a new node to hold clones.
1170 parent = p1.insertAfter()
1171 parent.h = 'Clones of marked nodes'
1172 cloned, n, p = [], 0, c.rootPosition()
1173 while p:
1174 # Careful: don't clone already-cloned nodes.
1175 if p == parent:
1176 p.moveToNodeAfterTree()
1177 elif p.isMarked() and p.v not in cloned:
1178 cloned.append(p.v)
1179 if 0: # old code
1180 # Calling p.clone would cause problems
1181 p.clone().moveToLastChildOf(parent)
1182 else: # New code.
1183 # Create the clone directly as a child of parent.
1184 p2 = p.copy()
1185 n = parent.numberOfChildren()
1186 p2._linkAsNthChild(parent, n)
1187 p.moveToNodeAfterTree()
1188 n += 1
1189 else:
1190 p.moveToThreadNext()
1191 if n:
1192 c.setChanged()
1193 parent.expand()
1194 c.selectPosition(parent)
1195 u.afterCloneMarkedNodes(p1)
1196 else:
1197 parent.doDelete()
1198 c.selectPosition(p1)
1199 if not g.unitTesting:
1200 g.blue(f"cloned {n} nodes")
1201 c.redraw()
1202#@+node:ekr.20160502090456.1: *3* c_oc.copyMarked
1203@g.commander_command('copy-marked-nodes')
1204def copyMarked(self, event=None):
1205 """Copy all marked nodes as children of a new node."""
1206 c, u = self, self.undoer
1207 p1 = c.p.copy()
1208 # Create a new node to hold clones.
1209 parent = p1.insertAfter()
1210 parent.h = 'Copies of marked nodes'
1211 copied, n, p = [], 0, c.rootPosition()
1212 while p:
1213 # Careful: don't clone already-cloned nodes.
1214 if p == parent:
1215 p.moveToNodeAfterTree()
1216 elif p.isMarked() and p.v not in copied:
1217 copied.append(p.v)
1218 p2 = p.copyWithNewVnodes(copyMarked=True)
1219 p2._linkAsNthChild(parent, n)
1220 p.moveToNodeAfterTree()
1221 n += 1
1222 else:
1223 p.moveToThreadNext()
1224 if n:
1225 c.setChanged()
1226 parent.expand()
1227 c.selectPosition(parent)
1228 u.afterCopyMarkedNodes(p1)
1229 else:
1230 parent.doDelete()
1231 c.selectPosition(p1)
1232 if not g.unitTesting:
1233 g.blue(f"copied {n} nodes")
1234 c.redraw()
1235#@+node:ekr.20111005081134.15540: *3* c_oc.deleteMarked
1236@g.commander_command('delete-marked-nodes')
1237def deleteMarked(self, event=None):
1238 """Delete all marked nodes."""
1239 c, u = self, self.undoer
1240 p1 = c.p.copy()
1241 undo_data, p = [], c.rootPosition()
1242 while p:
1243 if p.isMarked():
1244 undo_data.append(p.copy())
1245 next = p.positionAfterDeletedTree()
1246 p.doDelete()
1247 p = next
1248 else:
1249 p.moveToThreadNext()
1250 if undo_data:
1251 u.afterDeleteMarkedNodes(undo_data, p1)
1252 if not g.unitTesting:
1253 g.blue(f"deleted {len(undo_data)} nodes")
1254 c.setChanged()
1255 # Don't even *think* about restoring the old position.
1256 c.contractAllHeadlines()
1257 c.redraw(c.rootPosition())
1258#@+node:ekr.20111005081134.15539: *3* c_oc.moveMarked & helper
1259@g.commander_command('move-marked-nodes')
1260def moveMarked(self, event=None):
1261 """
1262 Move all marked nodes as children of a new node.
1263 This command is not undoable.
1264 Consider using clone-marked-nodes, followed by copy/paste instead.
1265 """
1266 c = self
1267 p1 = c.p.copy()
1268 # Check for marks.
1269 for v in c.all_unique_nodes():
1270 if v.isMarked():
1271 break
1272 else:
1273 g.warning('no marked nodes')
1274 return
1275 result = g.app.gui.runAskYesNoDialog(c,
1276 'Move Marked Nodes?',
1277 message='move-marked-nodes is not undoable\nProceed?',
1278 )
1279 if result == 'no':
1280 return
1281 # Create a new *root* node to hold the moved nodes.
1282 # This node's position remains stable while other nodes move.
1283 parent = createMoveMarkedNode(c)
1284 assert not parent.isMarked()
1285 moved = []
1286 p = c.rootPosition()
1287 while p:
1288 assert parent == c.rootPosition()
1289 # Careful: don't move already-moved nodes.
1290 if p.isMarked() and not parent.isAncestorOf(p):
1291 moved.append(p.copy())
1292 next = p.positionAfterDeletedTree()
1293 p.moveToLastChildOf(parent)
1294 # This does not change parent's position.
1295 p = next
1296 else:
1297 p.moveToThreadNext()
1298 if moved:
1299 # Find a position p2 outside of parent's tree with p2.v == p1.v.
1300 # Such a position may not exist.
1301 p2 = c.rootPosition()
1302 while p2:
1303 if p2 == parent:
1304 p2.moveToNodeAfterTree()
1305 elif p2.v == p1.v:
1306 break
1307 else:
1308 p2.moveToThreadNext()
1309 else:
1310 # Not found. Move to last top-level.
1311 p2 = c.lastTopLevel()
1312 parent.moveAfter(p2)
1313 # u.afterMoveMarkedNodes(moved, p1)
1314 if not g.unitTesting:
1315 g.blue(f"moved {len(moved)} nodes")
1316 c.setChanged()
1317 # Calling c.contractAllHeadlines() causes problems when in a chapter.
1318 c.redraw(parent)
1319#@+node:ekr.20111005081134.15543: *4* def createMoveMarkedNode
1320def createMoveMarkedNode(c):
1321 oldRoot = c.rootPosition()
1322 p = oldRoot.insertAfter()
1323 p.h = 'Moved marked nodes'
1324 p.moveToRoot()
1325 return p
1326#@+node:ekr.20031218072017.2923: *3* c_oc.markChangedHeadlines
1327@g.commander_command('mark-changed-items')
1328def markChangedHeadlines(self, event=None):
1329 """Mark all nodes that have been changed."""
1330 c, current, u = self, self.p, self.undoer
1331 undoType = 'Mark Changed'
1332 c.endEditing()
1333 u.beforeChangeGroup(current, undoType)
1334 for p in c.all_unique_positions():
1335 if p.isDirty() and not p.isMarked():
1336 bunch = u.beforeMark(p, undoType)
1337 # c.setMarked calls a hook.
1338 c.setMarked(p)
1339 p.setDirty()
1340 c.setChanged()
1341 u.afterMark(p, undoType, bunch)
1342 u.afterChangeGroup(current, undoType)
1343 if not g.unitTesting:
1344 g.blue('done')
1345 c.redraw_after_icons_changed()
1346#@+node:ekr.20031218072017.2924: *3* c_oc.markChangedRoots
1347def markChangedRoots(self, event=None):
1348 """Mark all changed @root nodes."""
1349 c, current, u = self, self.p, self.undoer
1350 undoType = 'Mark Changed'
1351 c.endEditing()
1352 u.beforeChangeGroup(current, undoType)
1353 for p in c.all_unique_positions():
1354 if p.isDirty() and not p.isMarked():
1355 s = p.b
1356 flag, i = g.is_special(s, "@root")
1357 if flag:
1358 bunch = u.beforeMark(p, undoType)
1359 c.setMarked(p) # Calls a hook.
1360 p.setDirty()
1361 c.setChanged()
1362 u.afterMark(p, undoType, bunch)
1363 u.afterChangeGroup(current, undoType)
1364 if not g.unitTesting:
1365 g.blue('done')
1366 c.redraw_after_icons_changed()
1367#@+node:ekr.20031218072017.2928: *3* c_oc.markHeadline
1368@g.commander_command('mark') # Compatibility
1369@g.commander_command('toggle-mark')
1370def markHeadline(self, event=None):
1371 """Toggle the mark of the selected node."""
1372 c, p, u = self, self.p, self.undoer
1373 if not p:
1374 return
1375 c.endEditing()
1376 undoType = 'Unmark' if p.isMarked() else 'Mark'
1377 bunch = u.beforeMark(p, undoType)
1378 # c.set/clearMarked call a hook.
1379 if p.isMarked():
1380 c.clearMarked(p)
1381 else:
1382 c.setMarked(p)
1383 p.setDirty()
1384 c.setChanged()
1385 u.afterMark(p, undoType, bunch)
1386 c.redraw_after_icons_changed()
1387#@+node:ekr.20031218072017.2929: *3* c_oc.markSubheads
1388@g.commander_command('mark-subheads')
1389def markSubheads(self, event=None):
1390 """Mark all children of the selected node as changed."""
1391 c, current, u = self, self.p, self.undoer
1392 undoType = 'Mark Subheads'
1393 if not current:
1394 return
1395 c.endEditing()
1396 u.beforeChangeGroup(current, undoType)
1397 for p in current.children():
1398 if not p.isMarked():
1399 bunch = u.beforeMark(p, undoType)
1400 c.setMarked(p) # Calls a hook.
1401 p.setDirty()
1402 c.setChanged()
1403 u.afterMark(p, undoType, bunch)
1404 u.afterChangeGroup(current, undoType)
1405 c.redraw_after_icons_changed()
1406#@+node:ekr.20031218072017.2930: *3* c_oc.unmarkAll
1407@g.commander_command('unmark-all')
1408def unmarkAll(self, event=None):
1409 """Unmark all nodes in the entire outline."""
1410 c, current, u = self, self.p, self.undoer
1411 undoType = 'Unmark All'
1412 if not current:
1413 return
1414 c.endEditing()
1415 u.beforeChangeGroup(current, undoType)
1416 changed = False
1417 p = None # To keep pylint happy.
1418 for p in c.all_unique_positions():
1419 if p.isMarked():
1420 bunch = u.beforeMark(p, undoType)
1421 # c.clearMarked(p) # Very slow: calls a hook.
1422 p.v.clearMarked()
1423 p.setDirty()
1424 u.afterMark(p, undoType, bunch)
1425 changed = True
1426 if changed:
1427 g.doHook("clear-all-marks", c=c, p=p)
1428 c.setChanged()
1429 u.afterChangeGroup(current, undoType)
1430 c.redraw_after_icons_changed()
1431#@+node:ekr.20031218072017.1766: ** c_oc.Move commands
1432#@+node:ekr.20031218072017.1767: *3* c_oc.demote
1433@g.commander_command('demote')
1434def demote(self, event=None):
1435 """Make all following siblings children of the selected node."""
1436 c, p, u = self, self.p, self.undoer
1437 if not p or not p.hasNext():
1438 c.treeFocusHelper()
1439 return
1440 # Make sure all the moves will be valid.
1441 next = p.next()
1442 while next:
1443 if not c.checkMoveWithParentWithWarning(next, p, True):
1444 c.treeFocusHelper()
1445 return
1446 next.moveToNext()
1447 c.endEditing()
1448 parent_v = p._parentVnode()
1449 n = p.childIndex()
1450 followingSibs = parent_v.children[n + 1 :]
1451 # Remove the moved nodes from the parent's children.
1452 parent_v.children = parent_v.children[: n + 1]
1453 # Add the moved nodes to p's children
1454 p.v.children.extend(followingSibs)
1455 # Adjust the parent links in the moved nodes.
1456 # There is no need to adjust descendant links.
1457 for child in followingSibs:
1458 child.parents.remove(parent_v)
1459 child.parents.append(p.v)
1460 p.expand()
1461 p.setDirty()
1462 c.setChanged()
1463 u.afterDemote(p, followingSibs)
1464 c.redraw(p)
1465 c.updateSyntaxColorer(p) # Moving can change syntax coloring.
1466#@+node:ekr.20031218072017.1768: *3* c_oc.moveOutlineDown
1467@g.commander_command('move-outline-down')
1468def moveOutlineDown(self, event=None):
1469 """Move the selected node down."""
1470 # Moving down is more tricky than moving up because we can't
1471 # move p to be a child of itself.
1472 #
1473 # An important optimization:
1474 # we don't have to call checkMoveWithParentWithWarning() if the parent of
1475 # the moved node remains the same.
1476 c, p, u = self, self.p, self.undoer
1477 if not p:
1478 return
1479 if not c.canMoveOutlineDown():
1480 if c.hoistStack:
1481 cantMoveMessage(c)
1482 c.treeFocusHelper()
1483 return
1484 parent = p.parent()
1485 next = p.visNext(c)
1486 while next and p.isAncestorOf(next):
1487 next = next.visNext(c)
1488 if not next:
1489 if c.hoistStack:
1490 cantMoveMessage(c)
1491 c.treeFocusHelper()
1492 return
1493 c.endEditing()
1494 undoData = u.beforeMoveNode(p)
1495 #@+<< Move p down & set moved if successful >>
1496 #@+node:ekr.20031218072017.1769: *4* << Move p down & set moved if successful >>
1497 if next.hasChildren() and next.isExpanded():
1498 # Attempt to move p to the first child of next.
1499 moved = c.checkMoveWithParentWithWarning(p, next, True)
1500 if moved:
1501 p.setDirty()
1502 p.moveToNthChildOf(next, 0)
1503 else:
1504 # Attempt to move p after next.
1505 moved = c.checkMoveWithParentWithWarning(p, next.parent(), True)
1506 if moved:
1507 p.setDirty()
1508 p.moveAfter(next)
1509 # Patch by nh2: 0004-Add-bool-collapse_nodes_after_move-option.patch
1510 if (
1511 c.collapse_nodes_after_move
1512 and moved and c.sparse_move
1513 and parent and not parent.isAncestorOf(p)
1514 ):
1515 # New in Leo 4.4.2: contract the old parent if it is no longer the parent of p.
1516 parent.contract()
1517 #@-<< Move p down & set moved if successful >>
1518 if moved:
1519 p.setDirty()
1520 c.setChanged()
1521 u.afterMoveNode(p, 'Move Down', undoData)
1522 c.redraw(p)
1523 c.updateSyntaxColorer(p) # Moving can change syntax coloring.
1524#@+node:ekr.20031218072017.1770: *3* c_oc.moveOutlineLeft
1525@g.commander_command('move-outline-left')
1526def moveOutlineLeft(self, event=None):
1527 """Move the selected node left if possible."""
1528 c, p, u = self, self.p, self.undoer
1529 if not p:
1530 return
1531 if not c.canMoveOutlineLeft():
1532 if c.hoistStack:
1533 cantMoveMessage(c)
1534 c.treeFocusHelper()
1535 return
1536 if not p.hasParent():
1537 c.treeFocusHelper()
1538 return
1539 parent = p.parent()
1540 c.endEditing()
1541 undoData = u.beforeMoveNode(p)
1542 p.setDirty()
1543 p.moveAfter(parent)
1544 p.setDirty()
1545 c.setChanged()
1546 u.afterMoveNode(p, 'Move Left', undoData)
1547 # Patch by nh2: 0004-Add-bool-collapse_nodes_after_move-option.patch
1548 if c.collapse_nodes_after_move and c.sparse_move: # New in Leo 4.4.2
1549 parent.contract()
1550 c.redraw(p)
1551 c.recolor() # Moving can change syntax coloring.
1552#@+node:ekr.20031218072017.1771: *3* c_oc.moveOutlineRight
1553@g.commander_command('move-outline-right')
1554def moveOutlineRight(self, event=None):
1555 """Move the selected node right if possible."""
1556 c, p, u = self, self.p, self.undoer
1557 if not p:
1558 return
1559 if not c.canMoveOutlineRight(): # 11/4/03: Support for hoist.
1560 if c.hoistStack:
1561 cantMoveMessage(c)
1562 c.treeFocusHelper()
1563 return
1564 back = p.back()
1565 if not back:
1566 c.treeFocusHelper()
1567 return
1568 if not c.checkMoveWithParentWithWarning(p, back, True):
1569 c.treeFocusHelper()
1570 return
1571 c.endEditing()
1572 undoData = u.beforeMoveNode(p)
1573 p.setDirty()
1574 n = back.numberOfChildren()
1575 p.moveToNthChildOf(back, n)
1576 p.setDirty()
1577 c.setChanged() # #2036.
1578 u.afterMoveNode(p, 'Move Right', undoData)
1579 c.redraw(p)
1580 c.recolor()
1581#@+node:ekr.20031218072017.1772: *3* c_oc.moveOutlineUp
1582@g.commander_command('move-outline-up')
1583def moveOutlineUp(self, event=None):
1584 """Move the selected node up if possible."""
1585 c, p, u = self, self.p, self.undoer
1586 if not p:
1587 return
1588 if not c.canMoveOutlineUp(): # Support for hoist.
1589 if c.hoistStack:
1590 cantMoveMessage(c)
1591 c.treeFocusHelper()
1592 return
1593 back = p.visBack(c)
1594 if not back:
1595 return
1596 back2 = back.visBack(c)
1597 c.endEditing()
1598 undoData = u.beforeMoveNode(p)
1599 moved = False
1600 #@+<< Move p up >>
1601 #@+node:ekr.20031218072017.1773: *4* << Move p up >>
1602 parent = p.parent()
1603 if not back2:
1604 if c.hoistStack: # hoist or chapter.
1605 limit, limitIsVisible = c.visLimit()
1606 assert limit
1607 if limitIsVisible:
1608 # canMoveOutlineUp should have caught this.
1609 g.trace('can not happen. In hoist')
1610 else:
1611 moved = True
1612 p.setDirty()
1613 p.moveToFirstChildOf(limit)
1614 else:
1615 # p will be the new root node
1616 p.setDirty()
1617 p.moveToRoot()
1618 moved = True
1619 elif back2.hasChildren() and back2.isExpanded():
1620 if c.checkMoveWithParentWithWarning(p, back2, True):
1621 moved = True
1622 p.setDirty()
1623 p.moveToNthChildOf(back2, 0)
1624 else:
1625 if c.checkMoveWithParentWithWarning(p, back2.parent(), True):
1626 moved = True
1627 p.setDirty()
1628 p.moveAfter(back2)
1629 # Patch by nh2: 0004-Add-bool-collapse_nodes_after_move-option.patch
1630 if (
1631 c.collapse_nodes_after_move
1632 and moved and c.sparse_move
1633 and parent and not parent.isAncestorOf(p)
1634 ):
1635 # New in Leo 4.4.2: contract the old parent if it is no longer the parent of p.
1636 parent.contract()
1637 #@-<< Move p up >>
1638 if moved:
1639 p.setDirty()
1640 c.setChanged()
1641 u.afterMoveNode(p, 'Move Up', undoData)
1642 c.redraw(p)
1643 c.updateSyntaxColorer(p) # Moving can change syntax coloring.
1644#@+node:ekr.20031218072017.1774: *3* c_oc.promote
1645@g.commander_command('promote')
1646def promote(self, event=None, undoFlag=True):
1647 """Make all children of the selected nodes siblings of the selected node."""
1648 c, p, u = self, self.p, self.undoer
1649 if not p or not p.hasChildren():
1650 c.treeFocusHelper()
1651 return
1652 c.endEditing()
1653 children = p.v.children # First, for undo.
1654 p.promote()
1655 c.setChanged()
1656 if undoFlag:
1657 p.setDirty()
1658 u.afterPromote(p, children)
1659 c.redraw(p)
1660 c.updateSyntaxColorer(p) # Moving can change syntax coloring.
1661#@+node:ekr.20071213185710: *3* c_oc.toggleSparseMove
1662@g.commander_command('toggle-sparse-move')
1663def toggleSparseMove(self, event=None):
1664 """Toggle whether moves collapse the outline."""
1665 c = self
1666 c.sparse_move = not c.sparse_move
1667 if not g.unitTesting:
1668 g.blue(f"sparse-move: {c.sparse_move}")
1669#@+node:ekr.20080425060424.1: ** c_oc.Sort commands
1670#@+node:ekr.20050415134809: *3* c_oc.sortChildren
1671@g.commander_command('sort-children')
1672def sortChildren(self, event=None, key=None, reverse=False):
1673 """Sort the children of a node."""
1674 # This method no longer supports the 'cmp' keyword arg.
1675 c, p = self, self.p
1676 if p and p.hasChildren():
1677 c.sortSiblings(p=p.firstChild(), sortChildren=True, key=key, reverse=reverse)
1678#@+node:ekr.20050415134809.1: *3* c_oc.sortSiblings
1679@g.commander_command('sort-siblings')
1680def sortSiblings(self, event=None,
1681 # cmp keyword is no longer supported.
1682 key=None,
1683 p=None,
1684 sortChildren=False,
1685 reverse=False
1686):
1687 """Sort the siblings of a node."""
1688 c, u = self, self.undoer
1689 if not p:
1690 p = c.p
1691 if not p:
1692 return
1693 c.endEditing()
1694 undoType = 'Sort Children' if sortChildren else 'Sort Siblings'
1695 parent_v = p._parentVnode()
1696 oldChildren = parent_v.children[:]
1697 newChildren = parent_v.children[:]
1698 if key is None:
1700 def lowerKey(self):
1701 return self.h.lower()
1703 key = lowerKey
1704 newChildren.sort(key=key, reverse=reverse)
1705 if oldChildren == newChildren:
1706 return
1707 # 2010/01/20. Fix bug 510148.
1708 c.setChanged()
1709 bunch = u.beforeSort(p, undoType, oldChildren, newChildren, sortChildren)
1710 parent_v.children = newChildren
1711 u.afterSort(p, bunch)
1712 # Sorting destroys position p, and possibly the root position.
1713 p = c.setPositionAfterSort(sortChildren)
1714 if p.parent():
1715 p.parent().setDirty()
1716 c.redraw(p)
1717#@+node:ekr.20070420092425: ** def cantMoveMessage
1718def cantMoveMessage(c):
1719 h = c.rootPosition().h
1720 kind = 'chapter' if h.startswith('@chapter') else 'hoist'
1721 g.warning("can't move node out of", kind)
1722#@+node:ekr.20180201040936.1: ** count-children
1723@g.command('count-children')
1724def count_children(event=None):
1725 c = event and event.get('c')
1726 if c:
1727 g.es_print(f"{c.p.numberOfChildren()} children")
1728#@-others
1729#@-leo