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