Coverage for C:\Repos\leo-editor\leo\core\leoNodes.py: 88%

1439 statements  

« prev     ^ index     » next       coverage.py v6.4, created at 2022-05-24 10:21 -0500

1# -*- coding: utf-8 -*- 

2#@+leo-ver=5-thin 

3#@+node:ekr.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).""" 

26 

27 __slots__ = ['defaultId', 'lastIndex', 'stack', 'timeString', 'userId'] 

28 

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()) 

133 

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. 

194 

195 

196class Position: 

197 

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 ] 

207 

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) 

222 

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) 

240 

241 def __le__(self, other: Any) -> bool: 

242 return self.__eq__(other) or self.__lt__(other) 

243 

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. 

277 

278 The tests 'if p' or 'if not p' are the _only_ correct ways to test 

279 whether a position p is valid. 

280 

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>" 

298 

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>" 

319 

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) 

335 

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('.')] 

339 

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). 

348 

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 

388 

389 def moreBody(self) -> str: 

390 """Returns the body string in MORE format. 

391 

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() 

411 

412 # Compatibility with old code... 

413 

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() 

423 

424 # Compatibility with old code... 

425 

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. 

433 

434 The search first proceeds up the p's tree. If a root is found, this 

435 generator yields just that root. 

436 

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() 

441 

442 def default_predicate(p: "Position") -> bool: 

443 return p.isAnyAtFileNode() 

444 

445 the_predicate = predicate or default_predicate 

446 

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. 

467 

468 The search first proceeds up the p's tree. If a root is found, this 

469 generator yields just that root. 

470 

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() 

476 

477 def default_predicate(p: "Position") -> bool: 

478 return p.isAnyAtFileNode() 

479 

480 the_predicate = predicate or default_predicate 

481 

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() 

499 

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() 

510 

511 # Compatibility with old code. 

512 

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() 

522 

523 # Compatibility with old code... 

524 

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() 

534 

535 # Compatibility with old code... 

536 

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() 

548 

549 # Compatibility with old code... 

550 

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() 

561 

562 # Compatibility with old code... 

563 

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() 

575 

576 # Compatibility with old code... 

577 

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 

588 

589 # Compatibility with old code. 

590 

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 

602 

603 # Compatibility with old code... 

604 

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() 

611 

612 def atAutoNodeName(self) -> str: 

613 return self.v.atAutoNodeName() 

614 

615 def atCleanNodeName(self) -> str: 

616 return self.v.atCleanNodeName() 

617 

618 def atEditNodeName(self) -> str: 

619 return self.v.atEditNodeName() 

620 

621 def atFileNodeName(self) -> str: 

622 return self.v.atFileNodeName() 

623 

624 def atNoSentinelsFileNodeName(self) -> str: 

625 return self.v.atNoSentinelsFileNodeName() 

626 

627 def atShadowFileNodeName(self) -> str: 

628 return self.v.atShadowFileNodeName() 

629 

630 def atSilentFileNodeName(self) -> str: 

631 return self.v.atSilentFileNodeName() 

632 

633 def atThinFileNodeName(self) -> str: 

634 return self.v.atThinFileNodeName() 

635 

636 # New names, less confusing 

637 atNoSentFileNodeName = atNoSentinelsFileNodeName 

638 atAsisFileNodeName = atSilentFileNodeName 

639 

640 def isAnyAtFileNode(self) -> bool: 

641 return self.v.isAnyAtFileNode() 

642 

643 def isAtAllNode(self) -> bool: 

644 return self.v.isAtAllNode() 

645 

646 def isAtAutoNode(self) -> bool: 

647 return self.v.isAtAutoNode() 

648 

649 def isAtAutoRstNode(self) -> bool: 

650 return self.v.isAtAutoRstNode() 

651 

652 def isAtCleanNode(self) -> bool: 

653 return self.v.isAtCleanNode() 

654 

655 def isAtEditNode(self) -> bool: 

656 return self.v.isAtEditNode() 

657 

658 def isAtFileNode(self) -> bool: 

659 return self.v.isAtFileNode() 

660 

661 def isAtIgnoreNode(self) -> bool: 

662 return self.v.isAtIgnoreNode() 

663 

664 def isAtNoSentinelsFileNode(self) -> bool: 

665 return self.v.isAtNoSentinelsFileNode() 

666 

667 def isAtOthersNode(self) -> bool: 

668 return self.v.isAtOthersNode() 

669 

670 def isAtRstFileNode(self) -> bool: 

671 return self.v.isAtRstFileNode() 

672 

673 def isAtSilentFileNode(self) -> bool: 

674 return self.v.isAtSilentFileNode() 

675 

676 def isAtShadowFileNode(self) -> bool: 

677 return self.v.isAtShadowFileNode() 

678 

679 def isAtThinFileNode(self) -> bool: 

680 return self.v.isAtThinFileNode() 

681 

682 # New names, less confusing: 

683 isAtNoSentFileNode = isAtNoSentinelsFileNode 

684 isAtAsisFileNode = isAtSilentFileNode 

685 

686 # Utilities. 

687 

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() 

693 

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() 

699 

700 def isMarked(self) -> bool: 

701 return self.v.isMarked() 

702 

703 def isOrphan(self) -> bool: 

704 return self.v.isOrphan() 

705 

706 def isSelected(self) -> bool: 

707 return self.v.isSelected() 

708 

709 def isTopBitSet(self) -> bool: 

710 return self.v.isTopBitSet() 

711 

712 def isVisited(self) -> bool: 

713 return self.v.isVisited() 

714 

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. 

720 

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 

731 

732 hasFirstChild = hasChildren 

733 

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_ 

740 

741 def getBack(self) -> "Position": 

742 return self.copy().moveToBack() 

743 

744 def getFirstChild(self) -> "Position": 

745 return self.copy().moveToFirstChild() 

746 

747 def getLastChild(self) -> "Position": 

748 return self.copy().moveToLastChild() 

749 

750 def getLastNode(self) -> "Position": 

751 return self.copy().moveToLastNode() 

752 

753 def getNext(self) -> "Position": 

754 return self.copy().moveToNext() 

755 

756 def getNodeAfterTree(self) -> "Position": 

757 return self.copy().moveToNodeAfterTree() 

758 

759 def getNthChild(self, n: int) -> "Position": 

760 return self.copy().moveToNthChild(n) 

761 

762 def getParent(self) -> "Position": 

763 return self.copy().moveToParent() 

764 

765 def getThreadBack(self) -> "Position": 

766 return self.copy().moveToThreadBack() 

767 

768 def getThreadNext(self) -> "Position": 

769 return self.copy().moveToThreadNext() 

770 

771 # New in Leo 4.4.3 b2: add c args. 

772 

773 def getVisBack(self, c: "Cmdr") -> "Position": 

774 return self.copy().moveToVisBack(c) 

775 

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. 

799 

800 New in Leo 6.6: Use a single, simplified format for UNL's: 

801 

802 - unl: // 

803 - self.v.context.fileName() # 

804 - a list of headlines separated by '-->' 

805 

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) 

820 

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 

830 

831 def hasParent(self) -> bool: 

832 p = self 

833 return bool(p.v and p.stack) 

834 

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 

891 

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 

900 

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 

916 

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. 

922 

923 This method allows scripts to traverse an outline, deleting nodes during the 

924 traversal. The pattern is:: 

925 

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() 

934 

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. 

938 

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 

1123 

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. 

1193 

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. 

1388 

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. 

1401 

1402 # To do: use v.copyTree instead. 

1403 

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 

1410 

1411 

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 

1437 

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. 

1464 

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. 

1483 

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. 

1497 

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. 

1508 

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. 

1522 

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 

1557 

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. 

1603 

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() 

1641 

1642 def __set_b(self, val: str) -> None: 

1643 """ 

1644 Set the body text of a position. 

1645 

1646 **Warning: the p.b = whatever is *expensive* because it calls 

1647 c.setBodyString(). 

1648 

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. 

1652 

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*. 

1662 

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() 

1670 

1671 def __set_h(self, val: str) -> None: 

1672 """ 

1673 Set the headline text of a position. 

1674 

1675 **Warning: the p.h = whatever is *expensive* because it calls 

1676 c.setHeadString(). 

1677 

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. 

1681 

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*. 

1691 

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 

1699 

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) 

1710 

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)]) 

1718 

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 

1726 

1727 def __set_u(self, val: Any) -> None: 

1728 p = self 

1729 p.v.u = val 

1730 

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() 

1742 

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() 

1753 

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. 

1763 

1764 def clearMarked(self) -> None: 

1765 self.v.clearMarked() 

1766 

1767 def clearOrphan(self) -> None: 

1768 self.v.clearOrphan() 

1769 

1770 def clearVisited(self) -> None: 

1771 self.v.clearVisited() 

1772 

1773 def initExpandedBit(self) -> None: 

1774 self.v.initExpandedBit() 

1775 

1776 def initMarkedBit(self) -> None: 

1777 self.v.initMarkedBit() 

1778 

1779 def initStatus(self, status: int) -> None: 

1780 self.v.initStatus(status) 

1781 

1782 def setMarked(self) -> None: 

1783 self.v.setMarked() 

1784 

1785 def setOrphan(self) -> None: 

1786 self.v.setOrphan() 

1787 

1788 def setSelected(self) -> None: 

1789 self.v.setSelected() 

1790 

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() 

1796 

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() 

1805 

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) 

1812 

1813 initBodyString = setBodyString 

1814 setTnodeText = setBodyString 

1815 scriptSetBodyString = setBodyString 

1816 

1817 def initHeadString(self, s: str) -> None: 

1818 p = self 

1819 p.v.initHeadString(s) 

1820 

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. 

1828 

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. 

1862 

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))) 

1876 

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') 

1889 

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 

1898 

1899position = Position # compatibility. 

1900#@+node:ville.20090311190405.68: ** class PosList (leoNodes.py) 

1901class PosList(list): # pragma: no cover 

1902 

1903 __slots__: List[str] = [] 

1904 

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. 

1935 

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 

1951 

1952Poslist = PosList # compatibility. 

1953#@+node:ekr.20031218072017.3341: ** class VNode 

1954#@@nobeautify 

1955 

1956class VNode: 

1957 

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 

2004 

2005 # Information that is never written to any file... 

2006 

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. 

2016 

2017 # For at.read logic. 

2018 self.at_read: Dict[str, set] = {} 

2019 

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 

2031 

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>" 

2036 

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. 

2070 

2071 def atAutoNodeName(self, h: Optional[str]=None) -> str: 

2072 return self.findAtFileName(g.app.atAutoNames, h=h) 

2073 

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. 

2077 

2078 def atAutoRstNodeName(self, h: Optional[str]=None) -> str: 

2079 names = ("@auto-rst",) 

2080 return self.findAtFileName(names, h=h) 

2081 

2082 def atCleanNodeName(self) -> str: 

2083 names = ("@clean",) 

2084 return self.findAtFileName(names) 

2085 

2086 def atEditNodeName(self) -> str: 

2087 names = ("@edit",) 

2088 return self.findAtFileName(names) 

2089 

2090 def atFileNodeName(self) -> str: 

2091 names = ("@file", "@thin") # Fix #403. 

2092 return self.findAtFileName(names) 

2093 

2094 def atNoSentinelsFileNodeName(self) -> str: 

2095 names = ("@nosent", "@file-nosent",) 

2096 return self.findAtFileName(names) 

2097 

2098 def atRstFileNodeName(self) -> str: 

2099 names = ("@rst",) 

2100 return self.findAtFileName(names) 

2101 

2102 def atShadowFileNodeName(self) -> str: 

2103 names = ("@shadow",) 

2104 return self.findAtFileName(names) 

2105 

2106 def atSilentFileNodeName(self) -> str: 

2107 names = ("@asis", "@file-asis",) 

2108 return self.findAtFileName(names) 

2109 

2110 def atThinFileNodeName(self) -> str: 

2111 names = ("@thin", "@file-thin",) 

2112 return self.findAtFileName(names) 

2113 

2114 # New names, less confusing 

2115 

2116 atNoSentFileNodeName = atNoSentinelsFileNodeName 

2117 atAsisFileNodeName = atSilentFileNodeName 

2118 #@+node:EKR.20040430152000: *4* v.isAtAllNode 

2119 def isAtAllNode(self) -> bool: 

2120 """Returns True if the receiver contains @others in its body at the start of a line.""" 

2121 flag, i = g.is_special(self._bodyString, "@all") 

2122 return flag 

2123 #@+node:ekr.20040326031436: *4* v.isAnyAtFileNode 

2124 def isAnyAtFileNode(self) -> bool: 

2125 """Return True if v is any kind of @file or related node.""" 

2126 return bool(self.anyAtFileNodeName()) 

2127 #@+node:ekr.20040325073709: *4* v.isAt...FileNode 

2128 def isAtAutoNode(self) -> bool: 

2129 return bool(self.atAutoNodeName()) 

2130 

2131 def isAtAutoRstNode(self) -> bool: 

2132 return bool(self.atAutoRstNodeName()) 

2133 

2134 def isAtCleanNode(self) -> bool: 

2135 return bool(self.atCleanNodeName()) 

2136 

2137 def isAtEditNode(self) -> bool: 

2138 return bool(self.atEditNodeName()) 

2139 

2140 def isAtFileNode(self) -> bool: 

2141 return bool(self.atFileNodeName()) 

2142 

2143 def isAtRstFileNode(self) -> bool: 

2144 return bool(self.atRstFileNodeName()) 

2145 

2146 def isAtNoSentinelsFileNode(self) -> bool: 

2147 return bool(self.atNoSentinelsFileNodeName()) 

2148 

2149 def isAtSilentFileNode(self) -> bool: 

2150 return bool(self.atSilentFileNodeName()) 

2151 

2152 def isAtShadowFileNode(self) -> bool: 

2153 return bool(self.atShadowFileNodeName()) 

2154 

2155 def isAtThinFileNode(self) -> bool: 

2156 return bool(self.atThinFileNodeName()) 

2157 

2158 # New names, less confusing: 

2159 

2160 isAtNoSentFileNode = isAtNoSentinelsFileNode 

2161 isAtAsisFileNode = isAtSilentFileNode 

2162 #@+node:ekr.20031218072017.3351: *4* v.isAtIgnoreNode 

2163 def isAtIgnoreNode(self) -> bool: 

2164 """ 

2165 Returns True if: 

2166 

2167 - the vnode' body contains @ignore at the start of a line or 

2168 

2169 - the vnode's headline starts with @ignore. 

2170 """ 

2171 # v = self 

2172 if g.match_word(self._headString, 0, '@ignore'): 

2173 return True 

2174 flag, i = g.is_special(self._bodyString, "@ignore") 

2175 return flag 

2176 #@+node:ekr.20031218072017.3352: *4* v.isAtOthersNode 

2177 def isAtOthersNode(self) -> bool: 

2178 """Returns True if the receiver contains @others in its body at the start of a line.""" 

2179 flag, i = g.is_special(self._bodyString, "@others") 

2180 return flag 

2181 #@+node:ekr.20031218072017.3353: *4* v.matchHeadline 

2182 def matchHeadline(self, pattern: str) -> bool: 

2183 """ 

2184 Returns True if the headline matches the pattern ignoring whitespace and case. 

2185 

2186 The headline may contain characters following the successfully matched pattern. 

2187 """ 

2188 v = self 

2189 h = g.toUnicode(v.headString()) 

2190 h = h.lower().replace(' ', '').replace('\t', '') 

2191 h = h.lstrip('.') # 2013/04/05. Allow leading period before section names. 

2192 pattern = g.toUnicode(pattern) 

2193 pattern = pattern.lower().replace(' ', '').replace('\t', '') 

2194 return h.startswith(pattern) 

2195 #@+node:ekr.20160502100151.1: *3* v.copyTree 

2196 def copyTree(self, copyMarked: bool=False) -> "VNode": 

2197 """ 

2198 Return an all-new tree of vnodes that are copies of self and all its 

2199 descendants. 

2200 

2201 **Important**: the v.parents ivar must be [] for all nodes. 

2202 v._addParentLinks will set all parents. 

2203 """ 

2204 v = self 

2205 # Allocate a new vnode and gnx with empty children & parents. 

2206 v2 = VNode(context=v.context, gnx=None) 

2207 assert v2.parents == [], v2.parents 

2208 assert v2.gnx 

2209 assert v.gnx != v2.gnx 

2210 # Copy vnode fields. Do **not** set v2.parents. 

2211 v2._headString = g.toUnicode(v._headString, reportErrors=True) 

2212 v2._bodyString = g.toUnicode(v._bodyString, reportErrors=True) 

2213 v2.u = copy.deepcopy(v.u) 

2214 if copyMarked and v.isMarked(): 

2215 v2.setMarked() 

2216 # Recursively copy all descendant vnodes. 

2217 for child in v.children: 

2218 v2.children.append(child.copyTree(copyMarked)) 

2219 return v2 

2220 #@+node:ekr.20031218072017.3359: *3* v.Getters 

2221 #@+node:ekr.20031218072017.3378: *4* v.bodyString 

2222 def bodyString(self) -> str: 

2223 # pylint: disable=no-else-return 

2224 if isinstance(self._bodyString, str): 

2225 return self._bodyString 

2226 else: # pragma: no cover 

2227 # This message should never be printed and we want to avoid crashing here! 

2228 g.internalError(f"body not unicode: {self._bodyString!r}") 

2229 return g.toUnicode(self._bodyString) 

2230 #@+node:ekr.20031218072017.3360: *4* v.Children 

2231 #@+node:ekr.20031218072017.3362: *5* v.firstChild 

2232 def firstChild(self) -> Optional["VNode"]: 

2233 v = self 

2234 return v.children[0] if v.children else None 

2235 #@+node:ekr.20040307085922: *5* v.hasChildren & hasFirstChild 

2236 def hasChildren(self) -> bool: 

2237 v = self 

2238 return len(v.children) > 0 

2239 

2240 hasFirstChild = hasChildren 

2241 #@+node:ekr.20031218072017.3364: *5* v.lastChild 

2242 def lastChild(self) -> Optional["VNode"]: 

2243 v = self 

2244 return v.children[-1] if v.children else None 

2245 #@+node:ekr.20031218072017.3365: *5* v.nthChild 

2246 # childIndex and nthChild are zero-based. 

2247 

2248 def nthChild(self, n: int) -> Optional["VNode"]: 

2249 v = self 

2250 if 0 <= n < len(v.children): 

2251 return v.children[n] 

2252 return None 

2253 #@+node:ekr.20031218072017.3366: *5* v.numberOfChildren 

2254 def numberOfChildren(self) -> int: 

2255 v = self 

2256 return len(v.children) 

2257 #@+node:ekr.20040323100443: *4* v.directParents 

2258 def directParents(self) -> List["VNode"]: 

2259 """(New in 4.2) Return a list of all direct parent vnodes of a VNode. 

2260 

2261 This is NOT the same as the list of ancestors of the VNode.""" 

2262 v = self 

2263 return v.parents 

2264 #@+node:ekr.20080429053831.6: *4* v.hasBody 

2265 def hasBody(self) -> bool: 

2266 """Return True if this VNode contains body text.""" 

2267 s = self._bodyString 

2268 return bool(s) and len(s) > 0 

2269 #@+node:ekr.20031218072017.1581: *4* v.headString 

2270 def headString(self) -> str: 

2271 """Return the headline string.""" 

2272 # pylint: disable=no-else-return 

2273 if isinstance(self._headString, str): 

2274 return self._headString 

2275 else: # pragma: no cover 

2276 # This message should never be printed and we want to avoid crashing here! 

2277 g.internalError(f"headline not unicode: {self._headString!r}") 

2278 return g.toUnicode(self._headString) 

2279 #@+node:ekr.20131223064351.16351: *4* v.isNthChildOf 

2280 def isNthChildOf(self, n: int, parent_v: "VNode") -> bool: 

2281 """Return True if v is the n'th child of parent_v.""" 

2282 v = self 

2283 if not parent_v: 

2284 return False 

2285 children = parent_v.children 

2286 if not children: 

2287 return False 

2288 return 0 <= n < len(children) and children[n] == v 

2289 #@+node:ekr.20031218072017.3367: *4* v.Status Bits 

2290 #@+node:ekr.20031218072017.3368: *5* v.isCloned 

2291 def isCloned(self) -> bool: 

2292 return len(self.parents) > 1 

2293 #@+node:ekr.20031218072017.3369: *5* v.isDirty 

2294 def isDirty(self) -> bool: 

2295 return (self.statusBits & self.dirtyBit) != 0 

2296 #@+node:ekr.20031218072017.3371: *5* v.isMarked 

2297 def isMarked(self) -> bool: 

2298 return (self.statusBits & VNode.markedBit) != 0 

2299 #@+node:ekr.20031218072017.3372: *5* v.isOrphan 

2300 def isOrphan(self) -> bool: 

2301 return (self.statusBits & VNode.orphanBit) != 0 

2302 #@+node:ekr.20031218072017.3373: *5* v.isSelected 

2303 def isSelected(self) -> bool: 

2304 return (self.statusBits & VNode.selectedBit) != 0 

2305 #@+node:ekr.20031218072017.3374: *5* v.isTopBitSet 

2306 def isTopBitSet(self) -> bool: 

2307 return (self.statusBits & self.topBit) != 0 

2308 #@+node:ekr.20031218072017.3376: *5* v.isVisited 

2309 def isVisited(self) -> bool: 

2310 return (self.statusBits & VNode.visitedBit) != 0 

2311 #@+node:ekr.20080429053831.10: *5* v.isWriteBit 

2312 def isWriteBit(self) -> bool: 

2313 v = self 

2314 return (v.statusBits & v.writeBit) != 0 

2315 #@+node:ekr.20031218072017.3377: *5* v.status 

2316 def status(self) -> int: 

2317 return self.statusBits 

2318 #@+node:ekr.20031218072017.3384: *3* v.Setters 

2319 #@+node:ekr.20031218072017.3386: *4* v.Status bits 

2320 #@+node:ekr.20031218072017.3389: *5* v.clearClonedBit 

2321 def clearClonedBit(self) -> None: 

2322 self.statusBits &= ~self.clonedBit 

2323 #@+node:ekr.20031218072017.3390: *5* v.clearDirty 

2324 def clearDirty(self) -> None: 

2325 """Clear the vnode dirty bit.""" 

2326 v = self 

2327 v.statusBits &= ~v.dirtyBit 

2328 #@+node:ekr.20031218072017.3391: *5* v.clearMarked 

2329 def clearMarked(self) -> None: 

2330 self.statusBits &= ~self.markedBit 

2331 #@+node:ekr.20031218072017.3392: *5* v.clearOrphan 

2332 def clearOrphan(self) -> None: 

2333 self.statusBits &= ~self.orphanBit 

2334 #@+node:ekr.20031218072017.3393: *5* v.clearVisited 

2335 def clearVisited(self) -> None: 

2336 self.statusBits &= ~self.visitedBit 

2337 #@+node:ekr.20080429053831.8: *5* v.clearWriteBit 

2338 def clearWriteBit(self) -> None: 

2339 self.statusBits &= ~self.writeBit 

2340 #@+node:ekr.20031218072017.3395: *5* v.contract/expand/initExpandedBit/isExpanded 

2341 def contract(self) -> None: 

2342 """Contract the node.""" 

2343 self.statusBits &= ~self.expandedBit 

2344 

2345 def expand(self) -> None: 

2346 """Expand the node.""" 

2347 self.statusBits |= self.expandedBit 

2348 

2349 def initExpandedBit(self) -> None: 

2350 """Init self.statusBits.""" 

2351 self.statusBits |= self.expandedBit 

2352 

2353 def isExpanded(self) -> bool: 

2354 """Return True if the VNode expansion bit is set.""" 

2355 return (self.statusBits & self.expandedBit) != 0 

2356 #@+node:ekr.20031218072017.3396: *5* v.initStatus 

2357 def initStatus(self, status: int) -> None: 

2358 self.statusBits = status 

2359 #@+node:ekr.20031218072017.3397: *5* v.setClonedBit & initClonedBit 

2360 def setClonedBit(self) -> None: 

2361 self.statusBits |= self.clonedBit 

2362 

2363 def initClonedBit(self, val: bool) -> None: 

2364 if val: 

2365 self.statusBits |= self.clonedBit 

2366 else: 

2367 self.statusBits &= ~self.clonedBit 

2368 #@+node:ekr.20080429053831.12: *5* v.setDirty 

2369 def setDirty(self) -> None: 

2370 """ 

2371 Set the vnode dirty bit. 

2372 

2373 This method is fast, but dangerous. Unlike p.setDirty, this method does 

2374 not call v.setAllAncestorAtFileNodesDirty. 

2375 """ 

2376 self.statusBits |= self.dirtyBit 

2377 #@+node:ekr.20031218072017.3398: *5* v.setMarked & initMarkedBit 

2378 def setMarked(self) -> None: 

2379 self.statusBits |= self.markedBit 

2380 

2381 def initMarkedBit(self) -> None: 

2382 self.statusBits |= self.markedBit 

2383 #@+node:ekr.20031218072017.3399: *5* v.setOrphan 

2384 def setOrphan(self) -> None: 

2385 """Set the vnode's orphan bit.""" 

2386 self.statusBits |= self.orphanBit 

2387 #@+node:ekr.20031218072017.3400: *5* v.setSelected 

2388 # This only sets the selected bit. 

2389 

2390 def setSelected(self) -> None: 

2391 self.statusBits |= self.selectedBit 

2392 #@+node:ekr.20031218072017.3401: *5* v.setVisited 

2393 # Compatibility routine for scripts 

2394 

2395 def setVisited(self) -> None: 

2396 self.statusBits |= self.visitedBit 

2397 #@+node:ekr.20080429053831.9: *5* v.setWriteBit 

2398 def setWriteBit(self) -> None: 

2399 self.statusBits |= self.writeBit 

2400 #@+node:ville.20120502221057.7499: *4* v.childrenModified 

2401 def childrenModified(self) -> None: 

2402 g.childrenModifiedSet.add(self) 

2403 #@+node:ekr.20031218072017.3385: *4* v.computeIcon & setIcon 

2404 def computeIcon(self) -> int: # pragma: no cover 

2405 v = self 

2406 val = 0 

2407 if v.hasBody(): 

2408 val += 1 

2409 if v.isMarked(): 

2410 val += 2 

2411 if v.isCloned(): 

2412 val += 4 

2413 if v.isDirty(): 

2414 val += 8 

2415 return val 

2416 

2417 def setIcon(self) -> None: # pragma: no cover 

2418 pass # Compatibility routine for old scripts 

2419 #@+node:ville.20120502221057.7498: *4* v.contentModified 

2420 def contentModified(self) -> None: 

2421 g.contentModifiedSet.add(self) 

2422 #@+node:ekr.20100303074003.5636: *4* v.restoreCursorAndScroll 

2423 # Called only by LeoTree.selectHelper. 

2424 

2425 def restoreCursorAndScroll(self) -> None: 

2426 """Restore the cursor position and scroll so it is visible.""" 

2427 v = self 

2428 ins = v.insertSpot 

2429 # start, n = v.selectionStart, v.selectionLength 

2430 spot = v.scrollBarSpot 

2431 body = self.context.frame.body 

2432 w = body.wrapper 

2433 # Fix bug 981849: incorrect body content shown. 

2434 if ins is None: 

2435 ins = 0 

2436 # This is very expensive for large text. 

2437 if hasattr(body.wrapper, 'setInsertPoint'): 

2438 w.setInsertPoint(ins) 

2439 # Override any changes to the scrollbar setting that might 

2440 # have been done above by w.setSelectionRange or w.setInsertPoint. 

2441 if spot is not None: # pragma: no cover 

2442 w.setYScrollPosition(spot) 

2443 v.scrollBarSpot = spot 

2444 # Never call w.see here. 

2445 #@+node:ekr.20100303074003.5638: *4* v.saveCursorAndScroll 

2446 def saveCursorAndScroll(self) -> None: # pragma: no cover 

2447 

2448 v = self 

2449 c = v.context 

2450 w = c.frame.body 

2451 if not w: 

2452 return 

2453 try: 

2454 v.scrollBarSpot = w.getYScrollPosition() 

2455 v.insertSpot = w.getInsertPoint() 

2456 except AttributeError: 

2457 # 2011/03/21: w may not support the high-level interface. 

2458 pass 

2459 #@+node:ekr.20191213161023.1: *4* v.setAllAncestorAtFileNodesDirty 

2460 def setAllAncestorAtFileNodesDirty(self) -> None: 

2461 """ 

2462 Original idea by Виталије Милошевић (Vitalije Milosevic). 

2463 

2464 Modified by EKR. 

2465 """ 

2466 v = self 

2467 seen: Set[VNode] = set([v.context.hiddenRootNode]) 

2468 

2469 def v_and_parents(v: "VNode") -> Generator: 

2470 if v in seen: 

2471 return 

2472 seen.add(v) 

2473 yield v 

2474 for parent_v in v.parents: 

2475 if parent_v not in seen: 

2476 yield from v_and_parents(parent_v) 

2477 

2478 for v2 in v_and_parents(v): 

2479 if v2.isAnyAtFileNode(): 

2480 v2.setDirty() 

2481 #@+node:ekr.20040315032144: *4* v.setBodyString & v.setHeadString 

2482 def setBodyString(self, s: Any) -> None: 

2483 # pylint: disable=no-else-return 

2484 v = self 

2485 if isinstance(s, str): 

2486 v._bodyString = s 

2487 return 

2488 else: # pragma: no cover 

2489 v._bodyString = g.toUnicode(s, reportErrors=True) 

2490 self.contentModified() # #1413. 

2491 signal_manager.emit(self.context, 'body_changed', self) 

2492 

2493 def setHeadString(self, s: Any) -> None: 

2494 # pylint: disable=no-else-return 

2495 # Fix bug: https://bugs.launchpad.net/leo-editor/+bug/1245535 

2496 # API allows headlines to contain newlines. 

2497 v = self 

2498 if isinstance(s, str): 

2499 v._headString = s.replace('\n', '') 

2500 return 

2501 else: # pragma: no cover 

2502 s = g.toUnicode(s, reportErrors=True) 

2503 v._headString = s.replace('\n', '') # type:ignore 

2504 self.contentModified() # #1413. 

2505 

2506 initBodyString = setBodyString 

2507 initHeadString = setHeadString 

2508 setHeadText = setHeadString 

2509 setTnodeText = setBodyString 

2510 #@+node:ekr.20031218072017.3402: *4* v.setSelection 

2511 def setSelection(self, start: int, length: int) -> None: 

2512 v = self 

2513 v.selectionStart = start 

2514 v.selectionLength = length 

2515 #@+node:ekr.20130524063409.10700: *3* v.Inserting & cloning 

2516 def cloneAsNthChild(self, parent_v: "VNode", n: int) -> "VNode": 

2517 # Does not check for illegal clones! 

2518 v = self 

2519 v._linkAsNthChild(parent_v, n) 

2520 return v 

2521 

2522 def insertAsFirstChild(self) -> "VNode": 

2523 v = self 

2524 return v.insertAsNthChild(0) 

2525 

2526 def insertAsLastChild(self) -> "VNode": 

2527 v = self 

2528 return v.insertAsNthChild(len(v.children)) 

2529 

2530 def insertAsNthChild(self, n: int) -> "VNode": 

2531 v = self 

2532 assert 0 <= n <= len(v.children) 

2533 v2 = VNode(v.context) 

2534 v2._linkAsNthChild(v, n) 

2535 assert v.children[n] == v2 

2536 return v2 

2537 #@+node:ekr.20080427062528.9: *3* v.Low level methods 

2538 #@+node:ekr.20180709175203.1: *4* v._addCopiedLink 

2539 def _addCopiedLink(self, childIndex: int, parent_v: "VNode") -> None: 

2540 """Adjust links after adding a link to v.""" 

2541 v = self 

2542 v.context.frame.tree.generation += 1 

2543 parent_v.childrenModified() # For a plugin. 

2544 # Update parent_v.children & v.parents. 

2545 parent_v.children.insert(childIndex, v) 

2546 v.parents.append(parent_v) 

2547 # Set zodb changed flags. 

2548 v._p_changed = True 

2549 parent_v._p_changed = True 

2550 #@+node:ekr.20090706110836.6135: *4* v._addLink & _addParentLinks 

2551 def _addLink(self, childIndex: int, parent_v: "VNode") -> None: 

2552 """Adjust links after adding a link to v.""" 

2553 v = self 

2554 v.context.frame.tree.generation += 1 

2555 parent_v.childrenModified() # For a plugin. 

2556 # Update parent_v.children & v.parents. 

2557 parent_v.children.insert(childIndex, v) 

2558 v.parents.append(parent_v) 

2559 # Set zodb changed flags. 

2560 v._p_changed = True 

2561 parent_v._p_changed = True 

2562 # If v has only one parent, we adjust all 

2563 # the parents links in the descendant tree. 

2564 # This handles clones properly when undoing a delete. 

2565 if len(v.parents) == 1: 

2566 for child in v.children: 

2567 child._addParentLinks(parent=v) 

2568 #@+node:ekr.20090804184658.6129: *5* v._addParentLinks 

2569 def _addParentLinks(self, parent: "VNode") -> None: 

2570 

2571 v = self 

2572 v.parents.append(parent) 

2573 if len(v.parents) == 1: 

2574 for child in v.children: 

2575 child._addParentLinks(parent=v) 

2576 #@+node:ekr.20090804184658.6128: *4* v._cutLink & _cutParentLinks 

2577 def _cutLink(self, childIndex: int, parent_v: "VNode") -> None: 

2578 """Adjust links after cutting a link to v.""" 

2579 v = self 

2580 v.context.frame.tree.generation += 1 

2581 parent_v.childrenModified() 

2582 assert parent_v.children[childIndex] == v 

2583 del parent_v.children[childIndex] 

2584 if parent_v in v.parents: 

2585 try: 

2586 v.parents.remove(parent_v) 

2587 except ValueError: # pragma: no cover 

2588 g.internalError(f"{parent_v} not in parents of {v}") 

2589 g.trace('v.parents:') 

2590 g.printObj(v.parents) 

2591 v._p_changed = True 

2592 parent_v._p_changed = True 

2593 # If v has no more parents, we adjust all 

2594 # the parent links in the descendant tree. 

2595 # This handles clones properly when deleting a tree. 

2596 if not v.parents: 

2597 for child in v.children: 

2598 child._cutParentLinks(parent=v) 

2599 #@+node:ekr.20090804190529.6133: *5* v._cutParentLinks 

2600 def _cutParentLinks(self, parent: "VNode") -> None: 

2601 

2602 v = self 

2603 v.parents.remove(parent) 

2604 if not v.parents: 

2605 for child in v.children: 

2606 child._cutParentLinks(parent=v) 

2607 #@+node:ekr.20180709064515.1: *4* v._deleteAllChildren 

2608 def _deleteAllChildren(self) -> None: 

2609 """ 

2610 Delete all children of self. 

2611 

2612 This is a low-level method, used by the read code. 

2613 It is not intended as a general replacement for p.doDelete(). 

2614 """ 

2615 v = self 

2616 for v2 in v.children: 

2617 try: 

2618 v2.parents.remove(v) 

2619 except ValueError: # pragma: no cover 

2620 g.internalError(f"{v} not in parents of {v2}") 

2621 g.trace('v2.parents:') 

2622 g.printObj(v2.parents) 

2623 v.children = [] 

2624 #@+node:ekr.20031218072017.3425: *4* v._linkAsNthChild 

2625 def _linkAsNthChild(self, parent_v: "VNode", n: int) -> None: 

2626 """Links self as the n'th child of VNode pv""" 

2627 v = self # The child node. 

2628 v._addLink(n, parent_v) 

2629 #@+node:ekr.20090130065000.1: *3* v.Properties 

2630 #@+node:ekr.20090130114732.5: *4* v.b Property 

2631 def __get_b(self) -> str: 

2632 v = self 

2633 return v.bodyString() 

2634 

2635 def __set_b(self, val: str) -> None: 

2636 v = self 

2637 v.setBodyString(val) 

2638 

2639 b = property( 

2640 __get_b, __set_b, 

2641 doc="VNode body string property") 

2642 #@+node:ekr.20090130125002.1: *4* v.h property 

2643 def __get_h(self) -> str: 

2644 v = self 

2645 return v.headString() 

2646 

2647 def __set_h(self, val: str) -> None: 

2648 v = self 

2649 v.setHeadString(val) 

2650 

2651 h = property( 

2652 __get_h, __set_h, 

2653 doc="VNode headline string property") 

2654 #@+node:ekr.20090130114732.6: *4* v.u Property 

2655 def __get_u(self) -> Dict: 

2656 v = self 

2657 # Wrong: return getattr(v, 'unknownAttributes', {}) 

2658 # It is does not set v.unknownAttributes, which can cause problems. 

2659 if not hasattr(v, 'unknownAttributes'): 

2660 v.unknownAttributes = {} # type:ignore 

2661 return v.unknownAttributes # type:ignore 

2662 

2663 def __set_u(self, val: Any) -> None: 

2664 v = self 

2665 if val is None: 

2666 if hasattr(v, 'unknownAttributes'): 

2667 delattr(v, 'unknownAttributes') 

2668 elif isinstance(val, dict): 

2669 v.unknownAttributes = val # type:ignore 

2670 else: 

2671 raise ValueError # pragma: no cover 

2672 

2673 u = property( 

2674 __get_u, __set_u, 

2675 doc="VNode u property") 

2676 #@+node:ekr.20090215165030.1: *4* v.gnx Property 

2677 def __get_gnx(self) -> str: 

2678 v = self 

2679 return v.fileIndex 

2680 

2681 gnx = property( 

2682 __get_gnx, # __set_gnx, 

2683 doc="VNode gnx property") 

2684 #@-others 

2685vnode = VNode # compatibility. 

2686 

2687#@@beautify 

2688#@-others 

2689#@@language python 

2690#@@tabwidth -4 

2691#@@pagewidth 70 

2692#@-leo