Coverage for C:\leo.repo\leo-editor\leo\core\leoNodes.py : 75%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# -*- coding: utf-8 -*-
2#@+leo-ver=5-thin
3#@+node:ekr.20031218072017.3320: * @file leoNodes.py
4#@@first
5"""Leo's fundamental data classes."""
6#@+<< imports >>
7#@+node:ekr.20060904165452.1: ** << imports >> (leoNodes.py)
8#Transcrypt does not support Python's copy module.
9import copy
10import itertools
11import time
12import re
13from typing import Any, Callable, Dict, Generator, List, Optional, Set, Tuple
14from typing import TYPE_CHECKING
15from leo.core import leoGlobals as g
16from leo.core import signal_manager
17if TYPE_CHECKING: # Always False at runtime.
18 from leo.core.leoCommands import Commands as Cmdr
19else:
20 Cmdr = None
21#@-<< imports >>
22#@+others
23#@+node:ekr.20031218072017.1991: ** class NodeIndices
24class NodeIndices:
25 """A class managing global node indices (gnx's)."""
27 __slots__ = ['defaultId', 'lastIndex', 'stack', 'timeString', 'userId']
29 #@+others
30 #@+node:ekr.20031218072017.1992: *3* ni.__init__
31 def __init__(self, id_: str) -> None:
32 """Ctor for NodeIndices class."""
33 self.defaultId = id_
34 self.lastIndex = 0
35 self.stack: List[Cmdr] = [] # A stack of open commanders.
36 self.timeString = '' # Set by setTimeStamp.
37 self.userId = id_
38 # Assign the initial timestamp.
39 self.setTimeStamp()
40 #@+node:ekr.20150321161305.8: *3* ni.check_gnx
41 def check_gnx(self, c: "Cmdr", gnx: str, v: "VNode") -> None:
42 """Check that no vnode exists with the given gnx in fc.gnxDict."""
43 fc = c.fileCommands
44 if gnx == 'hidden-root-vnode-gnx':
45 # No longer an error.
46 # fast.readWithElementTree always generates a nominal hidden vnode.
47 return
48 v2 = fc.gnxDict.get(gnx)
49 if v2 and v2 != v:
50 g.internalError(
51 f"getNewIndex: gnx clash {gnx}\n"
52 f" v: {v}\n"
53 f" v2: {v2}")
54 #@+node:ekr.20150302061758.14: *3* ni.compute_last_index
55 def compute_last_index(self, c: "Cmdr") -> None:
56 """Scan the entire leo outline to compute ni.last_index."""
57 ni = self
58 # Partial, experimental, fix for #658.
59 # Do not change self.lastIndex here!
60 # self.lastIndex = 0
61 for v in c.all_unique_nodes():
62 gnx = v.fileIndex
63 if gnx:
64 id_, t, n = self.scanGnx(gnx)
65 if t == ni.timeString and n is not None:
66 try:
67 n = int(n) # type:ignore
68 self.lastIndex = max(self.lastIndex, n) # type:ignore
69 except Exception:
70 g.es_exception()
71 self.lastIndex += 1
72 #@+node:ekr.20200528131303.1: *3* ni.computeNewIndex
73 def computeNewIndex(self) -> str:
74 """Return a new gnx."""
75 t_s = self.update()
76 # Updates self.lastTime and self.lastIndex.
77 gnx = g.toUnicode(f"{self.userId}.{t_s}.{self.lastIndex:d}")
78 return gnx
79 #@+node:ekr.20031218072017.1994: *3* ni.get/setDefaultId
80 # These are used by the FileCommands read/write code.
82 def getDefaultId(self) -> str:
83 """Return the id to be used by default in all gnx's"""
84 return self.defaultId
86 def setDefaultId(self, theId: str) -> None:
87 """Set the id to be used by default in all gnx's"""
88 self.defaultId = theId
89 #@+node:ekr.20031218072017.1995: *3* ni.getNewIndex
90 def getNewIndex(self, v: "VNode", cached: bool=False) -> str:
91 """
92 Create a new gnx for v or an empty string if the hold flag is set.
93 **Important**: the method must allocate a new gnx even if v.fileIndex exists.
94 """
95 if v is None:
96 g.internalError('getNewIndex: v is None')
97 return ''
98 c = v.context
99 fc = c.fileCommands
100 t_s = self.update()
101 # Updates self.lastTime and self.lastIndex.
102 gnx = g.toUnicode(f"{self.userId}.{t_s}.{self.lastIndex:d}")
103 v.fileIndex = gnx
104 self.check_gnx(c, gnx, v)
105 fc.gnxDict[gnx] = v
106 return gnx
107 #@+node:ekr.20150322134954.1: *3* ni.new_vnode_helper
108 def new_vnode_helper(self, c: "Cmdr", gnx: str, v: "VNode") -> None:
109 """Handle all gnx-related tasks for VNode.__init__."""
110 ni = self
111 # Special case for the c.hiddenRootNode. This eliminates a hack in c.initObjects.
112 if not getattr(c, 'fileCommands', None):
113 assert gnx == 'hidden-root-vnode-gnx'
114 v.fileIndex = gnx
115 return
116 if gnx:
117 v.fileIndex = gnx
118 ni.check_gnx(c, gnx, v)
119 c.fileCommands.gnxDict[gnx] = v
120 else:
121 v.fileIndex = ni.getNewIndex(v)
122 #@+node:ekr.20031218072017.1997: *3* ni.scanGnx
123 def scanGnx(self, s: str) -> Tuple[str, str, str]:
124 """Create a gnx from its string representation."""
125 if not isinstance(s, str):
126 g.error("scanGnx: unexpected index type:", type(s), '', s)
127 return None, None, None
128 s = s.strip()
129 theId, t, n = None, None, None
130 i, theId = g.skip_to_char(s, 0, '.')
131 if g.match(s, i, '.'):
132 i, t = g.skip_to_char(s, i + 1, '.')
133 if g.match(s, i, '.'):
134 i, n = g.skip_to_char(s, i + 1, '.')
135 # Use self.defaultId for missing id entries.
136 if not theId:
137 theId = self.defaultId
138 return theId, t, n
139 #@+node:ekr.20031218072017.1998: *3* ni.setTimeStamp
140 def setTimestamp(self) -> None:
141 """Set the timestamp string to be used by getNewIndex until further notice"""
142 self.timeString = time.strftime(
143 "%Y%m%d%H%M%S", # Help comparisons; avoid y2k problems.
144 time.localtime())
146 setTimeStamp = setTimestamp
147 #@+node:ekr.20141015035853.18304: *3* ni.tupleToString
148 def tupleToString(self, aTuple: Tuple) -> str:
149 """
150 Convert a gnx tuple returned by scanGnx
151 to its string representation.
152 """
153 theId, t, n = aTuple
154 # This logic must match the existing logic so that
155 # previously written gnx's can be found.
156 if n in (None, 0, '',):
157 s = f"{theId}.{t}"
158 else:
159 s = f"{theId}.{t}.{n}"
160 return g.toUnicode(s)
161 #@+node:ekr.20150321161305.13: *3* ni.update
162 def update(self) -> str:
163 """Update self.timeString and self.lastIndex"""
164 t_s = time.strftime("%Y%m%d%H%M%S", time.localtime())
165 if self.timeString == t_s:
166 self.lastIndex += 1
167 else:
168 self.lastIndex = 1
169 self.timeString = t_s
170 return t_s
171 #@+node:ekr.20141023110422.4: *3* ni.updateLastIndex
172 def updateLastIndex(self, gnx: str) -> None:
173 """Update ni.lastIndex if the gnx affects it."""
174 id_, t, n = self.scanGnx(gnx)
175 # pylint: disable=literal-comparison
176 # Don't you dare touch this code to keep pylint happy.
177 if not id_ or (n != 0 and not n):
178 return # the gnx is not well formed or n in ('',None)
179 if id_ == self.userId and t == self.timeString:
180 try:
181 n2 = int(n)
182 if n2 > self.lastIndex:
183 self.lastIndex = n2
184 g.trace(gnx, '-->', n2)
185 except Exception:
186 g.trace('can not happen', repr(n))
187 #@-others
188#@+node:ekr.20031218072017.889: ** class Position
189#@+<< about the position class >>
190#@+node:ekr.20031218072017.890: *3* << about the position class >>
191#@@language rest
192#@+at
193# A position marks the spot in a tree traversal. A position p consists of a VNode
194# p.v, a child index p._childIndex, and a stack of tuples (v,childIndex), one for
195# each ancestor **at the spot in tree traversal. Positions p has a unique set of
196# parents.
197#
198# The p.moveToX methods may return a null (invalid) position p with p.v = None.
199#
200# The tests "if p" or "if not p" are the _only_ correct way to test whether a
201# position p is valid. In particular, tests like "if p is None" or "if p is not
202# None" will not work properly.
203#@-<< about the position class >>
204# Positions should *never* be saved by the ZOBD.
207class Position:
209 __slots__ = [
210 '_childIndex', 'stack', 'v',
211 #
212 # EKR: The following fields are deprecated,
213 # as are the PosList class, c.find_h and c.find_b.
214 #
215 'matchiter', # for c.find_b and quicksearch.py.
216 'mo', # for c.find_h
217 ]
219 #@+others
220 #@+node:ekr.20040228094013: *3* p.ctor & other special methods...
221 #@+node:ekr.20080920052058.3: *4* p.__eq__ & __ne__
222 def __eq__(self, p2: Any) -> bool: # Use Any, not Position.
223 """Return True if two positions are equivalent."""
224 p1 = self
225 # Don't use g.trace: it might call p.__eq__ or p.__ne__.
226 if not isinstance(p2, Position):
227 return False
228 if p2 is None or p2.v is None:
229 return p1.v is None
230 return (p1.v == p2.v and
231 p1._childIndex == p2._childIndex and
232 p1.stack == p2.stack)
234 def __ne__(self, p2: Any) -> bool: # Use Any, not Position.
235 """Return True if two postions are not equivalent."""
236 return not self.__eq__(p2)
237 #@+node:ekr.20080416161551.190: *4* p.__init__
238 def __init__(self, v: "VNode", childIndex: int=0, stack: Optional[List]=None) -> None:
239 """Create a new position with the given childIndex and parent stack."""
240 self._childIndex = childIndex
241 self.v = v
242 # Stack entries are tuples (v, childIndex).
243 if stack:
244 self.stack = stack[:] # Creating a copy here is safest and best.
245 else:
246 self.stack = []
247 g.app.positions += 1
248 #@+node:ekr.20091210082012.6230: *4* p.__ge__ & __le__& __lt__
249 def __ge__(self, other: Any) -> bool:
250 return self.__eq__(other) or self.__gt__(other)
252 def __le__(self, other: Any) -> bool:
253 return self.__eq__(other) or self.__lt__(other)
255 def __lt__(self, other: Any) -> bool:
256 return not self.__eq__(other) and not self.__gt__(other)
257 #@+node:ekr.20091210082012.6233: *4* p.__gt__
258 def __gt__(self, other: Any) -> bool:
259 """Return True if self appears after other in outline order."""
260 stack1, stack2 = self.stack, other.stack
261 n1, n2 = len(stack1), len(stack2)
262 n = min(n1, n2)
263 # Compare the common part of the stacks.
264 for item1, item2 in zip(stack1, stack2):
265 v1, x1 = item1
266 v2, x2 = item2
267 if x1 > x2:
268 return True
269 if x1 < x2:
270 return False
271 # Finish the comparison.
272 if n1 == n2:
273 x1, x2 = self._childIndex, other._childIndex
274 return x1 > x2
275 if n1 < n2:
276 x1 = self._childIndex
277 v2, x2 = other.stack[n]
278 return x1 > x2
279 # n1 > n2
280 # 2011/07/28: Bug fix suggested by SegundoBob.
281 x1 = other._childIndex
282 v2, x2 = self.stack[n]
283 return x2 >= x1
284 #@+node:ekr.20040117173448: *4* p.__nonzero__ & __bool__
285 def __bool__(self) -> bool:
286 """
287 Return True if a position is valid.
289 The tests 'if p' or 'if not p' are the _only_ correct ways to test
290 whether a position p is valid.
292 Tests like 'if p is None' or 'if p is not None' will not work properly.
293 """
294 return self.v is not None
295 #@+node:ekr.20040301205720: *4* p.__str__ and p.__repr__
296 def __str__(self) -> str:
297 p = self
298 if p.v:
299 return (
300 "<"
301 f"pos {id(p)} "
302 f"childIndex: {p._childIndex} "
303 f"lvl: {p.level()} "
304 f"key: {p.key()} "
305 f"{p.h}"
306 ">"
307 )
308 return f"<pos {id(p)} [{len(p.stack)}] None>"
310 __repr__ = __str__
311 #@+node:ekr.20061006092649: *4* p.archivedPosition
312 def archivedPosition(self, root_p: Optional["Position"]=None) -> List[int]:
313 """Return a representation of a position suitable for use in .leo files."""
314 p = self
315 if root_p is None:
316 aList = [z._childIndex for z in p.self_and_parents()]
317 else:
318 aList = []
319 for z in p.self_and_parents(copy=False):
320 if z == root_p:
321 aList.append(0)
322 break
323 else:
324 aList.append(z._childIndex)
325 aList.reverse()
326 return aList
327 #@+node:ekr.20040310153624: *4* p.dump
328 def dumpLink(self, link: Optional[str]) -> str:
329 return link if link else "<none>"
331 def dump(self, label: str="") -> None:
332 p = self
333 if p.v:
334 p.v.dump() # Don't print a label
335 #@+node:ekr.20080416161551.191: *4* p.key & p.sort_key & __hash__
336 def key(self) -> str:
337 p = self
338 # For unified nodes we must include a complete key,
339 # so we can distinguish between clones.
340 result = []
341 for z in p.stack:
342 v, childIndex = z
343 result.append(f"{id(v)}:{childIndex}")
344 result.append(f"{id(p.v)}:{p._childIndex}")
345 return '.'.join(result)
347 def sort_key(self, p: "Position") -> List[int]:
348 return [int(s.split(':')[1]) for s in p.key().split('.')]
350 # Positions should *not* be hashable.
351 #
352 # From https://docs.python.org/3/reference/datamodel.html#object.__hash__
353 #
354 # If a class defines mutable objects and implements an __eq__() method, it
355 # should not implement __hash__(), since the implementation of hashable
356 # collections requires that a key’s hash value is immutable (if the object’s
357 # hash value changes, it will be in the wrong hash bucket).
359 # #1557: To keep mypy happy, don't define __hash__ at all.
360 # __hash__ = None
361 #@+node:ekr.20040315023430: *3* p.File Conversion
362 #@+at
363 # - convertTreeToString and moreHead can't be VNode methods because they uses level().
364 # - moreBody could be anywhere: it may as well be a postion method.
365 #@+node:ekr.20040315023430.1: *4* p.convertTreeToString
366 def convertTreeToString(self) -> str:
367 """Convert a positions suboutline to a string in MORE format."""
368 p = self
369 level1 = p.level()
370 array = []
371 for p in p.self_and_subtree(copy=False):
372 array.append(p.moreHead(level1) + '\n')
373 body = p.moreBody()
374 if body:
375 array.append(body + '\n')
376 return ''.join(array)
377 #@+node:ekr.20040315023430.2: *4* p.moreHead
378 def moreHead(self, firstLevel: int, useVerticalBar: bool=False) -> str:
379 """Return the headline string in MORE format."""
380 # useVerticalBar is unused, but it would be useful in over-ridden methods.
381 p = self
382 level = self.level() - firstLevel
383 plusMinus = "+" if p.hasChildren() else "-"
384 pad = '\t' * level
385 return f"{pad}{plusMinus} {p.h}"
386 #@+node:ekr.20040315023430.3: *4* p.moreBody
387 #@@language rest
388 #@+at
389 # + test line
390 # - test line
391 # \ test line
392 # test line +
393 # test line -
394 # test line \
395 # More lines...
396 #@@c
397 #@@language python
399 def moreBody(self) -> str:
400 """Returns the body string in MORE format.
402 Inserts a backslash before any leading plus, minus or backslash."""
403 p = self
404 array = []
405 lines = p.b.split('\n')
406 for s in lines:
407 i = g.skip_ws(s, 0)
408 if i < len(s) and s[i] in ('+', '-', '\\'):
409 s = s[:i] + '\\' + s[i:]
410 array.append(s)
411 return '\n'.join(array)
412 #@+node:ekr.20091001141621.6060: *3* p.generators
413 #@+node:ekr.20091001141621.6055: *4* p.children
414 def children(self, copy: bool=True) -> Generator:
415 """Yield all child positions of p."""
416 p = self
417 p = p.firstChild()
418 while p:
419 yield p.copy() if copy else p
420 p.moveToNext()
422 # Compatibility with old code...
424 children_iter = children
425 #@+node:ekr.20091002083910.6102: *4* p.following_siblings
426 def following_siblings(self, copy: bool=True) -> Generator:
427 """Yield all siblings positions that follow p, not including p."""
428 p = self
429 p = p.next() # pylint: disable=not-callable
430 while p:
431 yield p.copy() if copy else p
432 p.moveToNext()
434 # Compatibility with old code...
436 following_siblings_iter = following_siblings
437 #@+node:ekr.20161120105707.1: *4* p.nearest_roots
438 def nearest_roots(self, copy: bool=True, predicate: Optional[Callable]=None) -> Generator:
439 """
440 A generator yielding all the root positions "near" p1 = self that
441 satisfy the given predicate. p.isAnyAtFileNode is the default
442 predicate.
444 The search first proceeds up the p's tree. If a root is found, this
445 generator yields just that root.
447 Otherwise, the generator yields all nodes in p.subtree() that satisfy
448 the predicate. Once a root is found, the generator skips its subtree.
449 """
450 def default_predicate(p: "Position") -> bool:
451 return p.isAnyAtFileNode()
453 the_predicate = predicate or default_predicate
455 # First, look up the tree.
456 p1 = self
457 for p in p1.self_and_parents(copy=False):
458 if the_predicate(p):
459 yield p.copy() if copy else p
460 return
461 # Next, look for all .md files in the tree.
462 after = p1.nodeAfterTree()
463 p = p1
464 while p and p != after:
465 if the_predicate(p):
466 yield p.copy() if copy else p
467 p.moveToNodeAfterTree()
468 else:
469 p.moveToThreadNext()
470 #@+node:ekr.20161120163203.1: *4* p.nearest_unique_roots (aka p.nearest)
471 def nearest_unique_roots(self, copy: bool=True, predicate: Callable=None) -> Generator:
472 """
473 A generator yielding all unique root positions "near" p1 = self that
474 satisfy the given predicate. p.isAnyAtFileNode is the default
475 predicate.
477 The search first proceeds up the p's tree. If a root is found, this
478 generator yields just that root.
480 Otherwise, the generator yields all unique nodes in p.subtree() that
481 satisfy the predicate. Once a root is found, the generator skips its
482 subtree.
483 """
485 def default_predicate(p: "Position") -> bool:
486 return p.isAnyAtFileNode()
488 the_predicate = predicate or default_predicate
490 # First, look up the tree.
491 p1 = self
492 for p in p1.self_and_parents(copy=False):
493 if the_predicate(p):
494 yield p.copy() if copy else p
495 return
496 # Next, look for all unique .md files in the tree.
497 seen = set()
498 after = p1.nodeAfterTree()
499 p = p1
500 while p and p != after:
501 if the_predicate(p):
502 if p.v not in seen:
503 seen.add(p.v)
504 yield p.copy() if copy else p
505 p.moveToNodeAfterTree()
506 else:
507 p.moveToThreadNext()
509 nearest = nearest_unique_roots
510 #@+node:ekr.20091002083910.6104: *4* p.nodes
511 def nodes(self) -> Generator:
512 """Yield p.v and all vnodes in p's subtree."""
513 p = self
514 p = p.copy()
515 after = p.nodeAfterTree()
516 while p and p != after: # bug fix: 2013/10/12
517 yield p.v
518 p.moveToThreadNext()
520 # Compatibility with old code.
522 vnodes_iter = nodes
523 #@+node:ekr.20091001141621.6058: *4* p.parents
524 def parents(self, copy: bool=True) -> Generator:
525 """Yield all parent positions of p."""
526 p = self
527 p = p.parent()
528 while p:
529 yield p.copy() if copy else p
530 p.moveToParent()
532 # Compatibility with old code...
534 parents_iter = parents
535 #@+node:ekr.20091002083910.6099: *4* p.self_and_parents
536 def self_and_parents(self, copy: bool=True) -> Generator:
537 """Yield p and all parent positions of p."""
538 p = self
539 p = p.copy()
540 while p:
541 yield p.copy() if copy else p
542 p.moveToParent()
544 # Compatibility with old code...
546 self_and_parents_iter = self_and_parents
547 #@+node:ekr.20091001141621.6057: *4* p.self_and_siblings
548 def self_and_siblings(self, copy: bool=True) -> Generator:
549 """Yield all sibling positions of p including p."""
550 p = self
551 p = p.copy()
552 while p.hasBack():
553 p.moveToBack()
554 while p:
555 yield p.copy() if copy else p
556 p.moveToNext()
558 # Compatibility with old code...
560 self_and_siblings_iter = self_and_siblings
561 #@+node:ekr.20091001141621.6066: *4* p.self_and_subtree
562 def self_and_subtree(self, copy: bool=True) -> Generator:
563 """Yield p and all positions in p's subtree."""
564 p = self
565 p = p.copy()
566 after = p.nodeAfterTree()
567 while p and p != after:
568 yield p.copy() if copy else p
569 p.moveToThreadNext()
571 # Compatibility with old code...
573 self_and_subtree_iter = self_and_subtree
574 #@+node:ekr.20091001141621.6056: *4* p.subtree
575 def subtree(self, copy: bool=True) -> Generator:
576 """Yield all positions in p's subtree, but not p."""
577 p = self
578 p = p.copy()
579 after = p.nodeAfterTree()
580 p.moveToThreadNext()
581 while p and p != after:
582 yield p.copy() if copy else p
583 p.moveToThreadNext()
585 # Compatibility with old code...
587 subtree_iter = subtree
588 #@+node:ekr.20091002083910.6105: *4* p.unique_nodes
589 def unique_nodes(self) -> Generator:
590 """Yield p.v and all unique vnodes in p's subtree."""
591 p = self
592 seen = set()
593 for p in p.self_and_subtree(copy=False):
594 if p.v not in seen:
595 seen.add(p.v)
596 yield p.v
598 # Compatibility with old code.
600 unique_vnodes_iter = unique_nodes
601 #@+node:ekr.20091002083910.6103: *4* p.unique_subtree
602 def unique_subtree(self) -> Generator:
603 """Yield p and all other unique positions in p's subtree."""
604 p = self
605 seen = set()
606 for p in p.subtree():
607 if p.v not in seen:
608 seen.add(p.v)
609 # Fixed bug 1255208: p.unique_subtree returns vnodes, not positions.
610 yield p.copy() if copy else p
612 # Compatibility with old code...
614 subtree_with_unique_vnodes_iter = unique_subtree
615 #@+node:ekr.20040306212636: *3* p.Getters
616 #@+node:ekr.20040306210951: *4* p.VNode proxies
617 #@+node:ekr.20040306211032: *5* p.Comparisons
618 def anyAtFileNodeName(self) -> str:
619 return self.v.anyAtFileNodeName()
621 def atAutoNodeName(self) -> str:
622 return self.v.atAutoNodeName()
624 def atCleanNodeName(self) -> str:
625 return self.v.atCleanNodeName()
627 def atEditNodeName(self) -> str:
628 return self.v.atEditNodeName()
630 def atFileNodeName(self) -> str:
631 return self.v.atFileNodeName()
633 def atNoSentinelsFileNodeName(self) -> str:
634 return self.v.atNoSentinelsFileNodeName()
636 def atShadowFileNodeName(self) -> str:
637 return self.v.atShadowFileNodeName()
639 def atSilentFileNodeName(self) -> str:
640 return self.v.atSilentFileNodeName()
642 def atThinFileNodeName(self) -> str:
643 return self.v.atThinFileNodeName()
645 # New names, less confusing
646 atNoSentFileNodeName = atNoSentinelsFileNodeName
647 atAsisFileNodeName = atSilentFileNodeName
649 def isAnyAtFileNode(self) -> bool:
650 return self.v.isAnyAtFileNode()
652 def isAtAllNode(self) -> bool:
653 return self.v.isAtAllNode()
655 def isAtAutoNode(self) -> bool:
656 return self.v.isAtAutoNode()
658 def isAtAutoRstNode(self) -> bool:
659 return self.v.isAtAutoRstNode()
661 def isAtCleanNode(self) -> bool:
662 return self.v.isAtCleanNode()
664 def isAtEditNode(self) -> bool:
665 return self.v.isAtEditNode()
667 def isAtFileNode(self) -> bool:
668 return self.v.isAtFileNode()
670 def isAtIgnoreNode(self) -> bool:
671 return self.v.isAtIgnoreNode()
673 def isAtNoSentinelsFileNode(self) -> bool:
674 return self.v.isAtNoSentinelsFileNode()
676 def isAtOthersNode(self) -> bool:
677 return self.v.isAtOthersNode()
679 def isAtRstFileNode(self) -> bool:
680 return self.v.isAtRstFileNode()
682 def isAtSilentFileNode(self) -> bool:
683 return self.v.isAtSilentFileNode()
685 def isAtShadowFileNode(self) -> bool:
686 return self.v.isAtShadowFileNode()
688 def isAtThinFileNode(self) -> bool:
689 return self.v.isAtThinFileNode()
691 # New names, less confusing:
692 isAtNoSentFileNode = isAtNoSentinelsFileNode
693 isAtAsisFileNode = isAtSilentFileNode
695 # Utilities.
697 def matchHeadline(self, pattern: str) -> bool:
698 return self.v.matchHeadline(pattern)
699 #@+node:ekr.20040306220230: *5* p.Headline & body strings
700 def bodyString(self) -> str:
701 return self.v.bodyString()
703 def headString(self) -> str:
704 return self.v.headString()
705 #@+node:ekr.20040306214401: *5* p.Status bits
706 def isDirty(self) -> bool:
707 return self.v.isDirty()
709 def isMarked(self) -> bool:
710 return self.v.isMarked()
712 def isOrphan(self) -> bool:
713 return self.v.isOrphan()
715 def isSelected(self) -> bool:
716 return self.v.isSelected()
718 def isTopBitSet(self) -> bool:
719 return self.v.isTopBitSet()
721 def isVisited(self) -> bool:
722 return self.v.isVisited()
724 def status(self) -> int:
725 return self.v.status()
726 #@+node:ekr.20040306214240.2: *4* p.children & parents
727 #@+node:ekr.20040326064330: *5* p.childIndex
728 # This used to be time-critical code.
730 def childIndex(self) -> int:
731 p = self
732 return p._childIndex
733 #@+node:ekr.20040323160302: *5* p.directParents
734 def directParents(self) -> List["VNode"]:
735 return self.v.directParents()
736 #@+node:ekr.20040306214240.3: *5* p.hasChildren & p.numberOfChildren
737 def hasChildren(self) -> bool:
738 p = self
739 return len(p.v.children) > 0
741 hasFirstChild = hasChildren
743 def numberOfChildren(self) -> int:
744 p = self
745 return len(p.v.children)
746 #@+node:ekr.20031218072017.915: *4* p.getX & VNode compatibility traversal routines
747 # These methods are useful abbreviations.
748 # Warning: they make copies of positions, so they should be used _sparingly_
750 def getBack(self) -> "Position":
751 return self.copy().moveToBack()
753 def getFirstChild(self) -> "Position":
754 return self.copy().moveToFirstChild()
756 def getLastChild(self) -> "Position":
757 return self.copy().moveToLastChild()
759 def getLastNode(self) -> "Position":
760 return self.copy().moveToLastNode()
762 def getNext(self) -> "Position":
763 return self.copy().moveToNext()
765 def getNodeAfterTree(self) -> "Position":
766 return self.copy().moveToNodeAfterTree()
768 def getNthChild(self, n: int) -> "Position":
769 return self.copy().moveToNthChild(n)
771 def getParent(self) -> "Position":
772 return self.copy().moveToParent()
774 def getThreadBack(self) -> "Position":
775 return self.copy().moveToThreadBack()
777 def getThreadNext(self) -> "Position":
778 return self.copy().moveToThreadNext()
780 # New in Leo 4.4.3 b2: add c args.
782 def getVisBack(self, c: "Cmdr") -> "Position":
783 return self.copy().moveToVisBack(c)
785 def getVisNext(self, c: "Cmdr") -> "Position":
786 return self.copy().moveToVisNext(c)
787 # These are efficient enough now that iterators are the normal way to traverse the tree!
788 back = getBack
789 firstChild = getFirstChild
790 lastChild = getLastChild
791 lastNode = getLastNode
792 # lastVisible = getLastVisible # New in 4.2 (was in tk tree code).
793 next = getNext
794 nodeAfterTree = getNodeAfterTree
795 nthChild = getNthChild
796 parent = getParent
797 threadBack = getThreadBack
798 threadNext = getThreadNext
799 visBack = getVisBack
800 visNext = getVisNext
801 # New in Leo 4.4.3:
802 hasVisBack = visBack
803 hasVisNext = visNext
804 #@+node:tbrown.20111010104549.26758: *4* p.get_UNL
805 def get_UNL(self) -> str:
806 """
807 Return a UNL representing a clickable link.
809 New in Leo 6.6: Use a single, simplified format for UNL's:
811 - unl: //
812 - self.v.context.fileName() #
813 - a list of headlines separated by '-->'
815 New in Leo 6.6:
816 - Always add unl: // and file name.
817 - Never translate '-->' to '--%3E'.
818 - Never generate child indices.
819 """
820 return (
821 'unl://'
822 + self.v.context.fileName() + '#'
823 + '-->'.join(list(reversed([z.h for z in self.self_and_parents(copy=False)])))
824 )
825 #@+node:ekr.20080416161551.192: *4* p.hasBack/Next/Parent/ThreadBack
826 def hasBack(self) -> bool:
827 p = self
828 return bool(p.v and p._childIndex > 0)
830 def hasNext(self) -> bool:
831 p = self
832 try:
833 parent_v = p._parentVnode()
834 # Returns None if p.v is None.
835 return p.v and parent_v and p._childIndex + 1 < len(parent_v.children) # type:ignore
836 except Exception:
837 g.trace('*** Unexpected exception')
838 g.es_exception()
839 return None
841 def hasParent(self) -> bool:
842 p = self
843 return bool(p.v and p.stack)
845 def hasThreadBack(self) -> bool:
846 p = self
847 return bool(p.hasParent() or p.hasBack())
848 # Much cheaper than computing the actual value.
849 #@+node:ekr.20080416161551.193: *5* hasThreadNext (the only complex hasX method)
850 def hasThreadNext(self) -> bool:
851 p = self
852 if not p.v:
853 return False
854 if p.hasChildren() or p.hasNext():
855 return True
856 n = len(p.stack) - 1
857 while n >= 0:
858 v, childIndex = p.stack[n]
859 # See how many children v's parent has.
860 if n == 0:
861 parent_v = v.context.hiddenRootNode
862 else:
863 parent_v, junk = p.stack[n - 1]
864 if len(parent_v.children) > childIndex + 1:
865 # v has a next sibling.
866 return True
867 n -= 1
868 return False
869 #@+node:ekr.20060920203352: *4* p.findRootPosition
870 def findRootPosition(self) -> "Position":
871 # 2011/02/25: always use c.rootPosition
872 p = self
873 c = p.v.context
874 return c.rootPosition()
875 #@+node:ekr.20080416161551.194: *4* p.isAncestorOf
876 def isAncestorOf(self, p2: "Position") -> bool:
877 """Return True if p is one of the direct ancestors of p2."""
878 p = self
879 c = p.v.context
880 if not c.positionExists(p2):
881 return False
882 for z in p2.stack:
883 # 2013/12/25: bug fix: test childIndices.
884 # This is required for the new per-position expansion scheme.
885 parent_v, parent_childIndex = z
886 if parent_v == p.v and parent_childIndex == p._childIndex:
887 return True
888 return False
889 #@+node:ekr.20040306215056: *4* p.isCloned
890 def isCloned(self) -> bool:
891 p = self
892 return p.v.isCloned()
893 #@+node:ekr.20040307104131.2: *4* p.isRoot
894 def isRoot(self) -> bool:
895 p = self
896 return not p.hasParent() and not p.hasBack()
897 #@+node:ekr.20080416161551.196: *4* p.isVisible
898 def isVisible(self, c: "Cmdr") -> bool:
899 """Return True if p is visible in c's outline."""
900 p = self
902 def visible(p: "Position", root: Optional["Position"]=None) -> bool:
903 for parent in p.parents(copy=False):
904 if parent and parent == root:
905 # #12.
906 return True
907 if not c.shouldBeExpanded(parent):
908 return False
909 return True
911 if c.hoistStack: # Chapters are a form of hoist.
912 root = c.hoistStack[-1].p
913 if p == root:
914 # #12.
915 return True
916 return root.isAncestorOf(p) and visible(p, root=root)
917 for root in c.rootPosition().self_and_siblings(copy=False):
918 if root == p or root.isAncestorOf(p):
919 return visible(p)
920 return False
921 #@+node:ekr.20080416161551.197: *4* p.level & simpleLevel
922 def level(self) -> int:
923 """Return the number of p's parents."""
924 p = self
925 return len(p.stack) if p.v else 0
927 simpleLevel = level
928 #@+node:ekr.20111005152227.15566: *4* p.positionAfterDeletedTree
929 def positionAfterDeletedTree(self) -> "Position":
930 """Return the position corresponding to p.nodeAfterTree() after this node is
931 deleted. This will be p.nodeAfterTree() unless p.next() exists.
933 This method allows scripts to traverse an outline, deleting nodes during the
934 traversal. The pattern is::
936 p = c.rootPosition()
937 while p:
938 if <delete p?>:
939 next = p.positionAfterDeletedTree()
940 p.doDelete()
941 p = next
942 else:
943 p.moveToThreadNext()
945 This method also allows scripts to *move* nodes during a traversal, **provided**
946 that nodes are moved to a "safe" spot so that moving a node does not change the
947 position of any other nodes.
949 For example, the move-marked-nodes command first creates a **move node**, called
950 'Clones of marked nodes'. All moved nodes become children of this move node.
951 **Inserting** these nodes as children of the "move node" does not change the
952 positions of other nodes. **Deleting** these nodes *may* change the position of
953 nodes, but the pattern above handles this complication cleanly.
954 """
955 p = self
956 next = p.next() # pylint: disable=not-callable
957 if next:
958 # The new position will be the same as p, except for p.v.
959 p = p.copy()
960 p.v = next.v
961 return p
962 return p.nodeAfterTree()
963 #@+node:shadow.20080825171547.2: *4* p.textOffset
964 def textOffset(self) -> Optional[int]:
965 """
966 Return the fcol offset of self.
967 Return None if p is has no ancestor @<file> node.
968 http://tinyurl.com/5nescw
969 """
970 p = self
971 found, offset = False, 0
972 for p in p.self_and_parents(copy=False):
973 if p.isAnyAtFileNode():
974 # Ignore parent of @<file> node.
975 found = True
976 break
977 parent = p.parent()
978 if not parent:
979 break
980 # If p is a section definition, search the parent for the reference.
981 # Otherwise, search the parent for @others.
982 h = p.h.strip()
983 i = h.find('<<')
984 j = h.find('>>')
985 target = h[i : j + 2] if -1 < i < j else '@others'
986 for s in parent.b.split('\n'):
987 if s.find(target) > -1:
988 offset += g.skip_ws(s, 0)
989 break
990 return offset if found else None
991 #@+node:ekr.20080423062035.1: *3* p.Low level methods
992 # These methods are only for the use of low-level code
993 # in leoNodes.py, leoFileCommands.py and leoUndo.py.
994 #@+node:ekr.20080427062528.4: *4* p._adjustPositionBeforeUnlink
995 def _adjustPositionBeforeUnlink(self, p2: "Position") -> None:
996 """Adjust position p before unlinking p2."""
997 # p will change if p2 is a previous sibling of p or
998 # p2 is a previous sibling of any ancestor of p.
999 p = self
1000 sib = p.copy()
1001 # A special case for previous siblings.
1002 # Adjust p._childIndex, not the stack's childIndex.
1003 while sib.hasBack():
1004 sib.moveToBack()
1005 if sib == p2:
1006 p._childIndex -= 1
1007 return
1008 # Adjust p's stack.
1009 stack: List[Tuple[VNode, int]] = []
1010 changed, i = False, 0
1011 while i < len(p.stack):
1012 v, childIndex = p.stack[i]
1013 p3 = Position(v=v, childIndex=childIndex, stack=stack[:i])
1014 while p3:
1015 if p2 == p3:
1016 # 2011/02/25: compare full positions, not just vnodes.
1017 # A match with the to-be-moved node.
1018 stack.append((v, childIndex - 1),)
1019 changed = True
1020 break # terminate only the inner loop.
1021 p3.moveToBack()
1022 else:
1023 stack.append((v, childIndex),)
1024 i += 1
1025 if changed:
1026 p.stack = stack
1027 #@+node:ekr.20080416161551.214: *4* p._linkAfter
1028 def _linkAfter(self, p_after: "Position") -> None:
1029 """Link self after p_after."""
1030 p = self
1031 parent_v = p_after._parentVnode()
1032 p.stack = p_after.stack[:]
1033 p._childIndex = p_after._childIndex + 1
1034 child = p.v
1035 n = p_after._childIndex + 1
1036 child._addLink(n, parent_v)
1037 #@+node:ekr.20180709181718.1: *4* p._linkCopiedAfter
1038 def _linkCopiedAfter(self, p_after: "Position") -> None:
1039 """Link self, a newly copied tree, after p_after."""
1040 p = self
1041 parent_v = p_after._parentVnode()
1042 p.stack = p_after.stack[:]
1043 p._childIndex = p_after._childIndex + 1
1044 child = p.v
1045 n = p_after._childIndex + 1
1046 child._addCopiedLink(n, parent_v)
1047 #@+node:ekr.20080416161551.215: *4* p._linkAsNthChild
1048 def _linkAsNthChild(self, parent: "Position", n: int) -> None:
1049 """Link self as the n'th child of the parent."""
1050 p = self
1051 parent_v = parent.v
1052 p.stack = parent.stack[:]
1053 p.stack.append((parent_v, parent._childIndex),)
1054 p._childIndex = n
1055 child = p.v
1056 child._addLink(n, parent_v)
1057 #@+node:ekr.20180709180140.1: *4* p._linkCopiedAsNthChild
1058 def _linkCopiedAsNthChild(self, parent: "Position", n: int) -> None:
1059 """Link a copied self as the n'th child of the parent."""
1060 p = self
1061 parent_v = parent.v
1062 p.stack = parent.stack[:]
1063 p.stack.append((parent_v, parent._childIndex),)
1064 p._childIndex = n
1065 child = p.v
1066 child._addCopiedLink(n, parent_v)
1067 #@+node:ekr.20080416161551.216: *4* p._linkAsRoot
1068 def _linkAsRoot(self) -> "Position":
1069 """Link self as the root node."""
1070 p = self
1071 assert p.v
1072 parent_v = p.v.context.hiddenRootNode
1073 assert parent_v, g.callers()
1074 #
1075 # Make p the root position.
1076 p.stack = []
1077 p._childIndex = 0
1078 #
1079 # Make p.v the first child of parent_v.
1080 p.v._addLink(0, parent_v)
1081 return p
1082 #@+node:ekr.20080416161551.212: *4* p._parentVnode
1083 def _parentVnode(self) -> "VNode":
1084 """
1085 Return the parent VNode.
1086 Return the hiddenRootNode if there is no other parent.
1087 """
1088 p = self
1089 if p.v:
1090 data = p.stack and p.stack[-1]
1091 if data:
1092 v, junk = data
1093 return v
1094 return p.v.context.hiddenRootNode
1095 return None
1096 #@+node:ekr.20131219220412.16582: *4* p._relinkAsCloneOf
1097 def _relinkAsCloneOf(self, p2: "Position") -> None:
1098 """A low-level method to replace p.v by a p2.v."""
1099 p = self
1100 v, v2 = p.v, p2.v
1101 parent_v = p._parentVnode()
1102 if not parent_v:
1103 g.internalError('no parent_v', p)
1104 return
1105 if parent_v.children[p._childIndex] == v:
1106 parent_v.children[p._childIndex] = v2
1107 v2.parents.append(parent_v)
1108 # p.v no longer truly exists.
1109 # p.v = p2.v
1110 else:
1111 g.internalError(
1112 'parent_v.children[childIndex] != v',
1113 p, parent_v.children, p._childIndex, v)
1114 #@+node:ekr.20080416161551.217: *4* p._unlink
1115 def _unlink(self) -> None:
1116 """Unlink the receiver p from the tree."""
1117 p = self
1118 n = p._childIndex
1119 parent_v = p._parentVnode()
1120 # returns None if p.v is None
1121 child = p.v
1122 assert p.v
1123 assert parent_v
1124 # Delete the child.
1125 if (0 <= n < len(parent_v.children) and
1126 parent_v.children[n] == child
1127 ):
1128 # This is the only call to v._cutlink.
1129 child._cutLink(n, parent_v)
1130 else:
1131 self.badUnlink(parent_v, n, child)
1132 #@+node:ekr.20090706171333.6226: *5* p.badUnlink
1133 def badUnlink(self, parent_v: "VNode", n: int, child: "VNode") -> None:
1135 if 0 <= n < len(parent_v.children):
1136 g.trace(f"**can not happen: children[{n}] != p.v")
1137 g.trace('parent_v.children...\n',
1138 g.listToString(parent_v.children))
1139 g.trace('parent_v', parent_v)
1140 g.trace('parent_v.children[n]', parent_v.children[n])
1141 g.trace('child', child)
1142 g.trace('** callers:', g.callers())
1143 if g.unitTesting:
1144 assert False, 'children[%s] != p.v'
1145 else:
1146 g.trace(
1147 f"**can not happen: bad child index: {n}, "
1148 f"len(children): {len(parent_v.children)}")
1149 g.trace('parent_v.children...\n',
1150 g.listToString(parent_v.children))
1151 g.trace('parent_v', parent_v, 'child', child)
1152 g.trace('** callers:', g.callers())
1153 if g.unitTesting:
1154 assert False, f"bad child index: {n}"
1155 #@+node:ekr.20080416161551.199: *3* p.moveToX
1156 #@+at These routines change self to a new position "in place".
1157 # That is, these methods must _never_ call p.copy().
1158 #
1159 # When moving to a nonexistent position, these routines simply set p.v = None,
1160 # leaving the p.stack unchanged. This allows the caller to "undo" the effect of
1161 # the invalid move by simply restoring the previous value of p.v.
1162 #
1163 # These routines all return self on exit so the following kind of code will work:
1164 # after = p.copy().moveToNodeAfterTree()
1165 #@+node:ekr.20080416161551.200: *4* p.moveToBack
1166 def moveToBack(self) -> "Position":
1167 """Move self to its previous sibling."""
1168 p = self
1169 n = p._childIndex
1170 parent_v = p._parentVnode()
1171 # Returns None if p.v is None.
1172 # Do not assume n is in range: this is used by positionExists.
1173 if parent_v and p.v and 0 < n <= len(parent_v.children):
1174 p._childIndex -= 1
1175 p.v = parent_v.children[n - 1]
1176 else:
1177 p.v = None
1178 return p
1179 #@+node:ekr.20080416161551.201: *4* p.moveToFirstChild
1180 def moveToFirstChild(self) -> "Position":
1181 """Move a position to it's first child's position."""
1182 p = self
1183 if p.v and p.v.children:
1184 p.stack.append((p.v, p._childIndex),)
1185 p.v = p.v.children[0]
1186 p._childIndex = 0
1187 else:
1188 p.v = None
1189 return p
1190 #@+node:ekr.20080416161551.202: *4* p.moveToLastChild
1191 def moveToLastChild(self) -> "Position":
1192 """Move a position to it's last child's position."""
1193 p = self
1194 if p.v and p.v.children:
1195 p.stack.append((p.v, p._childIndex),)
1196 n = len(p.v.children)
1197 p.v = p.v.children[n - 1]
1198 p._childIndex = n - 1
1199 else:
1200 p.v = None
1201 return p
1202 #@+node:ekr.20080416161551.203: *4* p.moveToLastNode
1203 def moveToLastNode(self) -> "Position":
1204 """Move a position to last node of its tree.
1206 N.B. Returns p if p has no children."""
1207 p = self
1208 # Huge improvement for 4.2.
1209 while p.hasChildren():
1210 p.moveToLastChild()
1211 return p
1212 #@+node:ekr.20080416161551.204: *4* p.moveToNext
1213 def moveToNext(self) -> "Position":
1214 """Move a position to its next sibling."""
1215 p = self
1216 n = p._childIndex
1217 parent_v = p._parentVnode()
1218 # Returns None if p.v is None.
1219 if p and not p.v:
1220 g.trace('no p.v:', p, g.callers())
1221 if p.v and parent_v and len(parent_v.children) > n + 1:
1222 p._childIndex = n + 1
1223 p.v = parent_v.children[n + 1]
1224 else:
1225 p.v = None
1226 return p
1227 #@+node:ekr.20080416161551.205: *4* p.moveToNodeAfterTree
1228 def moveToNodeAfterTree(self) -> "Position":
1229 """Move a position to the node after the position's tree."""
1230 p = self
1231 while p:
1232 if p.hasNext():
1233 p.moveToNext()
1234 break
1235 p.moveToParent()
1236 return p
1237 #@+node:ekr.20080416161551.206: *4* p.moveToNthChild
1238 def moveToNthChild(self, n: int) -> "Position":
1239 p = self
1240 if p.v and len(p.v.children) > n:
1241 p.stack.append((p.v, p._childIndex),)
1242 p.v = p.v.children[n]
1243 p._childIndex = n
1244 else:
1245 # mypy rightly doesn't like setting p.v to None.
1246 # Leo's code must use the test `if p:` as appropriate.
1247 p.v = None # type:ignore
1248 return p
1249 #@+node:ekr.20080416161551.207: *4* p.moveToParent
1250 def moveToParent(self) -> "Position":
1251 """Move a position to its parent position."""
1252 p = self
1253 if p.v and p.stack:
1254 p.v, p._childIndex = p.stack.pop()
1255 else:
1256 # mypy rightly doesn't like setting p.v to None.
1257 # Leo's code must use the test `if p:` as appropriate.
1258 p.v = None # type:ignore
1259 return p
1260 #@+node:ekr.20080416161551.208: *4* p.moveToThreadBack
1261 def moveToThreadBack(self) -> "Position":
1262 """Move a position to it's threadBack position."""
1263 p = self
1264 if p.hasBack():
1265 p.moveToBack()
1266 p.moveToLastNode()
1267 else:
1268 p.moveToParent()
1269 return p
1270 #@+node:ekr.20080416161551.209: *4* p.moveToThreadNext
1271 def moveToThreadNext(self) -> "Position":
1272 """Move a position to threadNext position."""
1273 p = self
1274 if p.v:
1275 if p.v.children:
1276 p.moveToFirstChild()
1277 elif p.hasNext():
1278 p.moveToNext()
1279 else:
1280 p.moveToParent()
1281 while p:
1282 if p.hasNext():
1283 p.moveToNext()
1284 break #found
1285 p.moveToParent()
1286 # not found.
1287 return p
1288 #@+node:ekr.20080416161551.210: *4* p.moveToVisBack & helper
1289 def moveToVisBack(self, c: "Cmdr") -> "Position":
1290 """Move a position to the position of the previous visible node."""
1291 p = self
1292 limit, limitIsVisible = c.visLimit()
1293 while p:
1294 # Short-circuit if possible.
1295 back = p.back()
1296 if back and back.hasChildren() and back.isExpanded():
1297 p.moveToThreadBack()
1298 elif back:
1299 p.moveToBack()
1300 else:
1301 p.moveToParent() # Same as p.moveToThreadBack()
1302 if p:
1303 if limit:
1304 done, val = self.checkVisBackLimit(limit, limitIsVisible, p)
1305 if done:
1306 return val # A position or None
1307 if p.isVisible(c):
1308 return p
1309 return p
1310 #@+node:ekr.20090715145956.6166: *5* checkVisBackLimit
1311 def checkVisBackLimit(self,
1312 limit: "Position",
1313 limitIsVisible: bool,
1314 p: "Position",
1315 ) -> Tuple[bool, Optional["Position"]]:
1316 """Return done, p or None"""
1317 c = p.v.context
1318 if limit == p:
1319 if limitIsVisible and p.isVisible(c):
1320 return True, p
1321 return True, None
1322 if limit.isAncestorOf(p):
1323 return False, None
1324 return True, None
1325 #@+node:ekr.20080416161551.211: *4* p.moveToVisNext & helper
1326 def moveToVisNext(self, c: "Cmdr") -> "Position":
1327 """Move a position to the position of the next visible node."""
1328 p = self
1329 limit, limitIsVisible = c.visLimit()
1330 while p:
1331 if p.hasChildren():
1332 if p.isExpanded():
1333 p.moveToFirstChild()
1334 else:
1335 p.moveToNodeAfterTree()
1336 elif p.hasNext():
1337 p.moveToNext()
1338 else:
1339 p.moveToThreadNext()
1340 if p:
1341 if limit and self.checkVisNextLimit(limit, p):
1342 return None
1343 if p.isVisible(c):
1344 return p
1345 return p
1346 #@+node:ekr.20090715145956.6167: *5* checkVisNextLimit
1347 def checkVisNextLimit(self, limit: "Position", p: "Position") -> bool:
1348 """Return True is p is outside limit of visible nodes."""
1349 return limit != p and not limit.isAncestorOf(p)
1350 #@+node:ekr.20150316175921.6: *4* p.safeMoveToThreadNext
1351 def safeMoveToThreadNext(self) -> "Position":
1352 """
1353 Move a position to threadNext position.
1354 Issue an error if any vnode is an ancestor of itself.
1355 """
1356 p = self
1357 if p.v:
1358 child_v = p.v.children and p.v.children[0]
1359 if child_v:
1360 for parent in p.self_and_parents(copy=False):
1361 if child_v == parent.v:
1362 g.app.structure_errors += 1
1363 g.error(f"vnode: {child_v} is its own parent")
1364 # Allocating a new vnode would be difficult.
1365 # Just remove child_v from parent.v.children.
1366 parent.v.children = [
1367 v2 for v2 in parent.v.children if not v2 == child_v]
1368 if parent.v in child_v.parents:
1369 child_v.parents.remove(parent.v)
1370 # Try not to hang.
1371 p.moveToParent()
1372 break
1373 elif child_v.fileIndex == parent.v.fileIndex:
1374 g.app.structure_errors += 1
1375 g.error(
1376 f"duplicate gnx: {child_v.fileIndex!r} "
1377 f"v: {child_v} parent: {parent.v}")
1378 child_v.fileIndex = g.app.nodeIndices.getNewIndex(v=child_v)
1379 assert child_v.gnx != parent.v.gnx
1380 # Should be ok to continue.
1381 p.moveToFirstChild()
1382 break
1383 else:
1384 p.moveToFirstChild()
1385 elif p.hasNext():
1386 p.moveToNext()
1387 else:
1388 p.moveToParent()
1389 while p:
1390 if p.hasNext():
1391 p.moveToNext()
1392 break # found
1393 p.moveToParent()
1394 # not found.
1395 return p
1396 #@+node:ekr.20150316175921.7: *5* p.checkChild
1397 #@+node:ekr.20040303175026: *3* p.Moving, Inserting, Deleting, Cloning, Sorting
1398 #@+node:ekr.20040303175026.8: *4* p.clone
1399 def clone(self) -> "Position":
1400 """Create a clone of back.
1402 Returns the newly created position."""
1403 p = self
1404 p2 = p.copy() # Do *not* copy the VNode!
1405 p2._linkAfter(p) # This should "just work"
1406 return p2
1407 #@+node:ekr.20040117171654: *4* p.copy
1408 def copy(self) -> "Position":
1409 """"Return an independent copy of a position."""
1410 return Position(self.v, self._childIndex, self.stack)
1411 #@+node:ekr.20040303175026.9: *4* p.copyTreeAfter, copyTreeTo
1412 # These used by unit tests, by the group_operations plugin,
1413 # and by the files-compare-leo-files command.
1415 # To do: use v.copyTree instead.
1417 def copyTreeAfter(self, copyGnxs: bool=False) -> "Position":
1418 """Copy p and insert it after itself."""
1419 p = self
1420 p2 = p.insertAfter()
1421 p.copyTreeFromSelfTo(p2, copyGnxs=copyGnxs)
1422 return p2
1425 def copyTreeFromSelfTo(self, p2: "Position", copyGnxs: bool=False) -> None:
1426 p = self
1427 p2.v._headString = g.toUnicode(p.h, reportErrors=True) # 2017/01/24
1428 p2.v._bodyString = g.toUnicode(p.b, reportErrors=True) # 2017/01/24
1429 #
1430 # #1019794: p.copyTreeFromSelfTo, should deepcopy p.v.u.
1431 p2.v.u = copy.deepcopy(p.v.u)
1432 if copyGnxs:
1433 p2.v.fileIndex = p.v.fileIndex
1434 # 2009/10/02: no need to copy arg to iter
1435 for child in p.children():
1436 child2 = p2.insertAsLastChild()
1437 child.copyTreeFromSelfTo(child2, copyGnxs=copyGnxs)
1438 #@+node:ekr.20160502095354.1: *4* p.copyWithNewVnodes
1439 def copyWithNewVnodes(self, copyMarked: bool=False) -> "Position":
1440 """
1441 Return an **unlinked** copy of p with a new vnode v.
1442 The new vnode is complete copy of v and all its descendants.
1443 """
1444 p = self
1445 return Position(v=p.v.copyTree(copyMarked))
1446 #@+node:peckj.20131023115434.10115: *4* p.createNodeHierarchy
1447 def createNodeHierarchy(self, heads: List, forcecreate: bool=False) -> "Position":
1448 """ Create the proper hierarchy of nodes with headlines defined in
1449 'heads' as children of the current position
1451 params:
1452 heads - list of headlines in order to create, i.e. ['foo','bar','baz']
1453 will create:
1454 self
1455 -foo
1456 --bar
1457 ---baz
1458 forcecreate - If False (default), will not create nodes unless they don't exist
1459 If True, will create nodes regardless of existing nodes
1460 returns the final position ('baz' in the above example)
1461 """
1462 c = self.v.context
1463 return c.createNodeHierarchy(heads, parent=self, forcecreate=forcecreate)
1464 #@+node:ekr.20131230090121.16552: *4* p.deleteAllChildren
1465 def deleteAllChildren(self) -> None:
1466 """
1467 Delete all children of the receiver and set p.dirty().
1468 """
1469 p = self
1470 p.setDirty() # Mark @file nodes dirty!
1471 while p.hasChildren():
1472 p.firstChild().doDelete()
1473 #@+node:ekr.20040303175026.2: *4* p.doDelete
1474 def doDelete(self, newNode: Optional["Position"]=None) -> None:
1475 """
1476 Deletes position p from the outline.
1478 This is the main delete routine.
1479 It deletes the receiver's entire tree from the screen.
1480 Because of the undo command we never actually delete vnodes.
1481 """
1482 p = self
1483 p.setDirty() # Mark @file nodes dirty!
1484 sib = p.copy()
1485 while sib.hasNext():
1486 sib.moveToNext()
1487 if sib == newNode:
1488 # Adjust newNode._childIndex if newNode is a following sibling of p.
1489 newNode._childIndex -= 1
1490 break
1491 p._unlink()
1492 #@+node:ekr.20040303175026.3: *4* p.insertAfter
1493 def insertAfter(self) -> "Position":
1494 """
1495 Inserts a new position after self.
1497 Returns the newly created position.
1498 """
1499 p = self
1500 context = p.v.context
1501 p2 = self.copy()
1502 p2.v = VNode(context=context)
1503 p2.v.iconVal = 0
1504 p2._linkAfter(p)
1505 return p2
1506 #@+node:ekr.20040303175026.4: *4* p.insertAsLastChild
1507 def insertAsLastChild(self) -> "Position":
1508 """
1509 Insert a new VNode as the last child of self.
1511 Return the newly created position.
1512 """
1513 p = self
1514 n = p.numberOfChildren()
1515 return p.insertAsNthChild(n)
1516 #@+node:ekr.20040303175026.5: *4* p.insertAsNthChild
1517 def insertAsNthChild(self, n: int) -> "Position":
1518 """
1519 Inserts a new node as the the nth child of self.
1520 self must have at least n-1 children.
1522 Returns the newly created position.
1523 """
1524 p = self
1525 context = p.v.context
1526 p2 = self.copy()
1527 p2.v = VNode(context=context)
1528 p2.v.iconVal = 0
1529 p2._linkAsNthChild(p, n)
1530 return p2
1531 #@+node:ekr.20130923111858.11572: *4* p.insertBefore
1532 def insertBefore(self) -> "Position":
1533 """
1534 Insert a new position before self.
1536 Return the newly created position.
1537 """
1538 p = self
1539 parent = p.parent()
1540 if p.hasBack():
1541 back = p.getBack()
1542 p = back.insertAfter()
1543 elif parent:
1544 p = parent.insertAsNthChild(0)
1545 else:
1546 p = p.insertAfter()
1547 p.moveToRoot()
1548 return p
1549 #@+node:ekr.20040310062332.1: *4* p.invalidOutline
1550 def invalidOutline(self, message: str) -> None:
1551 p = self
1552 if p.hasParent():
1553 node = p.parent()
1554 else:
1555 node = p
1556 p.v.context.alert(f"invalid outline: {message}\n{node}")
1557 #@+node:ekr.20040303175026.10: *4* p.moveAfter
1558 def moveAfter(self, a: "Position") -> "Position":
1559 """Move a position after position a."""
1560 p = self # Do NOT copy the position!
1561 a._adjustPositionBeforeUnlink(p)
1562 p._unlink()
1563 p._linkAfter(a)
1564 return p
1565 #@+node:ekr.20040306060312: *4* p.moveToFirst/LastChildOf
1566 def moveToFirstChildOf(self, parent: "Position") -> "Position":
1567 """Move a position to the first child of parent."""
1568 p = self # Do NOT copy the position!
1569 return p.moveToNthChildOf(parent, 0) # Major bug fix: 2011/12/04
1571 def moveToLastChildOf(self, parent: "Position") -> "Position":
1572 """Move a position to the last child of parent."""
1573 p = self # Do NOT copy the position!
1574 n = parent.numberOfChildren()
1575 if p.parent() == parent:
1576 n -= 1 # 2011/12/10: Another bug fix.
1577 return p.moveToNthChildOf(parent, n) # Major bug fix: 2011/12/04
1578 #@+node:ekr.20040303175026.11: *4* p.moveToNthChildOf
1579 def moveToNthChildOf(self, parent: "Position", n: int) -> "Position":
1580 """Move a position to the nth child of parent."""
1581 p = self # Do NOT copy the position!
1582 parent._adjustPositionBeforeUnlink(p)
1583 p._unlink()
1584 p._linkAsNthChild(parent, n)
1585 return p
1586 #@+node:ekr.20040303175026.6: *4* p.moveToRoot
1587 def moveToRoot(self) -> "Position":
1588 """Move self to the root position."""
1589 p = self # Do NOT copy the position!
1590 #
1591 # #1631. The old root can not possibly be affected by unlinking p.
1592 p._unlink()
1593 p._linkAsRoot()
1594 return p
1595 #@+node:ekr.20180123062833.1: *4* p.promote
1596 def promote(self) -> None:
1597 """A low-level promote helper."""
1598 p = self # Do NOT copy the position.
1599 parent_v = p._parentVnode()
1600 children = p.v.children
1601 # Add the children to parent_v's children.
1602 n = p.childIndex() + 1
1603 z = parent_v.children[:]
1604 parent_v.children = z[:n]
1605 parent_v.children.extend(children)
1606 parent_v.children.extend(z[n:])
1607 # Remove v's children.
1608 p.v.children = []
1609 # Adjust the parent links in the moved children.
1610 # There is no need to adjust descendant links.
1611 for child in children:
1612 child.parents.remove(p.v)
1613 child.parents.append(parent_v)
1614 #@+node:ekr.20040303175026.13: *4* p.validateOutlineWithParent
1615 # This routine checks the structure of the receiver's tree.
1617 def validateOutlineWithParent(self, pv: "Position") -> bool:
1618 p = self
1619 result = True # optimists get only unpleasant surprises.
1620 parent = p.getParent()
1621 childIndex = p._childIndex
1622 #@+<< validate parent ivar >>
1623 #@+node:ekr.20040303175026.14: *5* << validate parent ivar >>
1624 if parent != pv:
1625 p.invalidOutline("Invalid parent link: " + repr(parent))
1626 #@-<< validate parent ivar >>
1627 #@+<< validate childIndex ivar >>
1628 #@+node:ekr.20040303175026.15: *5* << validate childIndex ivar >>
1629 if pv:
1630 if childIndex < 0:
1631 p.invalidOutline(f"missing childIndex: {childIndex!r}")
1632 elif childIndex >= pv.numberOfChildren():
1633 p.invalidOutline("missing children entry for index: {childIndex!r}")
1634 elif childIndex < 0:
1635 p.invalidOutline("negative childIndex: {childIndex!r}")
1636 #@-<< validate childIndex ivar >>
1637 #@+<< validate x ivar >>
1638 #@+node:ekr.20040303175026.16: *5* << validate x ivar >>
1639 if not p.v and pv:
1640 self.invalidOutline("Empty t")
1641 #@-<< validate x ivar >>
1642 # Recursively validate all the children.
1643 for child in p.children():
1644 r = child.validateOutlineWithParent(p)
1645 if not r:
1646 result = False
1647 return result
1648 #@+node:ekr.20090128083459.74: *3* p.Properties
1649 #@+node:ekr.20090128083459.75: *4* p.b property
1650 def __get_b(self) -> str:
1651 """Return the body text of a position."""
1652 p = self
1653 return p.bodyString()
1655 def __set_b(self, val: str) -> None:
1656 """
1657 Set the body text of a position.
1659 **Warning: the p.b = whatever is *expensive* because it calls
1660 c.setBodyString().
1662 Usually, code *should* use this setter, despite its cost, because it
1663 update's Leo's outline pane properly. Calling c.redraw() is *not*
1664 enough.
1666 This performance gotcha becomes important for repetitive commands, like
1667 cff, replace-all and recursive import. In such situations, code should
1668 use p.v.b instead of p.b.
1669 """
1670 p = self
1671 c = p.v and p.v.context
1672 if c:
1673 c.setBodyString(p, val)
1674 # Warning: c.setBodyString is *expensive*.
1676 b = property(
1677 __get_b, __set_b,
1678 doc="position body string property")
1679 #@+node:ekr.20090128083459.76: *4* p.h property
1680 def __get_h(self) -> str:
1681 p = self
1682 return p.headString()
1684 def __set_h(self, val: str) -> None:
1685 """
1686 Set the headline text of a position.
1688 **Warning: the p.h = whatever is *expensive* because it calls
1689 c.setHeadString().
1691 Usually, code *should* use this setter, despite its cost, because it
1692 update's Leo's outline pane properly. Calling c.redraw() is *not*
1693 enough.
1695 This performance gotcha becomes important for repetitive commands, like
1696 cff, replace-all and recursive import. In such situations, code should
1697 use p.v.h instead of p.h.
1698 """
1699 p = self
1700 c = p.v and p.v.context
1701 if c:
1702 c.setHeadString(p, val)
1703 # Warning: c.setHeadString is *expensive*.
1705 h = property(
1706 __get_h, __set_h,
1707 doc="position property returning the headline string")
1708 #@+node:ekr.20090215165030.3: *4* p.gnx property
1709 def __get_gnx(self) -> str:
1710 p = self
1711 return p.v.fileIndex
1713 gnx = property(
1714 __get_gnx, # __set_gnx,
1715 doc="position gnx property")
1716 #@+node:ekr.20140203082618.15486: *4* p.script property
1717 def __get_script(self) -> str:
1718 p = self
1719 return g.getScript(p.v.context, p,
1720 useSelectedText=False, # Always return the entire expansion.
1721 forcePythonSentinels=True,
1722 useSentinels=False)
1724 script = property(
1725 __get_script, # __set_script,
1726 doc="position property returning the script formed by p and its descendants")
1727 #@+node:ekr.20140218040104.16761: *4* p.nosentinels property
1728 def __get_nosentinels(self) -> str:
1729 p = self
1730 return ''.join([z for z in g.splitLines(p.b) if not g.isDirective(z)])
1732 nosentinels = property(
1733 __get_nosentinels, # __set_nosentinels
1734 doc="position property returning the body text without sentinels")
1735 #@+node:ekr.20160129073222.1: *4* p.u Property
1736 def __get_u(self) -> Any:
1737 p = self
1738 return p.v.u
1740 def __set_u(self, val: Any) -> None:
1741 p = self
1742 p.v.u = val
1744 u = property(
1745 __get_u, __set_u,
1746 doc="p.u property")
1747 #@+node:ekr.20040305222924: *3* p.Setters
1748 #@+node:ekr.20040306220634: *4* p.VNode proxies
1749 #@+node:ekr.20131222112420.16371: *5* p.contract/expand/isExpanded
1750 def contract(self) -> None:
1751 """Contract p.v and clear p.v.expandedPositions list."""
1752 p, v = self, self.v
1753 v.expandedPositions = [z for z in v.expandedPositions if z != p]
1754 v.contract()
1756 def expand(self) -> None:
1757 p = self
1758 v = self.v
1759 v.expandedPositions = [z for z in v.expandedPositions if z != p]
1760 for p2 in v.expandedPositions:
1761 if p == p2:
1762 break
1763 else:
1764 v.expandedPositions.append(p.copy())
1765 v.expand()
1767 def isExpanded(self) -> bool:
1768 p = self
1769 if p.isCloned():
1770 c = p.v.context
1771 return c.shouldBeExpanded(p)
1772 return p.v.isExpanded()
1773 #@+node:ekr.20040306220634.9: *5* p.Status bits
1774 # Clone bits are no longer used.
1775 # Dirty bits are handled carefully by the position class.
1777 def clearMarked(self) -> None:
1778 self.v.clearMarked()
1780 def clearOrphan(self) -> None:
1781 self.v.clearOrphan()
1783 def clearVisited(self) -> None:
1784 self.v.clearVisited()
1786 def initExpandedBit(self) -> None:
1787 self.v.initExpandedBit()
1789 def initMarkedBit(self) -> None:
1790 self.v.initMarkedBit()
1792 def initStatus(self, status: int) -> None:
1793 self.v.initStatus(status)
1795 def setMarked(self) -> None:
1796 self.v.setMarked()
1798 def setOrphan(self) -> None:
1799 self.v.setOrphan()
1801 def setSelected(self) -> None:
1802 self.v.setSelected()
1804 def setVisited(self) -> None:
1805 self.v.setVisited()
1806 #@+node:ekr.20040306220634.8: *5* p.computeIcon & p.setIcon
1807 def computeIcon(self) -> int:
1808 return self.v.computeIcon()
1810 def setIcon(self) -> None:
1811 pass # Compatibility routine for old scripts
1812 #@+node:ekr.20040306220634.29: *5* p.setSelection
1813 def setSelection(self, start: int, length: int) -> None:
1814 self.v.setSelection(start, length)
1815 #@+node:ekr.20100303074003.5637: *5* p.restore/saveCursorAndScroll
1816 def restoreCursorAndScroll(self) -> None:
1817 self.v.restoreCursorAndScroll()
1819 def saveCursorAndScroll(self) -> None:
1820 self.v.saveCursorAndScroll()
1821 #@+node:ekr.20040315034158: *4* p.setBodyString & setHeadString
1822 def setBodyString(self, s: str) -> None:
1823 p = self
1824 return p.v.setBodyString(s)
1826 initBodyString = setBodyString
1827 setTnodeText = setBodyString
1828 scriptSetBodyString = setBodyString
1830 def initHeadString(self, s: str) -> None:
1831 p = self
1832 p.v.initHeadString(s)
1834 def setHeadString(self, s: str) -> None:
1835 p = self
1836 p.v.initHeadString(s)
1837 p.setDirty()
1838 #@+node:ekr.20040312015908: *4* p.Visited bits
1839 #@+node:ekr.20040306220634.17: *5* p.clearVisitedInTree
1840 # Compatibility routine for scripts.
1842 def clearVisitedInTree(self) -> None:
1843 for p in self.self_and_subtree(copy=False):
1844 p.clearVisited()
1845 #@+node:ekr.20031218072017.3388: *5* p.clearAllVisitedInTree
1846 def clearAllVisitedInTree(self) -> None:
1847 for p in self.self_and_subtree(copy=False):
1848 p.v.clearVisited()
1849 p.v.clearWriteBit()
1850 #@+node:ekr.20040305162628: *4* p.Dirty bits
1851 #@+node:ekr.20040311113514: *5* p.clearDirty
1852 def clearDirty(self) -> None:
1853 """(p) Set p.v dirty."""
1854 p = self
1855 p.v.clearDirty()
1856 #@+node:ekr.20040702104823: *5* p.inAtIgnoreRange
1857 def inAtIgnoreRange(self) -> bool:
1858 """Returns True if position p or one of p's parents is an @ignore node."""
1859 p = self
1860 for p in p.self_and_parents(copy=False):
1861 if p.isAtIgnoreNode():
1862 return True
1863 return False
1864 #@+node:ekr.20040303214038: *5* p.setAllAncestorAtFileNodesDirty
1865 def setAllAncestorAtFileNodesDirty(self) -> None:
1866 """
1867 Set all ancestor @<file> nodes dirty, including ancestors of all clones of p.
1868 """
1869 p = self
1870 p.v.setAllAncestorAtFileNodesDirty()
1871 #@+node:ekr.20040303163330: *5* p.setDirty
1872 def setDirty(self) -> None:
1873 """
1874 Mark a node and all ancestor @file nodes dirty.
1876 p.setDirty() is no longer expensive.
1877 """
1878 p = self
1879 p.v.setAllAncestorAtFileNodesDirty()
1880 p.v.setDirty()
1881 #@+node:ekr.20160225153333.1: *3* p.Predicates
1882 #@+node:ekr.20160225153414.1: *4* p.is_at_all & is_at_all_tree
1883 def is_at_all(self) -> bool:
1884 """Return True if p.b contains an @all directive."""
1885 p = self
1886 return (
1887 p.isAnyAtFileNode()
1888 and any(g.match_word(s, 0, '@all') for s in g.splitLines(p.b)))
1890 def in_at_all_tree(self) -> bool:
1891 """Return True if p or one of p's ancestors is an @all node."""
1892 p = self
1893 for p in p.self_and_parents(copy=False):
1894 if p.is_at_all():
1895 return True
1896 return False
1897 #@+node:ekr.20160225153430.1: *4* p.is_at_ignore & in_at_ignore_tree
1898 def is_at_ignore(self) -> bool:
1899 """Return True if p is an @ignore node."""
1900 p = self
1901 return g.match_word(p.h, 0, '@ignore')
1903 def in_at_ignore_tree(self) -> bool:
1904 """Return True if p or one of p's ancestors is an @ignore node."""
1905 p = self
1906 for p in p.self_and_parents(copy=False):
1907 if g.match_word(p.h, 0, '@ignore'):
1908 return True
1909 return False
1910 #@-others
1912position = Position # compatibility.
1913#@+node:ville.20090311190405.68: ** class PosList (leoNodes.py)
1914class PosList(list):
1916 __slots__: List[str] = []
1918 #@+others
1919 #@+node:bob.20101215134608.5897: *3* PosList.children
1920 def children(self) -> "PosList":
1921 """ Return a PosList instance containing pointers to
1922 all the immediate children of nodes in PosList self.
1923 """
1924 res = PosList()
1925 for p in self:
1926 for child_p in p.children():
1927 res.append(child_p.copy())
1928 return res
1929 #@+node:ville.20090311190405.69: *3* PosList.filter_h
1930 def filter_h(self, regex: str, flags: Any=re.IGNORECASE) -> "PosList":
1931 """
1932 Find all the nodes in PosList self where zero or more characters at
1933 the beginning of the headline match regex.
1934 """
1935 pat = re.compile(regex, flags)
1936 res = PosList()
1937 for p in self:
1938 mo = re.match(pat, p.h)
1939 if mo:
1940 # #2012: Don't inject pc.mo.
1941 pc = p.copy()
1942 res.append(pc)
1943 return res
1944 #@+node:ville.20090311195550.1: *3* PosList.filter_b
1945 def filter_b(self, regex: str, flags: Any=re.IGNORECASE) -> "PosList":
1946 """ Find all the nodes in PosList self where body matches regex
1947 one or more times.
1949 """
1950 pat = re.compile(regex, flags)
1951 res = PosList()
1952 for p in self:
1953 m = re.finditer(pat, p.b)
1954 t1, t2 = itertools.tee(m, 2)
1955 try:
1956 t1.__next__()
1957 pc = p.copy()
1958 pc.matchiter = t2
1959 res.append(pc)
1960 except StopIteration:
1961 pass
1962 return res
1963 #@-others
1965Poslist = PosList # compatibility.
1966#@+node:ekr.20031218072017.3341: ** class VNode
1967#@@nobeautify
1969class VNode:
1971 __slots__ = [
1972 '_bodyString', '_headString', '_p_changed',
1973 'children', 'fileIndex', 'iconVal', 'parents', 'statusBits',
1974 'unknownAttributes',
1975 # Injected by read code.
1976 'at_read', 'tempAttributes',
1977 # Not written to any file.
1978 'context', 'expandedPositions', 'insertSpot',
1979 'scrollBarSpot', 'selectionLength', 'selectionStart',
1980 ]
1981 #@+<< VNode constants >>
1982 #@+node:ekr.20031218072017.951: *3* << VNode constants >>
1983 # Define the meaning of status bits in new vnodes.
1984 # Archived...
1985 clonedBit = 0x01 # True: VNode has clone mark.
1986 # unused 0x02
1987 expandedBit = 0x04 # True: VNode is expanded.
1988 markedBit = 0x08 # True: VNode is marked
1989 # unused = 0x10 # (was orphanBit)
1990 selectedBit = 0x20 # True: VNode is current VNode.
1991 topBit = 0x40 # True: VNode was top VNode when saved.
1992 # Not archived...
1993 richTextBit = 0x080 # Determines whether we use <bt> or <btr> tags.
1994 visitedBit = 0x100
1995 dirtyBit = 0x200
1996 writeBit = 0x400
1997 orphanBit = 0x800 # True: error in @<file> tree prevented it from being written.
1998 #@-<< VNode constants >>
1999 #@+others
2000 #@+node:ekr.20031218072017.3342: *3* v.Birth & death
2001 #@+node:ekr.20031218072017.3344: *4* v.__init__
2002 def __init__(self, context: "Cmdr", gnx: Optional[str]=None):
2003 """
2004 Ctor for the VNode class.
2005 To support ZODB, the code must set v._p_changed = True whenever
2006 v.unknownAttributes or any mutable VNode object changes.
2007 """
2008 # The primary data: headline and body text.
2009 self._headString = 'newHeadline'
2010 self._bodyString = ''
2011 # For zodb.
2012 self._p_changed = False
2013 # Structure data...
2014 self.children: List["VNode"] = []
2015 # Ordered list of all children of this node.
2016 self.parents: List["VNode"] = []
2017 # Unordered list of all parents of this node.
2018 # Other essential data...
2019 self.fileIndex: Optional[str] = None
2020 # The immutable fileIndex (gnx) for this node. Set below.
2021 self.iconVal = 0
2022 # The present value of the node's icon.
2023 self.statusBits = 0
2024 # status bits
2025 # Information that is never written to any file...
2026 self.context: Cmdr = context # The context containing context.hiddenRootNode.
2027 # Required so we can compute top-level siblings.
2028 # It is named .context rather than .c to emphasize its limited usage.
2029 self.expandedPositions: List[Position] = []
2030 # Positions that should be expanded.
2031 self.insertSpot: Optional[int] = None
2032 # Location of previous insert point.
2033 self.scrollBarSpot: Optional[int] = None
2034 # Previous value of scrollbar position.
2035 self.selectionLength = 0
2036 # The length of the selected body text.
2037 self.selectionStart = 0
2038 # The start of the selected body text.
2039 #
2040 # For at.read logic.
2041 self.at_read: Dict[str, set] = {}
2042 #
2043 # To make VNode's independent of Leo's core,
2044 # wrap all calls to the VNode ctor::
2045 #
2046 # def allocate_vnode(c,gnx):
2047 # v = VNode(c)
2048 # g.app.nodeIndices.new_vnode_helper(c,gnx,v)
2049 g.app.nodeIndices.new_vnode_helper(context, gnx, self)
2050 assert self.fileIndex, g.callers()
2051 #@+node:ekr.20031218072017.3345: *4* v.__repr__ & v.__str__
2052 def __repr__(self) -> str:
2053 return f"<VNode {self.gnx} {self.headString()}>"
2055 __str__ = __repr__
2056 #@+node:ekr.20040312145256: *4* v.dump
2057 def dumpLink(self, link: Optional[str]) -> str:
2058 return link if link else "<none>"
2060 def dump(self, label: str="") -> None:
2061 v = self
2062 s = '-' * 10
2063 print(f"{s} {label} {v}")
2064 # print('gnx: %s' % v.gnx)
2065 print(f"len(parents): {len(v.parents)}")
2066 print(f"len(children): {len(v.children)}")
2067 print(f"parents: {g.listToString(v.parents)}")
2068 print(f"children: {g.listToString(v.children)}")
2069 #@+node:ekr.20031218072017.3346: *3* v.Comparisons
2070 #@+node:ekr.20040705201018: *4* v.findAtFileName
2071 def findAtFileName(self, names: Tuple, h: Optional[str]=None) -> str:
2072 """Return the name following one of the names in nameList or """
2073 # Allow h argument for unit testing.
2074 if not h:
2075 h = self.headString()
2076 if not g.match(h, 0, '@'):
2077 return ""
2078 i = g.skip_id(h, 1, '-')
2079 word = h[:i]
2080 if word in names and g.match_word(h, 0, word):
2081 name = h[i:].strip()
2082 return name
2083 return ""
2084 #@+node:ekr.20031218072017.3350: *4* v.anyAtFileNodeName
2085 def anyAtFileNodeName(self) -> str:
2086 """Return the file name following an @file node or an empty string."""
2087 return (
2088 self.findAtFileName(g.app.atAutoNames) or
2089 self.findAtFileName(g.app.atFileNames))
2090 #@+node:ekr.20031218072017.3348: *4* v.at...FileNodeName
2091 # These return the filename following @xxx, in v.headString.
2092 # Return the the empty string if v is not an @xxx node.
2094 def atAutoNodeName(self, h: Optional[str]=None) -> str:
2095 return self.findAtFileName(g.app.atAutoNames, h=h)
2097 # Retain this special case as part of the "escape hatch".
2098 # That is, we fall back on code in leoRst.py if no
2099 # importer or writer for reStructuredText exists.
2101 def atAutoRstNodeName(self, h: Optional[str]=None) -> str:
2102 names = ("@auto-rst",)
2103 return self.findAtFileName(names, h=h)
2105 def atCleanNodeName(self) -> str:
2106 names = ("@clean",)
2107 return self.findAtFileName(names)
2109 def atEditNodeName(self) -> str:
2110 names = ("@edit",)
2111 return self.findAtFileName(names)
2113 def atFileNodeName(self) -> str:
2114 names = ("@file", "@thin")
2115 # Fix #403.
2116 return self.findAtFileName(names)
2118 def atNoSentinelsFileNodeName(self) -> str:
2119 names = ("@nosent", "@file-nosent",)
2120 return self.findAtFileName(names)
2122 def atRstFileNodeName(self) -> str:
2123 names = ("@rst",)
2124 return self.findAtFileName(names)
2126 def atShadowFileNodeName(self) -> str:
2127 names = ("@shadow",)
2128 return self.findAtFileName(names)
2130 def atSilentFileNodeName(self) -> str:
2131 names = ("@asis", "@file-asis",)
2132 return self.findAtFileName(names)
2134 def atThinFileNodeName(self) -> str:
2135 names = ("@thin", "@file-thin",)
2136 return self.findAtFileName(names)
2138 # New names, less confusing
2140 atNoSentFileNodeName = atNoSentinelsFileNodeName
2141 atAsisFileNodeName = atSilentFileNodeName
2142 #@+node:EKR.20040430152000: *4* v.isAtAllNode
2143 def isAtAllNode(self) -> bool:
2144 """Returns True if the receiver contains @others in its body at the start of a line."""
2145 flag, i = g.is_special(self._bodyString, "@all")
2146 return flag
2147 #@+node:ekr.20040326031436: *4* v.isAnyAtFileNode
2148 def isAnyAtFileNode(self) -> bool:
2149 """Return True if v is any kind of @file or related node."""
2150 return bool(self.anyAtFileNodeName())
2151 #@+node:ekr.20040325073709: *4* v.isAt...FileNode
2152 def isAtAutoNode(self) -> bool:
2153 return bool(self.atAutoNodeName())
2155 def isAtAutoRstNode(self) -> bool:
2156 return bool(self.atAutoRstNodeName())
2158 def isAtCleanNode(self) -> bool:
2159 return bool(self.atCleanNodeName())
2161 def isAtEditNode(self) -> bool:
2162 return bool(self.atEditNodeName())
2164 def isAtFileNode(self) -> bool:
2165 return bool(self.atFileNodeName())
2167 def isAtRstFileNode(self) -> bool:
2168 return bool(self.atRstFileNodeName())
2170 def isAtNoSentinelsFileNode(self) -> bool:
2171 return bool(self.atNoSentinelsFileNodeName())
2173 def isAtSilentFileNode(self) -> bool:
2174 return bool(self.atSilentFileNodeName())
2176 def isAtShadowFileNode(self) -> bool:
2177 return bool(self.atShadowFileNodeName())
2179 def isAtThinFileNode(self) -> bool:
2180 return bool(self.atThinFileNodeName())
2182 # New names, less confusing:
2184 isAtNoSentFileNode = isAtNoSentinelsFileNode
2185 isAtAsisFileNode = isAtSilentFileNode
2186 #@+node:ekr.20031218072017.3351: *4* v.isAtIgnoreNode
2187 def isAtIgnoreNode(self) -> bool:
2188 """
2189 Returns True if:
2191 - the vnode' body contains @ignore at the start of a line or
2193 - the vnode's headline starts with @ignore.
2194 """
2195 # v = self
2196 if g.match_word(self._headString, 0, '@ignore'):
2197 return True
2198 flag, i = g.is_special(self._bodyString, "@ignore")
2199 return flag
2200 #@+node:ekr.20031218072017.3352: *4* v.isAtOthersNode
2201 def isAtOthersNode(self) -> bool:
2202 """Returns True if the receiver contains @others in its body at the start of a line."""
2203 flag, i = g.is_special(self._bodyString, "@others")
2204 return flag
2205 #@+node:ekr.20031218072017.3353: *4* v.matchHeadline
2206 def matchHeadline(self, pattern: str) -> bool:
2207 """
2208 Returns True if the headline matches the pattern ignoring whitespace and case.
2210 The headline may contain characters following the successfully matched pattern.
2211 """
2212 v = self
2213 h = g.toUnicode(v.headString())
2214 h = h.lower().replace(' ', '').replace('\t', '')
2215 h = h.lstrip('.') # 2013/04/05. Allow leading period before section names.
2216 pattern = g.toUnicode(pattern)
2217 pattern = pattern.lower().replace(' ', '').replace('\t', '')
2218 return h.startswith(pattern)
2219 #@+node:ekr.20160502100151.1: *3* v.copyTree
2220 def copyTree(self, copyMarked: bool=False) -> "VNode":
2221 """
2222 Return an all-new tree of vnodes that are copies of self and all its
2223 descendants.
2225 **Important**: the v.parents ivar must be [] for all nodes.
2226 v._addParentLinks will set all parents.
2227 """
2228 v = self
2229 # Allocate a new vnode and gnx with empty children & parents.
2230 v2 = VNode(context=v.context, gnx=None)
2231 assert v2.parents == [], v2.parents
2232 assert v2.gnx
2233 assert v.gnx != v2.gnx
2234 # Copy vnode fields. Do **not** set v2.parents.
2235 v2._headString = g.toUnicode(v._headString, reportErrors=True)
2236 v2._bodyString = g.toUnicode(v._bodyString, reportErrors=True)
2237 v2.u = copy.deepcopy(v.u)
2238 if copyMarked and v.isMarked():
2239 v2.setMarked()
2240 # Recursively copy all descendant vnodes.
2241 for child in v.children:
2242 v2.children.append(child.copyTree(copyMarked))
2243 return v2
2244 #@+node:ekr.20031218072017.3359: *3* v.Getters
2245 #@+node:ekr.20031218072017.3378: *4* v.bodyString
2246 def bodyString(self) -> str:
2247 # This message should never be printed and we want to avoid crashing here!
2248 if isinstance(self._bodyString, str):
2249 return self._bodyString
2250 g.internalError(f"body not unicode: {self._bodyString!r}")
2251 return g.toUnicode(self._bodyString)
2252 #@+node:ekr.20031218072017.3360: *4* v.Children
2253 #@+node:ekr.20031218072017.3362: *5* v.firstChild
2254 def firstChild(self) -> Optional["VNode"]:
2255 v = self
2256 return v.children[0] if v.children else None
2257 #@+node:ekr.20040307085922: *5* v.hasChildren & hasFirstChild
2258 def hasChildren(self) -> bool:
2259 v = self
2260 return len(v.children) > 0
2262 hasFirstChild = hasChildren
2263 #@+node:ekr.20031218072017.3364: *5* v.lastChild
2264 def lastChild(self) -> Optional["VNode"]:
2265 v = self
2266 return v.children[-1] if v.children else None
2267 #@+node:ekr.20031218072017.3365: *5* v.nthChild
2268 # childIndex and nthChild are zero-based.
2270 def nthChild(self, n: int) -> Optional["VNode"]:
2271 v = self
2272 if 0 <= n < len(v.children):
2273 return v.children[n]
2274 return None
2275 #@+node:ekr.20031218072017.3366: *5* v.numberOfChildren
2276 def numberOfChildren(self) -> int:
2277 v = self
2278 return len(v.children)
2279 #@+node:ekr.20040323100443: *4* v.directParents
2280 def directParents(self) -> List["VNode"]:
2281 """(New in 4.2) Return a list of all direct parent vnodes of a VNode.
2283 This is NOT the same as the list of ancestors of the VNode."""
2284 v = self
2285 return v.parents
2286 #@+node:ekr.20080429053831.6: *4* v.hasBody
2287 def hasBody(self) -> bool:
2288 """Return True if this VNode contains body text."""
2289 s = self._bodyString
2290 return bool(s) and len(s) > 0
2291 #@+node:ekr.20031218072017.1581: *4* v.headString
2292 def headString(self) -> str:
2293 """Return the headline string."""
2294 # This message should never be printed and we want to avoid crashing here!
2295 if isinstance(self._headString, str):
2296 return self._headString
2297 g.internalError(f"headline not unicode: {self._headString!r}")
2298 return g.toUnicode(self._headString)
2299 #@+node:ekr.20131223064351.16351: *4* v.isNthChildOf
2300 def isNthChildOf(self, n: int, parent_v: "VNode") -> bool:
2301 """Return True if v is the n'th child of parent_v."""
2302 v = self
2303 if not parent_v:
2304 return False
2305 children = parent_v.children
2306 if not children:
2307 return False
2308 return 0 <= n < len(children) and children[n] == v
2309 #@+node:ekr.20031218072017.3367: *4* v.Status Bits
2310 #@+node:ekr.20031218072017.3368: *5* v.isCloned
2311 def isCloned(self) -> bool:
2312 return len(self.parents) > 1
2313 #@+node:ekr.20031218072017.3369: *5* v.isDirty
2314 def isDirty(self) -> bool:
2315 return (self.statusBits & self.dirtyBit) != 0
2316 #@+node:ekr.20031218072017.3371: *5* v.isMarked
2317 def isMarked(self) -> bool:
2318 return (self.statusBits & VNode.markedBit) != 0
2319 #@+node:ekr.20031218072017.3372: *5* v.isOrphan
2320 def isOrphan(self) -> bool:
2321 return (self.statusBits & VNode.orphanBit) != 0
2322 #@+node:ekr.20031218072017.3373: *5* v.isSelected
2323 def isSelected(self) -> bool:
2324 return (self.statusBits & VNode.selectedBit) != 0
2325 #@+node:ekr.20031218072017.3374: *5* v.isTopBitSet
2326 def isTopBitSet(self) -> bool:
2327 return (self.statusBits & self.topBit) != 0
2328 #@+node:ekr.20031218072017.3376: *5* v.isVisited
2329 def isVisited(self) -> bool:
2330 return (self.statusBits & VNode.visitedBit) != 0
2331 #@+node:ekr.20080429053831.10: *5* v.isWriteBit
2332 def isWriteBit(self) -> bool:
2333 v = self
2334 return (v.statusBits & v.writeBit) != 0
2335 #@+node:ekr.20031218072017.3377: *5* v.status
2336 def status(self) -> int:
2337 return self.statusBits
2338 #@+node:ekr.20031218072017.3384: *3* v.Setters
2339 #@+node:ekr.20031218072017.3386: *4* v.Status bits
2340 #@+node:ekr.20031218072017.3389: *5* v.clearClonedBit
2341 def clearClonedBit(self) -> None:
2342 self.statusBits &= ~self.clonedBit
2343 #@+node:ekr.20031218072017.3390: *5* v.clearDirty
2344 def clearDirty(self) -> None:
2345 """Clear the vnode dirty bit."""
2346 v = self
2347 v.statusBits &= ~v.dirtyBit
2348 #@+node:ekr.20031218072017.3391: *5* v.clearMarked
2349 def clearMarked(self) -> None:
2350 self.statusBits &= ~self.markedBit
2351 #@+node:ekr.20031218072017.3392: *5* v.clearOrphan
2352 def clearOrphan(self) -> None:
2353 self.statusBits &= ~self.orphanBit
2354 #@+node:ekr.20031218072017.3393: *5* v.clearVisited
2355 def clearVisited(self) -> None:
2356 self.statusBits &= ~self.visitedBit
2357 #@+node:ekr.20080429053831.8: *5* v.clearWriteBit
2358 def clearWriteBit(self) -> None:
2359 self.statusBits &= ~self.writeBit
2360 #@+node:ekr.20031218072017.3395: *5* v.contract/expand/initExpandedBit/isExpanded
2361 def contract(self) -> None:
2362 """Contract the node."""
2363 self.statusBits &= ~self.expandedBit
2365 def expand(self) -> None:
2366 """Expand the node."""
2367 self.statusBits |= self.expandedBit
2369 def initExpandedBit(self) -> None:
2370 """Init self.statusBits."""
2371 self.statusBits |= self.expandedBit
2373 def isExpanded(self) -> bool:
2374 """Return True if the VNode expansion bit is set."""
2375 return (self.statusBits & self.expandedBit) != 0
2376 #@+node:ekr.20031218072017.3396: *5* v.initStatus
2377 def initStatus(self, status: int) -> None:
2378 self.statusBits = status
2379 #@+node:ekr.20031218072017.3397: *5* v.setClonedBit & initClonedBit
2380 def setClonedBit(self) -> None:
2381 self.statusBits |= self.clonedBit
2383 def initClonedBit(self, val: bool) -> None:
2384 if val:
2385 self.statusBits |= self.clonedBit
2386 else:
2387 self.statusBits &= ~self.clonedBit
2388 #@+node:ekr.20080429053831.12: *5* v.setDirty
2389 def setDirty(self) -> None:
2390 """
2391 Set the vnode dirty bit.
2393 This method is fast, but dangerous. Unlike p.setDirty, this method does
2394 not call v.setAllAncestorAtFileNodesDirty.
2395 """
2396 self.statusBits |= self.dirtyBit
2397 #@+node:ekr.20031218072017.3398: *5* v.setMarked & initMarkedBit
2398 def setMarked(self) -> None:
2399 self.statusBits |= self.markedBit
2401 def initMarkedBit(self) -> None:
2402 self.statusBits |= self.markedBit
2403 #@+node:ekr.20031218072017.3399: *5* v.setOrphan
2404 def setOrphan(self) -> None:
2405 """Set the vnode's orphan bit."""
2406 self.statusBits |= self.orphanBit
2407 #@+node:ekr.20031218072017.3400: *5* v.setSelected
2408 # This only sets the selected bit.
2410 def setSelected(self) -> None:
2411 self.statusBits |= self.selectedBit
2412 #@+node:ekr.20031218072017.3401: *5* v.setVisited
2413 # Compatibility routine for scripts
2415 def setVisited(self) -> None:
2416 self.statusBits |= self.visitedBit
2417 #@+node:ekr.20080429053831.9: *5* v.setWriteBit
2418 def setWriteBit(self) -> None:
2419 self.statusBits |= self.writeBit
2420 #@+node:ville.20120502221057.7499: *4* v.childrenModified
2421 def childrenModified(self) -> None:
2422 g.childrenModifiedSet.add(self)
2423 #@+node:ekr.20031218072017.3385: *4* v.computeIcon & setIcon
2424 def computeIcon(self) -> int:
2425 v = self
2426 val = 0
2427 if v.hasBody():
2428 val += 1
2429 if v.isMarked():
2430 val += 2
2431 if v.isCloned():
2432 val += 4
2433 if v.isDirty():
2434 val += 8
2435 return val
2437 def setIcon(self) -> None:
2438 pass # Compatibility routine for old scripts
2439 #@+node:ville.20120502221057.7498: *4* v.contentModified
2440 def contentModified(self) -> None:
2441 g.contentModifiedSet.add(self)
2442 #@+node:ekr.20100303074003.5636: *4* v.restoreCursorAndScroll
2443 # Called only by LeoTree.selectHelper.
2445 def restoreCursorAndScroll(self) -> None:
2446 """Restore the cursor position and scroll so it is visible."""
2447 traceTime = False and not g.unitTesting
2448 v = self
2449 ins = v.insertSpot
2450 # start, n = v.selectionStart, v.selectionLength
2451 spot = v.scrollBarSpot
2452 body = self.context.frame.body
2453 w = body.wrapper
2454 # Fix bug 981849: incorrect body content shown.
2455 if ins is None:
2456 ins = 0
2457 # This is very expensive for large text.
2458 if traceTime:
2459 t1 = time.time()
2460 if hasattr(body.wrapper, 'setInsertPoint'):
2461 w.setInsertPoint(ins)
2462 if traceTime:
2463 delta_t = time.time() - t1
2464 if delta_t > 0.1:
2465 g.trace(f"{delta_t:2.3f} sec")
2466 # Override any changes to the scrollbar setting that might
2467 # have been done above by w.setSelectionRange or w.setInsertPoint.
2468 if spot is not None:
2469 w.setYScrollPosition(spot)
2470 v.scrollBarSpot = spot
2471 # Never call w.see here.
2472 #@+node:ekr.20100303074003.5638: *4* v.saveCursorAndScroll
2473 def saveCursorAndScroll(self) -> None:
2475 v = self
2476 c = v.context
2477 w = c.frame.body
2478 if not w:
2479 return
2480 try:
2481 v.scrollBarSpot = w.getYScrollPosition()
2482 v.insertSpot = w.getInsertPoint()
2483 except AttributeError:
2484 # 2011/03/21: w may not support the high-level interface.
2485 pass
2486 #@+node:ekr.20191213161023.1: *4* v.setAllAncestorAtFileNodesDirty
2487 def setAllAncestorAtFileNodesDirty(self) -> None:
2488 """
2489 Original idea by Виталије Милошевић (Vitalije Milosevic).
2491 Modified by EKR.
2492 """
2493 v = self
2494 seen: Set[VNode] = set([v.context.hiddenRootNode])
2496 def v_and_parents(v: "VNode") -> Generator:
2497 if v in seen:
2498 return
2499 seen.add(v)
2500 yield v
2501 for parent_v in v.parents:
2502 if parent_v not in seen:
2503 yield from v_and_parents(parent_v)
2505 for v2 in v_and_parents(v):
2506 if v2.isAnyAtFileNode():
2507 v2.setDirty()
2508 #@+node:ekr.20040315032144: *4* v.setBodyString & v.setHeadString
2509 def setBodyString(self, s: Any) -> None:
2510 v = self
2511 if isinstance(s, str):
2512 v._bodyString = s
2513 return
2514 v._bodyString = g.toUnicode(s, reportErrors=True)
2515 self.contentModified() # #1413.
2516 signal_manager.emit(self.context, 'body_changed', self)
2518 def setHeadString(self, s: Any) -> None:
2519 # Fix bug: https://bugs.launchpad.net/leo-editor/+bug/1245535
2520 # API allows headlines to contain newlines.
2521 v = self
2522 if isinstance(s, str):
2523 v._headString = s.replace('\n', '')
2524 return
2525 s = g.toUnicode(s, reportErrors=True)
2526 v._headString = s.replace('\n', '') # type:ignore
2527 self.contentModified() # #1413.
2529 initBodyString = setBodyString
2530 initHeadString = setHeadString
2531 setHeadText = setHeadString
2532 setTnodeText = setBodyString
2533 #@+node:ekr.20031218072017.3402: *4* v.setSelection
2534 def setSelection(self, start: int, length: int) -> None:
2535 v = self
2536 v.selectionStart = start
2537 v.selectionLength = length
2538 #@+node:ekr.20130524063409.10700: *3* v.Inserting & cloning
2539 def cloneAsNthChild(self, parent_v: "VNode", n: int) -> "VNode":
2540 # Does not check for illegal clones!
2541 v = self
2542 v._linkAsNthChild(parent_v, n)
2543 return v
2545 def insertAsFirstChild(self) -> "VNode":
2546 v = self
2547 return v.insertAsNthChild(0)
2549 def insertAsLastChild(self) -> "VNode":
2550 v = self
2551 return v.insertAsNthChild(len(v.children))
2553 def insertAsNthChild(self, n: int) -> "VNode":
2554 v = self
2555 assert 0 <= n <= len(v.children)
2556 v2 = VNode(v.context)
2557 v2._linkAsNthChild(v, n)
2558 assert v.children[n] == v2
2559 return v2
2560 #@+node:ekr.20080427062528.9: *3* v.Low level methods
2561 #@+node:ekr.20180709175203.1: *4* v._addCopiedLink
2562 def _addCopiedLink(self, childIndex: int, parent_v: "VNode") -> None:
2563 """Adjust links after adding a link to v."""
2564 v = self
2565 v.context.frame.tree.generation += 1
2566 parent_v.childrenModified()
2567 # For a plugin.
2568 # Update parent_v.children & v.parents.
2569 parent_v.children.insert(childIndex, v)
2570 v.parents.append(parent_v)
2571 # Set zodb changed flags.
2572 v._p_changed = True
2573 parent_v._p_changed = True
2574 #@+node:ekr.20090706110836.6135: *4* v._addLink & _addParentLinks
2575 def _addLink(self, childIndex: int, parent_v: "VNode") -> None:
2576 """Adjust links after adding a link to v."""
2577 v = self
2578 v.context.frame.tree.generation += 1
2579 parent_v.childrenModified()
2580 # For a plugin.
2581 # Update parent_v.children & v.parents.
2582 parent_v.children.insert(childIndex, v)
2583 v.parents.append(parent_v)
2584 # Set zodb changed flags.
2585 v._p_changed = True
2586 parent_v._p_changed = True
2587 # If v has only one parent, we adjust all
2588 # the parents links in the descendant tree.
2589 # This handles clones properly when undoing a delete.
2590 if len(v.parents) == 1:
2591 for child in v.children:
2592 child._addParentLinks(parent=v)
2593 #@+node:ekr.20090804184658.6129: *5* v._addParentLinks
2594 def _addParentLinks(self, parent: "VNode") -> None:
2596 v = self
2597 v.parents.append(parent)
2598 if len(v.parents) == 1:
2599 for child in v.children:
2600 child._addParentLinks(parent=v)
2601 #@+node:ekr.20090804184658.6128: *4* v._cutLink & _cutParentLinks
2602 def _cutLink(self, childIndex: int, parent_v: "VNode") -> None:
2603 """Adjust links after cutting a link to v."""
2604 v = self
2605 v.context.frame.tree.generation += 1
2606 parent_v.childrenModified()
2607 assert parent_v.children[childIndex] == v
2608 del parent_v.children[childIndex]
2609 if parent_v in v.parents:
2610 try:
2611 v.parents.remove(parent_v)
2612 except ValueError:
2613 g.internalError(f"{parent_v} not in parents of {v}")
2614 g.trace('v.parents:')
2615 g.printObj(v.parents)
2616 v._p_changed = True
2617 parent_v._p_changed = True
2618 # If v has no more parents, we adjust all
2619 # the parent links in the descendant tree.
2620 # This handles clones properly when deleting a tree.
2621 if not v.parents:
2622 for child in v.children:
2623 child._cutParentLinks(parent=v)
2624 #@+node:ekr.20090804190529.6133: *5* v._cutParentLinks
2625 def _cutParentLinks(self, parent: "VNode") -> None:
2627 v = self
2628 v.parents.remove(parent)
2629 if not v.parents:
2630 for child in v.children:
2631 child._cutParentLinks(parent=v)
2632 #@+node:ekr.20180709064515.1: *4* v._deleteAllChildren
2633 def _deleteAllChildren(self) -> None:
2634 """
2635 Delete all children of self.
2637 This is a low-level method, used by the read code.
2638 It is not intended as a general replacement for p.doDelete().
2639 """
2640 v = self
2641 for v2 in v.children:
2642 try:
2643 v2.parents.remove(v)
2644 except ValueError:
2645 g.internalError(f"{v} not in parents of {v2}")
2646 g.trace('v2.parents:')
2647 g.printObj(v2.parents)
2648 v.children = []
2649 #@+node:ekr.20031218072017.3425: *4* v._linkAsNthChild
2650 def _linkAsNthChild(self, parent_v: "VNode", n: int) -> None:
2651 """Links self as the n'th child of VNode pv"""
2652 v = self # The child node.
2653 v._addLink(n, parent_v)
2654 #@+node:ekr.20090130065000.1: *3* v.Properties
2655 #@+node:ekr.20090130114732.5: *4* v.b Property
2656 def __get_b(self) -> str:
2657 v = self
2658 return v.bodyString()
2660 def __set_b(self, val: str) -> None:
2661 v = self
2662 v.setBodyString(val)
2664 b = property(
2665 __get_b, __set_b,
2666 doc="VNode body string property")
2667 #@+node:ekr.20090130125002.1: *4* v.h property
2668 def __get_h(self) -> str:
2669 v = self
2670 return v.headString()
2672 def __set_h(self, val: str) -> None:
2673 v = self
2674 v.setHeadString(val)
2676 h = property(
2677 __get_h, __set_h,
2678 doc="VNode headline string property")
2679 #@+node:ekr.20090130114732.6: *4* v.u Property
2680 def __get_u(self) -> Dict:
2681 v = self
2682 # Wrong: return getattr(v, 'unknownAttributes', {})
2683 # It is does not set v.unknownAttributes, which can cause problems.
2684 if not hasattr(v, 'unknownAttributes'):
2685 v.unknownAttributes = {} # type:ignore
2686 return v.unknownAttributes # type:ignore
2688 def __set_u(self, val: Any) -> None:
2689 v = self
2690 if val is None:
2691 if hasattr(v, 'unknownAttributes'):
2692 delattr(v, 'unknownAttributes')
2693 elif isinstance(val, dict):
2694 v.unknownAttributes = val # type:ignore
2695 else:
2696 raise ValueError
2698 u = property(
2699 __get_u, __set_u,
2700 doc="VNode u property")
2701 #@+node:ekr.20090215165030.1: *4* v.gnx Property
2702 def __get_gnx(self) -> str:
2703 v = self
2704 return v.fileIndex
2706 gnx = property(
2707 __get_gnx, # __set_gnx,
2708 doc="VNode gnx property")
2709 #@-others
2710vnode = VNode # compatibility.
2712#@@beautify
2713#@-others
2714#@@language python
2715#@@tabwidth -4
2716#@@pagewidth 70
2717#@-leo