Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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

2#@+leo-ver=5-thin 

3#@+node:ekr.20031218072017.3320: * @file leoNodes.py 

4#@@first 

5"""Leo's fundamental data classes.""" 

6#@+<< imports >> 

7#@+node:ekr.20060904165452.1: ** << imports >> (leoNodes.py) 

8#Transcrypt does not support Python's copy module. 

9import copy 

10import itertools 

11import time 

12import re 

13from typing import Any, Callable, Dict, Generator, List, Optional, Set, Tuple 

14from typing import TYPE_CHECKING 

15from leo.core import leoGlobals as g 

16from leo.core import signal_manager 

17if TYPE_CHECKING: # Always False at runtime. 

18 from leo.core.leoCommands import Commands as Cmdr 

19else: 

20 Cmdr = None 

21#@-<< imports >> 

22#@+others 

23#@+node:ekr.20031218072017.1991: ** class NodeIndices 

24class NodeIndices: 

25 """A class managing global node indices (gnx's).""" 

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( 

51 f"getNewIndex: gnx clash {gnx}\n" 

52 f" v: {v}\n" 

53 f" v2: {v2}") 

54 #@+node:ekr.20150302061758.14: *3* ni.compute_last_index 

55 def compute_last_index(self, c: "Cmdr") -> None: 

56 """Scan the entire leo outline to compute ni.last_index.""" 

57 ni = self 

58 # Partial, experimental, fix for #658. 

59 # Do not change self.lastIndex here! 

60 # self.lastIndex = 0 

61 for v in c.all_unique_nodes(): 

62 gnx = v.fileIndex 

63 if gnx: 

64 id_, t, n = self.scanGnx(gnx) 

65 if t == ni.timeString and n is not None: 

66 try: 

67 n = int(n) # type:ignore 

68 self.lastIndex = max(self.lastIndex, n) # type:ignore 

69 except Exception: 

70 g.es_exception() 

71 self.lastIndex += 1 

72 #@+node:ekr.20200528131303.1: *3* ni.computeNewIndex 

73 def computeNewIndex(self) -> str: 

74 """Return a new gnx.""" 

75 t_s = self.update() 

76 # Updates self.lastTime and self.lastIndex. 

77 gnx = g.toUnicode(f"{self.userId}.{t_s}.{self.lastIndex:d}") 

78 return gnx 

79 #@+node:ekr.20031218072017.1994: *3* ni.get/setDefaultId 

80 # These are used by the FileCommands read/write code. 

81 

82 def getDefaultId(self) -> str: 

83 """Return the id to be used by default in all gnx's""" 

84 return self.defaultId 

85 

86 def setDefaultId(self, theId: str) -> None: 

87 """Set the id to be used by default in all gnx's""" 

88 self.defaultId = theId 

89 #@+node:ekr.20031218072017.1995: *3* ni.getNewIndex 

90 def getNewIndex(self, v: "VNode", cached: bool=False) -> str: 

91 """ 

92 Create a new gnx for v or an empty string if the hold flag is set. 

93 **Important**: the method must allocate a new gnx even if v.fileIndex exists. 

94 """ 

95 if v is None: 

96 g.internalError('getNewIndex: v is None') 

97 return '' 

98 c = v.context 

99 fc = c.fileCommands 

100 t_s = self.update() 

101 # Updates self.lastTime and self.lastIndex. 

102 gnx = g.toUnicode(f"{self.userId}.{t_s}.{self.lastIndex:d}") 

103 v.fileIndex = gnx 

104 self.check_gnx(c, gnx, v) 

105 fc.gnxDict[gnx] = v 

106 return gnx 

107 #@+node:ekr.20150322134954.1: *3* ni.new_vnode_helper 

108 def new_vnode_helper(self, c: "Cmdr", gnx: str, v: "VNode") -> None: 

109 """Handle all gnx-related tasks for VNode.__init__.""" 

110 ni = self 

111 # Special case for the c.hiddenRootNode. This eliminates a hack in c.initObjects. 

112 if not getattr(c, 'fileCommands', None): 

113 assert gnx == 'hidden-root-vnode-gnx' 

114 v.fileIndex = gnx 

115 return 

116 if gnx: 

117 v.fileIndex = gnx 

118 ni.check_gnx(c, gnx, v) 

119 c.fileCommands.gnxDict[gnx] = v 

120 else: 

121 v.fileIndex = ni.getNewIndex(v) 

122 #@+node:ekr.20031218072017.1997: *3* ni.scanGnx 

123 def scanGnx(self, s: str) -> Tuple[str, str, str]: 

124 """Create a gnx from its string representation.""" 

125 if not isinstance(s, str): 

126 g.error("scanGnx: unexpected index type:", type(s), '', s) 

127 return None, None, None 

128 s = s.strip() 

129 theId, t, n = None, None, None 

130 i, theId = g.skip_to_char(s, 0, '.') 

131 if g.match(s, i, '.'): 

132 i, t = g.skip_to_char(s, i + 1, '.') 

133 if g.match(s, i, '.'): 

134 i, n = g.skip_to_char(s, i + 1, '.') 

135 # Use self.defaultId for missing id entries. 

136 if not theId: 

137 theId = self.defaultId 

138 return theId, t, n 

139 #@+node:ekr.20031218072017.1998: *3* ni.setTimeStamp 

140 def setTimestamp(self) -> None: 

141 """Set the timestamp string to be used by getNewIndex until further notice""" 

142 self.timeString = time.strftime( 

143 "%Y%m%d%H%M%S", # Help comparisons; avoid y2k problems. 

144 time.localtime()) 

145 

146 setTimeStamp = setTimestamp 

147 #@+node:ekr.20141015035853.18304: *3* ni.tupleToString 

148 def tupleToString(self, aTuple: Tuple) -> str: 

149 """ 

150 Convert a gnx tuple returned by scanGnx 

151 to its string representation. 

152 """ 

153 theId, t, n = aTuple 

154 # This logic must match the existing logic so that 

155 # previously written gnx's can be found. 

156 if n in (None, 0, '',): 

157 s = f"{theId}.{t}" 

158 else: 

159 s = f"{theId}.{t}.{n}" 

160 return g.toUnicode(s) 

161 #@+node:ekr.20150321161305.13: *3* ni.update 

162 def update(self) -> str: 

163 """Update self.timeString and self.lastIndex""" 

164 t_s = time.strftime("%Y%m%d%H%M%S", time.localtime()) 

165 if self.timeString == t_s: 

166 self.lastIndex += 1 

167 else: 

168 self.lastIndex = 1 

169 self.timeString = t_s 

170 return t_s 

171 #@+node:ekr.20141023110422.4: *3* ni.updateLastIndex 

172 def updateLastIndex(self, gnx: str) -> None: 

173 """Update ni.lastIndex if the gnx affects it.""" 

174 id_, t, n = self.scanGnx(gnx) 

175 # pylint: disable=literal-comparison 

176 # Don't you dare touch this code to keep pylint happy. 

177 if not id_ or (n != 0 and not n): 

178 return # the gnx is not well formed or n in ('',None) 

179 if id_ == self.userId and t == self.timeString: 

180 try: 

181 n2 = int(n) 

182 if n2 > self.lastIndex: 

183 self.lastIndex = n2 

184 g.trace(gnx, '-->', n2) 

185 except Exception: 

186 g.trace('can not happen', repr(n)) 

187 #@-others 

188#@+node:ekr.20031218072017.889: ** class Position 

189#@+<< about the position class >> 

190#@+node:ekr.20031218072017.890: *3* << about the position class >> 

191#@@language rest 

192#@+at 

193# A position marks the spot in a tree traversal. A position p consists of a VNode 

194# p.v, a child index p._childIndex, and a stack of tuples (v,childIndex), one for 

195# each ancestor **at the spot in tree traversal. Positions p has a unique set of 

196# parents. 

197# 

198# The p.moveToX methods may return a null (invalid) position p with p.v = None. 

199# 

200# The tests "if p" or "if not p" are the _only_ correct way to test whether a 

201# position p is valid. In particular, tests like "if p is None" or "if p is not 

202# None" will not work properly. 

203#@-<< about the position class >> 

204# Positions should *never* be saved by the ZOBD. 

205 

206 

207class Position: 

208 

209 __slots__ = [ 

210 '_childIndex', 'stack', 'v', 

211 # 

212 # EKR: The following fields are deprecated, 

213 # as are the PosList class, c.find_h and c.find_b. 

214 # 

215 'matchiter', # for c.find_b and quicksearch.py. 

216 'mo', # for c.find_h 

217 ] 

218 

219 #@+others 

220 #@+node:ekr.20040228094013: *3* p.ctor & other special methods... 

221 #@+node:ekr.20080920052058.3: *4* p.__eq__ & __ne__ 

222 def __eq__(self, p2: Any) -> bool: # Use Any, not Position. 

223 """Return True if two positions are equivalent.""" 

224 p1 = self 

225 # Don't use g.trace: it might call p.__eq__ or p.__ne__. 

226 if not isinstance(p2, Position): 

227 return False 

228 if p2 is None or p2.v is None: 

229 return p1.v is None 

230 return (p1.v == p2.v and 

231 p1._childIndex == p2._childIndex and 

232 p1.stack == p2.stack) 

233 

234 def __ne__(self, p2: Any) -> bool: # Use Any, not Position. 

235 """Return True if two postions are not equivalent.""" 

236 return not self.__eq__(p2) 

237 #@+node:ekr.20080416161551.190: *4* p.__init__ 

238 def __init__(self, v: "VNode", childIndex: int=0, stack: Optional[List]=None) -> None: 

239 """Create a new position with the given childIndex and parent stack.""" 

240 self._childIndex = childIndex 

241 self.v = v 

242 # Stack entries are tuples (v, childIndex). 

243 if stack: 

244 self.stack = stack[:] # Creating a copy here is safest and best. 

245 else: 

246 self.stack = [] 

247 g.app.positions += 1 

248 #@+node:ekr.20091210082012.6230: *4* p.__ge__ & __le__& __lt__ 

249 def __ge__(self, other: Any) -> bool: 

250 return self.__eq__(other) or self.__gt__(other) 

251 

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

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

254 

255 def __lt__(self, other: Any) -> bool: 

256 return not self.__eq__(other) and not self.__gt__(other) 

257 #@+node:ekr.20091210082012.6233: *4* p.__gt__ 

258 def __gt__(self, other: Any) -> bool: 

259 """Return True if self appears after other in outline order.""" 

260 stack1, stack2 = self.stack, other.stack 

261 n1, n2 = len(stack1), len(stack2) 

262 n = min(n1, n2) 

263 # Compare the common part of the stacks. 

264 for item1, item2 in zip(stack1, stack2): 

265 v1, x1 = item1 

266 v2, x2 = item2 

267 if x1 > x2: 

268 return True 

269 if x1 < x2: 

270 return False 

271 # Finish the comparison. 

272 if n1 == n2: 

273 x1, x2 = self._childIndex, other._childIndex 

274 return x1 > x2 

275 if n1 < n2: 

276 x1 = self._childIndex 

277 v2, x2 = other.stack[n] 

278 return x1 > x2 

279 # n1 > n2 

280 # 2011/07/28: Bug fix suggested by SegundoBob. 

281 x1 = other._childIndex 

282 v2, x2 = self.stack[n] 

283 return x2 >= x1 

284 #@+node:ekr.20040117173448: *4* p.__nonzero__ & __bool__ 

285 def __bool__(self) -> bool: 

286 """ 

287 Return True if a position is valid. 

288 

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

290 whether a position p is valid. 

291 

292 Tests like 'if p is None' or 'if p is not None' will not work properly. 

293 """ 

294 return self.v is not None 

295 #@+node:ekr.20040301205720: *4* p.__str__ and p.__repr__ 

296 def __str__(self) -> str: 

297 p = self 

298 if p.v: 

299 return ( 

300 "<" 

301 f"pos {id(p)} " 

302 f"childIndex: {p._childIndex} " 

303 f"lvl: {p.level()} " 

304 f"key: {p.key()} " 

305 f"{p.h}" 

306 ">" 

307 ) 

308 return f"<pos {id(p)} [{len(p.stack)}] None>" 

309 

310 __repr__ = __str__ 

311 #@+node:ekr.20061006092649: *4* p.archivedPosition 

312 def archivedPosition(self, root_p: Optional["Position"]=None) -> List[int]: 

313 """Return a representation of a position suitable for use in .leo files.""" 

314 p = self 

315 if root_p is None: 

316 aList = [z._childIndex for z in p.self_and_parents()] 

317 else: 

318 aList = [] 

319 for z in p.self_and_parents(copy=False): 

320 if z == root_p: 

321 aList.append(0) 

322 break 

323 else: 

324 aList.append(z._childIndex) 

325 aList.reverse() 

326 return aList 

327 #@+node:ekr.20040310153624: *4* p.dump 

328 def dumpLink(self, link: Optional[str]) -> str: 

329 return link if link else "<none>" 

330 

331 def dump(self, label: str="") -> None: 

332 p = self 

333 if p.v: 

334 p.v.dump() # Don't print a label 

335 #@+node:ekr.20080416161551.191: *4* p.key & p.sort_key & __hash__ 

336 def key(self) -> str: 

337 p = self 

338 # For unified nodes we must include a complete key, 

339 # so we can distinguish between clones. 

340 result = [] 

341 for z in p.stack: 

342 v, childIndex = z 

343 result.append(f"{id(v)}:{childIndex}") 

344 result.append(f"{id(p.v)}:{p._childIndex}") 

345 return '.'.join(result) 

346 

347 def sort_key(self, p: "Position") -> List[int]: 

348 return [int(s.split(':')[1]) for s in p.key().split('.')] 

349 

350 # Positions should *not* be hashable. 

351 # 

352 # From https://docs.python.org/3/reference/datamodel.html#object.__hash__ 

353 # 

354 # If a class defines mutable objects and implements an __eq__() method, it 

355 # should not implement __hash__(), since the implementation of hashable 

356 # collections requires that a key’s hash value is immutable (if the object’s 

357 # hash value changes, it will be in the wrong hash bucket). 

358 

359 # #1557: To keep mypy happy, don't define __hash__ at all. 

360 # __hash__ = None 

361 #@+node:ekr.20040315023430: *3* p.File Conversion 

362 #@+at 

363 # - convertTreeToString and moreHead can't be VNode methods because they uses level(). 

364 # - moreBody could be anywhere: it may as well be a postion method. 

365 #@+node:ekr.20040315023430.1: *4* p.convertTreeToString 

366 def convertTreeToString(self) -> str: 

367 """Convert a positions suboutline to a string in MORE format.""" 

368 p = self 

369 level1 = p.level() 

370 array = [] 

371 for p in p.self_and_subtree(copy=False): 

372 array.append(p.moreHead(level1) + '\n') 

373 body = p.moreBody() 

374 if body: 

375 array.append(body + '\n') 

376 return ''.join(array) 

377 #@+node:ekr.20040315023430.2: *4* p.moreHead 

378 def moreHead(self, firstLevel: int, useVerticalBar: bool=False) -> str: 

379 """Return the headline string in MORE format.""" 

380 # useVerticalBar is unused, but it would be useful in over-ridden methods. 

381 p = self 

382 level = self.level() - firstLevel 

383 plusMinus = "+" if p.hasChildren() else "-" 

384 pad = '\t' * level 

385 return f"{pad}{plusMinus} {p.h}" 

386 #@+node:ekr.20040315023430.3: *4* p.moreBody 

387 #@@language rest 

388 #@+at 

389 # + test line 

390 # - test line 

391 # \ test line 

392 # test line + 

393 # test line - 

394 # test line \ 

395 # More lines... 

396 #@@c 

397 #@@language python 

398 

399 def moreBody(self) -> str: 

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

401 

402 Inserts a backslash before any leading plus, minus or backslash.""" 

403 p = self 

404 array = [] 

405 lines = p.b.split('\n') 

406 for s in lines: 

407 i = g.skip_ws(s, 0) 

408 if i < len(s) and s[i] in ('+', '-', '\\'): 

409 s = s[:i] + '\\' + s[i:] 

410 array.append(s) 

411 return '\n'.join(array) 

412 #@+node:ekr.20091001141621.6060: *3* p.generators 

413 #@+node:ekr.20091001141621.6055: *4* p.children 

414 def children(self, copy: bool=True) -> Generator: 

415 """Yield all child positions of p.""" 

416 p = self 

417 p = p.firstChild() 

418 while p: 

419 yield p.copy() if copy else p 

420 p.moveToNext() 

421 

422 # Compatibility with old code... 

423 

424 children_iter = children 

425 #@+node:ekr.20091002083910.6102: *4* p.following_siblings 

426 def following_siblings(self, copy: bool=True) -> Generator: 

427 """Yield all siblings positions that follow p, not including p.""" 

428 p = self 

429 p = p.next() # pylint: disable=not-callable 

430 while p: 

431 yield p.copy() if copy else p 

432 p.moveToNext() 

433 

434 # Compatibility with old code... 

435 

436 following_siblings_iter = following_siblings 

437 #@+node:ekr.20161120105707.1: *4* p.nearest_roots 

438 def nearest_roots(self, copy: bool=True, predicate: Optional[Callable]=None) -> Generator: 

439 """ 

440 A generator yielding all the root positions "near" p1 = self that 

441 satisfy the given predicate. p.isAnyAtFileNode is the default 

442 predicate. 

443 

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

445 generator yields just that root. 

446 

447 Otherwise, the generator yields all nodes in p.subtree() that satisfy 

448 the predicate. Once a root is found, the generator skips its subtree. 

449 """ 

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

451 return p.isAnyAtFileNode() 

452 

453 the_predicate = predicate or default_predicate 

454 

455 # First, look up the tree. 

456 p1 = self 

457 for p in p1.self_and_parents(copy=False): 

458 if the_predicate(p): 

459 yield p.copy() if copy else p 

460 return 

461 # Next, look for all .md files in the tree. 

462 after = p1.nodeAfterTree() 

463 p = p1 

464 while p and p != after: 

465 if the_predicate(p): 

466 yield p.copy() if copy else p 

467 p.moveToNodeAfterTree() 

468 else: 

469 p.moveToThreadNext() 

470 #@+node:ekr.20161120163203.1: *4* p.nearest_unique_roots (aka p.nearest) 

471 def nearest_unique_roots(self, copy: bool=True, predicate: Callable=None) -> Generator: 

472 """ 

473 A generator yielding all unique root positions "near" p1 = self that 

474 satisfy the given predicate. p.isAnyAtFileNode is the default 

475 predicate. 

476 

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

478 generator yields just that root. 

479 

480 Otherwise, the generator yields all unique nodes in p.subtree() that 

481 satisfy the predicate. Once a root is found, the generator skips its 

482 subtree. 

483 """ 

484 

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

486 return p.isAnyAtFileNode() 

487 

488 the_predicate = predicate or default_predicate 

489 

490 # First, look up the tree. 

491 p1 = self 

492 for p in p1.self_and_parents(copy=False): 

493 if the_predicate(p): 

494 yield p.copy() if copy else p 

495 return 

496 # Next, look for all unique .md files in the tree. 

497 seen = set() 

498 after = p1.nodeAfterTree() 

499 p = p1 

500 while p and p != after: 

501 if the_predicate(p): 

502 if p.v not in seen: 

503 seen.add(p.v) 

504 yield p.copy() if copy else p 

505 p.moveToNodeAfterTree() 

506 else: 

507 p.moveToThreadNext() 

508 

509 nearest = nearest_unique_roots 

510 #@+node:ekr.20091002083910.6104: *4* p.nodes 

511 def nodes(self) -> Generator: 

512 """Yield p.v and all vnodes in p's subtree.""" 

513 p = self 

514 p = p.copy() 

515 after = p.nodeAfterTree() 

516 while p and p != after: # bug fix: 2013/10/12 

517 yield p.v 

518 p.moveToThreadNext() 

519 

520 # Compatibility with old code. 

521 

522 vnodes_iter = nodes 

523 #@+node:ekr.20091001141621.6058: *4* p.parents 

524 def parents(self, copy: bool=True) -> Generator: 

525 """Yield all parent positions of p.""" 

526 p = self 

527 p = p.parent() 

528 while p: 

529 yield p.copy() if copy else p 

530 p.moveToParent() 

531 

532 # Compatibility with old code... 

533 

534 parents_iter = parents 

535 #@+node:ekr.20091002083910.6099: *4* p.self_and_parents 

536 def self_and_parents(self, copy: bool=True) -> Generator: 

537 """Yield p and all parent positions of p.""" 

538 p = self 

539 p = p.copy() 

540 while p: 

541 yield p.copy() if copy else p 

542 p.moveToParent() 

543 

544 # Compatibility with old code... 

545 

546 self_and_parents_iter = self_and_parents 

547 #@+node:ekr.20091001141621.6057: *4* p.self_and_siblings 

548 def self_and_siblings(self, copy: bool=True) -> Generator: 

549 """Yield all sibling positions of p including p.""" 

550 p = self 

551 p = p.copy() 

552 while p.hasBack(): 

553 p.moveToBack() 

554 while p: 

555 yield p.copy() if copy else p 

556 p.moveToNext() 

557 

558 # Compatibility with old code... 

559 

560 self_and_siblings_iter = self_and_siblings 

561 #@+node:ekr.20091001141621.6066: *4* p.self_and_subtree 

562 def self_and_subtree(self, copy: bool=True) -> Generator: 

563 """Yield p and all positions in p's subtree.""" 

564 p = self 

565 p = p.copy() 

566 after = p.nodeAfterTree() 

567 while p and p != after: 

568 yield p.copy() if copy else p 

569 p.moveToThreadNext() 

570 

571 # Compatibility with old code... 

572 

573 self_and_subtree_iter = self_and_subtree 

574 #@+node:ekr.20091001141621.6056: *4* p.subtree 

575 def subtree(self, copy: bool=True) -> Generator: 

576 """Yield all positions in p's subtree, but not p.""" 

577 p = self 

578 p = p.copy() 

579 after = p.nodeAfterTree() 

580 p.moveToThreadNext() 

581 while p and p != after: 

582 yield p.copy() if copy else p 

583 p.moveToThreadNext() 

584 

585 # Compatibility with old code... 

586 

587 subtree_iter = subtree 

588 #@+node:ekr.20091002083910.6105: *4* p.unique_nodes 

589 def unique_nodes(self) -> Generator: 

590 """Yield p.v and all unique vnodes in p's subtree.""" 

591 p = self 

592 seen = set() 

593 for p in p.self_and_subtree(copy=False): 

594 if p.v not in seen: 

595 seen.add(p.v) 

596 yield p.v 

597 

598 # Compatibility with old code. 

599 

600 unique_vnodes_iter = unique_nodes 

601 #@+node:ekr.20091002083910.6103: *4* p.unique_subtree 

602 def unique_subtree(self) -> Generator: 

603 """Yield p and all other unique positions in p's subtree.""" 

604 p = self 

605 seen = set() 

606 for p in p.subtree(): 

607 if p.v not in seen: 

608 seen.add(p.v) 

609 # Fixed bug 1255208: p.unique_subtree returns vnodes, not positions. 

610 yield p.copy() if copy else p 

611 

612 # Compatibility with old code... 

613 

614 subtree_with_unique_vnodes_iter = unique_subtree 

615 #@+node:ekr.20040306212636: *3* p.Getters 

616 #@+node:ekr.20040306210951: *4* p.VNode proxies 

617 #@+node:ekr.20040306211032: *5* p.Comparisons 

618 def anyAtFileNodeName(self) -> str: 

619 return self.v.anyAtFileNodeName() 

620 

621 def atAutoNodeName(self) -> str: 

622 return self.v.atAutoNodeName() 

623 

624 def atCleanNodeName(self) -> str: 

625 return self.v.atCleanNodeName() 

626 

627 def atEditNodeName(self) -> str: 

628 return self.v.atEditNodeName() 

629 

630 def atFileNodeName(self) -> str: 

631 return self.v.atFileNodeName() 

632 

633 def atNoSentinelsFileNodeName(self) -> str: 

634 return self.v.atNoSentinelsFileNodeName() 

635 

636 def atShadowFileNodeName(self) -> str: 

637 return self.v.atShadowFileNodeName() 

638 

639 def atSilentFileNodeName(self) -> str: 

640 return self.v.atSilentFileNodeName() 

641 

642 def atThinFileNodeName(self) -> str: 

643 return self.v.atThinFileNodeName() 

644 

645 # New names, less confusing 

646 atNoSentFileNodeName = atNoSentinelsFileNodeName 

647 atAsisFileNodeName = atSilentFileNodeName 

648 

649 def isAnyAtFileNode(self) -> bool: 

650 return self.v.isAnyAtFileNode() 

651 

652 def isAtAllNode(self) -> bool: 

653 return self.v.isAtAllNode() 

654 

655 def isAtAutoNode(self) -> bool: 

656 return self.v.isAtAutoNode() 

657 

658 def isAtAutoRstNode(self) -> bool: 

659 return self.v.isAtAutoRstNode() 

660 

661 def isAtCleanNode(self) -> bool: 

662 return self.v.isAtCleanNode() 

663 

664 def isAtEditNode(self) -> bool: 

665 return self.v.isAtEditNode() 

666 

667 def isAtFileNode(self) -> bool: 

668 return self.v.isAtFileNode() 

669 

670 def isAtIgnoreNode(self) -> bool: 

671 return self.v.isAtIgnoreNode() 

672 

673 def isAtNoSentinelsFileNode(self) -> bool: 

674 return self.v.isAtNoSentinelsFileNode() 

675 

676 def isAtOthersNode(self) -> bool: 

677 return self.v.isAtOthersNode() 

678 

679 def isAtRstFileNode(self) -> bool: 

680 return self.v.isAtRstFileNode() 

681 

682 def isAtSilentFileNode(self) -> bool: 

683 return self.v.isAtSilentFileNode() 

684 

685 def isAtShadowFileNode(self) -> bool: 

686 return self.v.isAtShadowFileNode() 

687 

688 def isAtThinFileNode(self) -> bool: 

689 return self.v.isAtThinFileNode() 

690 

691 # New names, less confusing: 

692 isAtNoSentFileNode = isAtNoSentinelsFileNode 

693 isAtAsisFileNode = isAtSilentFileNode 

694 

695 # Utilities. 

696 

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

698 return self.v.matchHeadline(pattern) 

699 #@+node:ekr.20040306220230: *5* p.Headline & body strings 

700 def bodyString(self) -> str: 

701 return self.v.bodyString() 

702 

703 def headString(self) -> str: 

704 return self.v.headString() 

705 #@+node:ekr.20040306214401: *5* p.Status bits 

706 def isDirty(self) -> bool: 

707 return self.v.isDirty() 

708 

709 def isMarked(self) -> bool: 

710 return self.v.isMarked() 

711 

712 def isOrphan(self) -> bool: 

713 return self.v.isOrphan() 

714 

715 def isSelected(self) -> bool: 

716 return self.v.isSelected() 

717 

718 def isTopBitSet(self) -> bool: 

719 return self.v.isTopBitSet() 

720 

721 def isVisited(self) -> bool: 

722 return self.v.isVisited() 

723 

724 def status(self) -> int: 

725 return self.v.status() 

726 #@+node:ekr.20040306214240.2: *4* p.children & parents 

727 #@+node:ekr.20040326064330: *5* p.childIndex 

728 # This used to be time-critical code. 

729 

730 def childIndex(self) -> int: 

731 p = self 

732 return p._childIndex 

733 #@+node:ekr.20040323160302: *5* p.directParents 

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

735 return self.v.directParents() 

736 #@+node:ekr.20040306214240.3: *5* p.hasChildren & p.numberOfChildren 

737 def hasChildren(self) -> bool: 

738 p = self 

739 return len(p.v.children) > 0 

740 

741 hasFirstChild = hasChildren 

742 

743 def numberOfChildren(self) -> int: 

744 p = self 

745 return len(p.v.children) 

746 #@+node:ekr.20031218072017.915: *4* p.getX & VNode compatibility traversal routines 

747 # These methods are useful abbreviations. 

748 # Warning: they make copies of positions, so they should be used _sparingly_ 

749 

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

751 return self.copy().moveToBack() 

752 

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

754 return self.copy().moveToFirstChild() 

755 

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

757 return self.copy().moveToLastChild() 

758 

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

760 return self.copy().moveToLastNode() 

761 

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

763 return self.copy().moveToNext() 

764 

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

766 return self.copy().moveToNodeAfterTree() 

767 

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

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

770 

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

772 return self.copy().moveToParent() 

773 

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

775 return self.copy().moveToThreadBack() 

776 

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

778 return self.copy().moveToThreadNext() 

779 

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

781 

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

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

784 

785 def getVisNext(self, c: "Cmdr") -> "Position": 

786 return self.copy().moveToVisNext(c) 

787 # These are efficient enough now that iterators are the normal way to traverse the tree! 

788 back = getBack 

789 firstChild = getFirstChild 

790 lastChild = getLastChild 

791 lastNode = getLastNode 

792 # lastVisible = getLastVisible # New in 4.2 (was in tk tree code). 

793 next = getNext 

794 nodeAfterTree = getNodeAfterTree 

795 nthChild = getNthChild 

796 parent = getParent 

797 threadBack = getThreadBack 

798 threadNext = getThreadNext 

799 visBack = getVisBack 

800 visNext = getVisNext 

801 # New in Leo 4.4.3: 

802 hasVisBack = visBack 

803 hasVisNext = visNext 

804 #@+node:tbrown.20111010104549.26758: *4* p.get_UNL 

805 def get_UNL(self) -> str: 

806 """ 

807 Return a UNL representing a clickable link. 

808  

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

810  

811 - unl: // 

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

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

814  

815 New in Leo 6.6: 

816 - Always add unl: // and file name. 

817 - Never translate '-->' to '--%3E'. 

818 - Never generate child indices. 

819 """ 

820 return ( 

821 'unl://' 

822 + self.v.context.fileName() + '#' 

823 + '-->'.join(list(reversed([z.h for z in self.self_and_parents(copy=False)]))) 

824 ) 

825 #@+node:ekr.20080416161551.192: *4* p.hasBack/Next/Parent/ThreadBack 

826 def hasBack(self) -> bool: 

827 p = self 

828 return bool(p.v and p._childIndex > 0) 

829 

830 def hasNext(self) -> bool: 

831 p = self 

832 try: 

833 parent_v = p._parentVnode() 

834 # Returns None if p.v is None. 

835 return p.v and parent_v and p._childIndex + 1 < len(parent_v.children) # type:ignore 

836 except Exception: 

837 g.trace('*** Unexpected exception') 

838 g.es_exception() 

839 return None 

840 

841 def hasParent(self) -> bool: 

842 p = self 

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

844 

845 def hasThreadBack(self) -> bool: 

846 p = self 

847 return bool(p.hasParent() or p.hasBack()) 

848 # Much cheaper than computing the actual value. 

849 #@+node:ekr.20080416161551.193: *5* hasThreadNext (the only complex hasX method) 

850 def hasThreadNext(self) -> bool: 

851 p = self 

852 if not p.v: 

853 return False 

854 if p.hasChildren() or p.hasNext(): 

855 return True 

856 n = len(p.stack) - 1 

857 while n >= 0: 

858 v, childIndex = p.stack[n] 

859 # See how many children v's parent has. 

860 if n == 0: 

861 parent_v = v.context.hiddenRootNode 

862 else: 

863 parent_v, junk = p.stack[n - 1] 

864 if len(parent_v.children) > childIndex + 1: 

865 # v has a next sibling. 

866 return True 

867 n -= 1 

868 return False 

869 #@+node:ekr.20060920203352: *4* p.findRootPosition 

870 def findRootPosition(self) -> "Position": 

871 # 2011/02/25: always use c.rootPosition 

872 p = self 

873 c = p.v.context 

874 return c.rootPosition() 

875 #@+node:ekr.20080416161551.194: *4* p.isAncestorOf 

876 def isAncestorOf(self, p2: "Position") -> bool: 

877 """Return True if p is one of the direct ancestors of p2.""" 

878 p = self 

879 c = p.v.context 

880 if not c.positionExists(p2): 

881 return False 

882 for z in p2.stack: 

883 # 2013/12/25: bug fix: test childIndices. 

884 # This is required for the new per-position expansion scheme. 

885 parent_v, parent_childIndex = z 

886 if parent_v == p.v and parent_childIndex == p._childIndex: 

887 return True 

888 return False 

889 #@+node:ekr.20040306215056: *4* p.isCloned 

890 def isCloned(self) -> bool: 

891 p = self 

892 return p.v.isCloned() 

893 #@+node:ekr.20040307104131.2: *4* p.isRoot 

894 def isRoot(self) -> bool: 

895 p = self 

896 return not p.hasParent() and not p.hasBack() 

897 #@+node:ekr.20080416161551.196: *4* p.isVisible 

898 def isVisible(self, c: "Cmdr") -> bool: 

899 """Return True if p is visible in c's outline.""" 

900 p = self 

901 

902 def visible(p: "Position", root: Optional["Position"]=None) -> bool: 

903 for parent in p.parents(copy=False): 

904 if parent and parent == root: 

905 # #12. 

906 return True 

907 if not c.shouldBeExpanded(parent): 

908 return False 

909 return True 

910 

911 if c.hoistStack: # Chapters are a form of hoist. 

912 root = c.hoistStack[-1].p 

913 if p == root: 

914 # #12. 

915 return True 

916 return root.isAncestorOf(p) and visible(p, root=root) 

917 for root in c.rootPosition().self_and_siblings(copy=False): 

918 if root == p or root.isAncestorOf(p): 

919 return visible(p) 

920 return False 

921 #@+node:ekr.20080416161551.197: *4* p.level & simpleLevel 

922 def level(self) -> int: 

923 """Return the number of p's parents.""" 

924 p = self 

925 return len(p.stack) if p.v else 0 

926 

927 simpleLevel = level 

928 #@+node:ekr.20111005152227.15566: *4* p.positionAfterDeletedTree 

929 def positionAfterDeletedTree(self) -> "Position": 

930 """Return the position corresponding to p.nodeAfterTree() after this node is 

931 deleted. This will be p.nodeAfterTree() unless p.next() exists. 

932 

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

934 traversal. The pattern is:: 

935 

936 p = c.rootPosition() 

937 while p: 

938 if <delete p?>: 

939 next = p.positionAfterDeletedTree() 

940 p.doDelete() 

941 p = next 

942 else: 

943 p.moveToThreadNext() 

944 

945 This method also allows scripts to *move* nodes during a traversal, **provided** 

946 that nodes are moved to a "safe" spot so that moving a node does not change the 

947 position of any other nodes. 

948 

949 For example, the move-marked-nodes command first creates a **move node**, called 

950 'Clones of marked nodes'. All moved nodes become children of this move node. 

951 **Inserting** these nodes as children of the "move node" does not change the 

952 positions of other nodes. **Deleting** these nodes *may* change the position of 

953 nodes, but the pattern above handles this complication cleanly. 

954 """ 

955 p = self 

956 next = p.next() # pylint: disable=not-callable 

957 if next: 

958 # The new position will be the same as p, except for p.v. 

959 p = p.copy() 

960 p.v = next.v 

961 return p 

962 return p.nodeAfterTree() 

963 #@+node:shadow.20080825171547.2: *4* p.textOffset 

964 def textOffset(self) -> Optional[int]: 

965 """ 

966 Return the fcol offset of self. 

967 Return None if p is has no ancestor @<file> node. 

968 http://tinyurl.com/5nescw 

969 """ 

970 p = self 

971 found, offset = False, 0 

972 for p in p.self_and_parents(copy=False): 

973 if p.isAnyAtFileNode(): 

974 # Ignore parent of @<file> node. 

975 found = True 

976 break 

977 parent = p.parent() 

978 if not parent: 

979 break 

980 # If p is a section definition, search the parent for the reference. 

981 # Otherwise, search the parent for @others. 

982 h = p.h.strip() 

983 i = h.find('<<') 

984 j = h.find('>>') 

985 target = h[i : j + 2] if -1 < i < j else '@others' 

986 for s in parent.b.split('\n'): 

987 if s.find(target) > -1: 

988 offset += g.skip_ws(s, 0) 

989 break 

990 return offset if found else None 

991 #@+node:ekr.20080423062035.1: *3* p.Low level methods 

992 # These methods are only for the use of low-level code 

993 # in leoNodes.py, leoFileCommands.py and leoUndo.py. 

994 #@+node:ekr.20080427062528.4: *4* p._adjustPositionBeforeUnlink 

995 def _adjustPositionBeforeUnlink(self, p2: "Position") -> None: 

996 """Adjust position p before unlinking p2.""" 

997 # p will change if p2 is a previous sibling of p or 

998 # p2 is a previous sibling of any ancestor of p. 

999 p = self 

1000 sib = p.copy() 

1001 # A special case for previous siblings. 

1002 # Adjust p._childIndex, not the stack's childIndex. 

1003 while sib.hasBack(): 

1004 sib.moveToBack() 

1005 if sib == p2: 

1006 p._childIndex -= 1 

1007 return 

1008 # Adjust p's stack. 

1009 stack: List[Tuple[VNode, int]] = [] 

1010 changed, i = False, 0 

1011 while i < len(p.stack): 

1012 v, childIndex = p.stack[i] 

1013 p3 = Position(v=v, childIndex=childIndex, stack=stack[:i]) 

1014 while p3: 

1015 if p2 == p3: 

1016 # 2011/02/25: compare full positions, not just vnodes. 

1017 # A match with the to-be-moved node. 

1018 stack.append((v, childIndex - 1),) 

1019 changed = True 

1020 break # terminate only the inner loop. 

1021 p3.moveToBack() 

1022 else: 

1023 stack.append((v, childIndex),) 

1024 i += 1 

1025 if changed: 

1026 p.stack = stack 

1027 #@+node:ekr.20080416161551.214: *4* p._linkAfter 

1028 def _linkAfter(self, p_after: "Position") -> None: 

1029 """Link self after p_after.""" 

1030 p = self 

1031 parent_v = p_after._parentVnode() 

1032 p.stack = p_after.stack[:] 

1033 p._childIndex = p_after._childIndex + 1 

1034 child = p.v 

1035 n = p_after._childIndex + 1 

1036 child._addLink(n, parent_v) 

1037 #@+node:ekr.20180709181718.1: *4* p._linkCopiedAfter 

1038 def _linkCopiedAfter(self, p_after: "Position") -> None: 

1039 """Link self, a newly copied tree, after p_after.""" 

1040 p = self 

1041 parent_v = p_after._parentVnode() 

1042 p.stack = p_after.stack[:] 

1043 p._childIndex = p_after._childIndex + 1 

1044 child = p.v 

1045 n = p_after._childIndex + 1 

1046 child._addCopiedLink(n, parent_v) 

1047 #@+node:ekr.20080416161551.215: *4* p._linkAsNthChild 

1048 def _linkAsNthChild(self, parent: "Position", n: int) -> None: 

1049 """Link self as the n'th child of the parent.""" 

1050 p = self 

1051 parent_v = parent.v 

1052 p.stack = parent.stack[:] 

1053 p.stack.append((parent_v, parent._childIndex),) 

1054 p._childIndex = n 

1055 child = p.v 

1056 child._addLink(n, parent_v) 

1057 #@+node:ekr.20180709180140.1: *4* p._linkCopiedAsNthChild 

1058 def _linkCopiedAsNthChild(self, parent: "Position", n: int) -> None: 

1059 """Link a copied self as the n'th child of the parent.""" 

1060 p = self 

1061 parent_v = parent.v 

1062 p.stack = parent.stack[:] 

1063 p.stack.append((parent_v, parent._childIndex),) 

1064 p._childIndex = n 

1065 child = p.v 

1066 child._addCopiedLink(n, parent_v) 

1067 #@+node:ekr.20080416161551.216: *4* p._linkAsRoot 

1068 def _linkAsRoot(self) -> "Position": 

1069 """Link self as the root node.""" 

1070 p = self 

1071 assert p.v 

1072 parent_v = p.v.context.hiddenRootNode 

1073 assert parent_v, g.callers() 

1074 # 

1075 # Make p the root position. 

1076 p.stack = [] 

1077 p._childIndex = 0 

1078 # 

1079 # Make p.v the first child of parent_v. 

1080 p.v._addLink(0, parent_v) 

1081 return p 

1082 #@+node:ekr.20080416161551.212: *4* p._parentVnode 

1083 def _parentVnode(self) -> "VNode": 

1084 """ 

1085 Return the parent VNode. 

1086 Return the hiddenRootNode if there is no other parent. 

1087 """ 

1088 p = self 

1089 if p.v: 

1090 data = p.stack and p.stack[-1] 

1091 if data: 

1092 v, junk = data 

1093 return v 

1094 return p.v.context.hiddenRootNode 

1095 return None 

1096 #@+node:ekr.20131219220412.16582: *4* p._relinkAsCloneOf 

1097 def _relinkAsCloneOf(self, p2: "Position") -> None: 

1098 """A low-level method to replace p.v by a p2.v.""" 

1099 p = self 

1100 v, v2 = p.v, p2.v 

1101 parent_v = p._parentVnode() 

1102 if not parent_v: 

1103 g.internalError('no parent_v', p) 

1104 return 

1105 if parent_v.children[p._childIndex] == v: 

1106 parent_v.children[p._childIndex] = v2 

1107 v2.parents.append(parent_v) 

1108 # p.v no longer truly exists. 

1109 # p.v = p2.v 

1110 else: 

1111 g.internalError( 

1112 'parent_v.children[childIndex] != v', 

1113 p, parent_v.children, p._childIndex, v) 

1114 #@+node:ekr.20080416161551.217: *4* p._unlink 

1115 def _unlink(self) -> None: 

1116 """Unlink the receiver p from the tree.""" 

1117 p = self 

1118 n = p._childIndex 

1119 parent_v = p._parentVnode() 

1120 # returns None if p.v is None 

1121 child = p.v 

1122 assert p.v 

1123 assert parent_v 

1124 # Delete the child. 

1125 if (0 <= n < len(parent_v.children) and 

1126 parent_v.children[n] == child 

1127 ): 

1128 # This is the only call to v._cutlink. 

1129 child._cutLink(n, parent_v) 

1130 else: 

1131 self.badUnlink(parent_v, n, child) 

1132 #@+node:ekr.20090706171333.6226: *5* p.badUnlink 

1133 def badUnlink(self, parent_v: "VNode", n: int, child: "VNode") -> None: 

1134 

1135 if 0 <= n < len(parent_v.children): 

1136 g.trace(f"**can not happen: children[{n}] != p.v") 

1137 g.trace('parent_v.children...\n', 

1138 g.listToString(parent_v.children)) 

1139 g.trace('parent_v', parent_v) 

1140 g.trace('parent_v.children[n]', parent_v.children[n]) 

1141 g.trace('child', child) 

1142 g.trace('** callers:', g.callers()) 

1143 if g.unitTesting: 

1144 assert False, 'children[%s] != p.v' 

1145 else: 

1146 g.trace( 

1147 f"**can not happen: bad child index: {n}, " 

1148 f"len(children): {len(parent_v.children)}") 

1149 g.trace('parent_v.children...\n', 

1150 g.listToString(parent_v.children)) 

1151 g.trace('parent_v', parent_v, 'child', child) 

1152 g.trace('** callers:', g.callers()) 

1153 if g.unitTesting: 

1154 assert False, f"bad child index: {n}" 

1155 #@+node:ekr.20080416161551.199: *3* p.moveToX 

1156 #@+at These routines change self to a new position "in place". 

1157 # That is, these methods must _never_ call p.copy(). 

1158 # 

1159 # When moving to a nonexistent position, these routines simply set p.v = None, 

1160 # leaving the p.stack unchanged. This allows the caller to "undo" the effect of 

1161 # the invalid move by simply restoring the previous value of p.v. 

1162 # 

1163 # These routines all return self on exit so the following kind of code will work: 

1164 # after = p.copy().moveToNodeAfterTree() 

1165 #@+node:ekr.20080416161551.200: *4* p.moveToBack 

1166 def moveToBack(self) -> "Position": 

1167 """Move self to its previous sibling.""" 

1168 p = self 

1169 n = p._childIndex 

1170 parent_v = p._parentVnode() 

1171 # Returns None if p.v is None. 

1172 # Do not assume n is in range: this is used by positionExists. 

1173 if parent_v and p.v and 0 < n <= len(parent_v.children): 

1174 p._childIndex -= 1 

1175 p.v = parent_v.children[n - 1] 

1176 else: 

1177 p.v = None 

1178 return p 

1179 #@+node:ekr.20080416161551.201: *4* p.moveToFirstChild 

1180 def moveToFirstChild(self) -> "Position": 

1181 """Move a position to it's first child's position.""" 

1182 p = self 

1183 if p.v and p.v.children: 

1184 p.stack.append((p.v, p._childIndex),) 

1185 p.v = p.v.children[0] 

1186 p._childIndex = 0 

1187 else: 

1188 p.v = None 

1189 return p 

1190 #@+node:ekr.20080416161551.202: *4* p.moveToLastChild 

1191 def moveToLastChild(self) -> "Position": 

1192 """Move a position to it's last child's position.""" 

1193 p = self 

1194 if p.v and p.v.children: 

1195 p.stack.append((p.v, p._childIndex),) 

1196 n = len(p.v.children) 

1197 p.v = p.v.children[n - 1] 

1198 p._childIndex = n - 1 

1199 else: 

1200 p.v = None 

1201 return p 

1202 #@+node:ekr.20080416161551.203: *4* p.moveToLastNode 

1203 def moveToLastNode(self) -> "Position": 

1204 """Move a position to last node of its tree. 

1205 

1206 N.B. Returns p if p has no children.""" 

1207 p = self 

1208 # Huge improvement for 4.2. 

1209 while p.hasChildren(): 

1210 p.moveToLastChild() 

1211 return p 

1212 #@+node:ekr.20080416161551.204: *4* p.moveToNext 

1213 def moveToNext(self) -> "Position": 

1214 """Move a position to its next sibling.""" 

1215 p = self 

1216 n = p._childIndex 

1217 parent_v = p._parentVnode() 

1218 # Returns None if p.v is None. 

1219 if p and not p.v: 

1220 g.trace('no p.v:', p, g.callers()) 

1221 if p.v and parent_v and len(parent_v.children) > n + 1: 

1222 p._childIndex = n + 1 

1223 p.v = parent_v.children[n + 1] 

1224 else: 

1225 p.v = None 

1226 return p 

1227 #@+node:ekr.20080416161551.205: *4* p.moveToNodeAfterTree 

1228 def moveToNodeAfterTree(self) -> "Position": 

1229 """Move a position to the node after the position's tree.""" 

1230 p = self 

1231 while p: 

1232 if p.hasNext(): 

1233 p.moveToNext() 

1234 break 

1235 p.moveToParent() 

1236 return p 

1237 #@+node:ekr.20080416161551.206: *4* p.moveToNthChild 

1238 def moveToNthChild(self, n: int) -> "Position": 

1239 p = self 

1240 if p.v and len(p.v.children) > n: 

1241 p.stack.append((p.v, p._childIndex),) 

1242 p.v = p.v.children[n] 

1243 p._childIndex = n 

1244 else: 

1245 # mypy rightly doesn't like setting p.v to None. 

1246 # Leo's code must use the test `if p:` as appropriate. 

1247 p.v = None # type:ignore 

1248 return p 

1249 #@+node:ekr.20080416161551.207: *4* p.moveToParent 

1250 def moveToParent(self) -> "Position": 

1251 """Move a position to its parent position.""" 

1252 p = self 

1253 if p.v and p.stack: 

1254 p.v, p._childIndex = p.stack.pop() 

1255 else: 

1256 # mypy rightly doesn't like setting p.v to None. 

1257 # Leo's code must use the test `if p:` as appropriate. 

1258 p.v = None # type:ignore 

1259 return p 

1260 #@+node:ekr.20080416161551.208: *4* p.moveToThreadBack 

1261 def moveToThreadBack(self) -> "Position": 

1262 """Move a position to it's threadBack position.""" 

1263 p = self 

1264 if p.hasBack(): 

1265 p.moveToBack() 

1266 p.moveToLastNode() 

1267 else: 

1268 p.moveToParent() 

1269 return p 

1270 #@+node:ekr.20080416161551.209: *4* p.moveToThreadNext 

1271 def moveToThreadNext(self) -> "Position": 

1272 """Move a position to threadNext position.""" 

1273 p = self 

1274 if p.v: 

1275 if p.v.children: 

1276 p.moveToFirstChild() 

1277 elif p.hasNext(): 

1278 p.moveToNext() 

1279 else: 

1280 p.moveToParent() 

1281 while p: 

1282 if p.hasNext(): 

1283 p.moveToNext() 

1284 break #found 

1285 p.moveToParent() 

1286 # not found. 

1287 return p 

1288 #@+node:ekr.20080416161551.210: *4* p.moveToVisBack & helper 

1289 def moveToVisBack(self, c: "Cmdr") -> "Position": 

1290 """Move a position to the position of the previous visible node.""" 

1291 p = self 

1292 limit, limitIsVisible = c.visLimit() 

1293 while p: 

1294 # Short-circuit if possible. 

1295 back = p.back() 

1296 if back and back.hasChildren() and back.isExpanded(): 

1297 p.moveToThreadBack() 

1298 elif back: 

1299 p.moveToBack() 

1300 else: 

1301 p.moveToParent() # Same as p.moveToThreadBack() 

1302 if p: 

1303 if limit: 

1304 done, val = self.checkVisBackLimit(limit, limitIsVisible, p) 

1305 if done: 

1306 return val # A position or None 

1307 if p.isVisible(c): 

1308 return p 

1309 return p 

1310 #@+node:ekr.20090715145956.6166: *5* checkVisBackLimit 

1311 def checkVisBackLimit(self, 

1312 limit: "Position", 

1313 limitIsVisible: bool, 

1314 p: "Position", 

1315 ) -> Tuple[bool, Optional["Position"]]: 

1316 """Return done, p or None""" 

1317 c = p.v.context 

1318 if limit == p: 

1319 if limitIsVisible and p.isVisible(c): 

1320 return True, p 

1321 return True, None 

1322 if limit.isAncestorOf(p): 

1323 return False, None 

1324 return True, None 

1325 #@+node:ekr.20080416161551.211: *4* p.moveToVisNext & helper 

1326 def moveToVisNext(self, c: "Cmdr") -> "Position": 

1327 """Move a position to the position of the next visible node.""" 

1328 p = self 

1329 limit, limitIsVisible = c.visLimit() 

1330 while p: 

1331 if p.hasChildren(): 

1332 if p.isExpanded(): 

1333 p.moveToFirstChild() 

1334 else: 

1335 p.moveToNodeAfterTree() 

1336 elif p.hasNext(): 

1337 p.moveToNext() 

1338 else: 

1339 p.moveToThreadNext() 

1340 if p: 

1341 if limit and self.checkVisNextLimit(limit, p): 

1342 return None 

1343 if p.isVisible(c): 

1344 return p 

1345 return p 

1346 #@+node:ekr.20090715145956.6167: *5* checkVisNextLimit 

1347 def checkVisNextLimit(self, limit: "Position", p: "Position") -> bool: 

1348 """Return True is p is outside limit of visible nodes.""" 

1349 return limit != p and not limit.isAncestorOf(p) 

1350 #@+node:ekr.20150316175921.6: *4* p.safeMoveToThreadNext 

1351 def safeMoveToThreadNext(self) -> "Position": 

1352 """ 

1353 Move a position to threadNext position. 

1354 Issue an error if any vnode is an ancestor of itself. 

1355 """ 

1356 p = self 

1357 if p.v: 

1358 child_v = p.v.children and p.v.children[0] 

1359 if child_v: 

1360 for parent in p.self_and_parents(copy=False): 

1361 if child_v == parent.v: 

1362 g.app.structure_errors += 1 

1363 g.error(f"vnode: {child_v} is its own parent") 

1364 # Allocating a new vnode would be difficult. 

1365 # Just remove child_v from parent.v.children. 

1366 parent.v.children = [ 

1367 v2 for v2 in parent.v.children if not v2 == child_v] 

1368 if parent.v in child_v.parents: 

1369 child_v.parents.remove(parent.v) 

1370 # Try not to hang. 

1371 p.moveToParent() 

1372 break 

1373 elif child_v.fileIndex == parent.v.fileIndex: 

1374 g.app.structure_errors += 1 

1375 g.error( 

1376 f"duplicate gnx: {child_v.fileIndex!r} " 

1377 f"v: {child_v} parent: {parent.v}") 

1378 child_v.fileIndex = g.app.nodeIndices.getNewIndex(v=child_v) 

1379 assert child_v.gnx != parent.v.gnx 

1380 # Should be ok to continue. 

1381 p.moveToFirstChild() 

1382 break 

1383 else: 

1384 p.moveToFirstChild() 

1385 elif p.hasNext(): 

1386 p.moveToNext() 

1387 else: 

1388 p.moveToParent() 

1389 while p: 

1390 if p.hasNext(): 

1391 p.moveToNext() 

1392 break # found 

1393 p.moveToParent() 

1394 # not found. 

1395 return p 

1396 #@+node:ekr.20150316175921.7: *5* p.checkChild 

1397 #@+node:ekr.20040303175026: *3* p.Moving, Inserting, Deleting, Cloning, Sorting 

1398 #@+node:ekr.20040303175026.8: *4* p.clone 

1399 def clone(self) -> "Position": 

1400 """Create a clone of back. 

1401 

1402 Returns the newly created position.""" 

1403 p = self 

1404 p2 = p.copy() # Do *not* copy the VNode! 

1405 p2._linkAfter(p) # This should "just work" 

1406 return p2 

1407 #@+node:ekr.20040117171654: *4* p.copy 

1408 def copy(self) -> "Position": 

1409 """"Return an independent copy of a position.""" 

1410 return Position(self.v, self._childIndex, self.stack) 

1411 #@+node:ekr.20040303175026.9: *4* p.copyTreeAfter, copyTreeTo 

1412 # These used by unit tests, by the group_operations plugin, 

1413 # and by the files-compare-leo-files command. 

1414 

1415 # To do: use v.copyTree instead. 

1416 

1417 def copyTreeAfter(self, copyGnxs: bool=False) -> "Position": 

1418 """Copy p and insert it after itself.""" 

1419 p = self 

1420 p2 = p.insertAfter() 

1421 p.copyTreeFromSelfTo(p2, copyGnxs=copyGnxs) 

1422 return p2 

1423 

1424 

1425 def copyTreeFromSelfTo(self, p2: "Position", copyGnxs: bool=False) -> None: 

1426 p = self 

1427 p2.v._headString = g.toUnicode(p.h, reportErrors=True) # 2017/01/24 

1428 p2.v._bodyString = g.toUnicode(p.b, reportErrors=True) # 2017/01/24 

1429 # 

1430 # #1019794: p.copyTreeFromSelfTo, should deepcopy p.v.u. 

1431 p2.v.u = copy.deepcopy(p.v.u) 

1432 if copyGnxs: 

1433 p2.v.fileIndex = p.v.fileIndex 

1434 # 2009/10/02: no need to copy arg to iter 

1435 for child in p.children(): 

1436 child2 = p2.insertAsLastChild() 

1437 child.copyTreeFromSelfTo(child2, copyGnxs=copyGnxs) 

1438 #@+node:ekr.20160502095354.1: *4* p.copyWithNewVnodes 

1439 def copyWithNewVnodes(self, copyMarked: bool=False) -> "Position": 

1440 """ 

1441 Return an **unlinked** copy of p with a new vnode v. 

1442 The new vnode is complete copy of v and all its descendants. 

1443 """ 

1444 p = self 

1445 return Position(v=p.v.copyTree(copyMarked)) 

1446 #@+node:peckj.20131023115434.10115: *4* p.createNodeHierarchy 

1447 def createNodeHierarchy(self, heads: List, forcecreate: bool=False) -> "Position": 

1448 """ Create the proper hierarchy of nodes with headlines defined in 

1449 'heads' as children of the current position 

1450 

1451 params: 

1452 heads - list of headlines in order to create, i.e. ['foo','bar','baz'] 

1453 will create: 

1454 self 

1455 -foo 

1456 --bar 

1457 ---baz 

1458 forcecreate - If False (default), will not create nodes unless they don't exist 

1459 If True, will create nodes regardless of existing nodes 

1460 returns the final position ('baz' in the above example) 

1461 """ 

1462 c = self.v.context 

1463 return c.createNodeHierarchy(heads, parent=self, forcecreate=forcecreate) 

1464 #@+node:ekr.20131230090121.16552: *4* p.deleteAllChildren 

1465 def deleteAllChildren(self) -> None: 

1466 """ 

1467 Delete all children of the receiver and set p.dirty(). 

1468 """ 

1469 p = self 

1470 p.setDirty() # Mark @file nodes dirty! 

1471 while p.hasChildren(): 

1472 p.firstChild().doDelete() 

1473 #@+node:ekr.20040303175026.2: *4* p.doDelete 

1474 def doDelete(self, newNode: Optional["Position"]=None) -> None: 

1475 """ 

1476 Deletes position p from the outline. 

1477 

1478 This is the main delete routine. 

1479 It deletes the receiver's entire tree from the screen. 

1480 Because of the undo command we never actually delete vnodes. 

1481 """ 

1482 p = self 

1483 p.setDirty() # Mark @file nodes dirty! 

1484 sib = p.copy() 

1485 while sib.hasNext(): 

1486 sib.moveToNext() 

1487 if sib == newNode: 

1488 # Adjust newNode._childIndex if newNode is a following sibling of p. 

1489 newNode._childIndex -= 1 

1490 break 

1491 p._unlink() 

1492 #@+node:ekr.20040303175026.3: *4* p.insertAfter 

1493 def insertAfter(self) -> "Position": 

1494 """ 

1495 Inserts a new position after self. 

1496 

1497 Returns the newly created position. 

1498 """ 

1499 p = self 

1500 context = p.v.context 

1501 p2 = self.copy() 

1502 p2.v = VNode(context=context) 

1503 p2.v.iconVal = 0 

1504 p2._linkAfter(p) 

1505 return p2 

1506 #@+node:ekr.20040303175026.4: *4* p.insertAsLastChild 

1507 def insertAsLastChild(self) -> "Position": 

1508 """ 

1509 Insert a new VNode as the last child of self. 

1510 

1511 Return the newly created position. 

1512 """ 

1513 p = self 

1514 n = p.numberOfChildren() 

1515 return p.insertAsNthChild(n) 

1516 #@+node:ekr.20040303175026.5: *4* p.insertAsNthChild 

1517 def insertAsNthChild(self, n: int) -> "Position": 

1518 """ 

1519 Inserts a new node as the the nth child of self. 

1520 self must have at least n-1 children. 

1521 

1522 Returns the newly created position. 

1523 """ 

1524 p = self 

1525 context = p.v.context 

1526 p2 = self.copy() 

1527 p2.v = VNode(context=context) 

1528 p2.v.iconVal = 0 

1529 p2._linkAsNthChild(p, n) 

1530 return p2 

1531 #@+node:ekr.20130923111858.11572: *4* p.insertBefore 

1532 def insertBefore(self) -> "Position": 

1533 """ 

1534 Insert a new position before self. 

1535 

1536 Return the newly created position. 

1537 """ 

1538 p = self 

1539 parent = p.parent() 

1540 if p.hasBack(): 

1541 back = p.getBack() 

1542 p = back.insertAfter() 

1543 elif parent: 

1544 p = parent.insertAsNthChild(0) 

1545 else: 

1546 p = p.insertAfter() 

1547 p.moveToRoot() 

1548 return p 

1549 #@+node:ekr.20040310062332.1: *4* p.invalidOutline 

1550 def invalidOutline(self, message: str) -> None: 

1551 p = self 

1552 if p.hasParent(): 

1553 node = p.parent() 

1554 else: 

1555 node = p 

1556 p.v.context.alert(f"invalid outline: {message}\n{node}") 

1557 #@+node:ekr.20040303175026.10: *4* p.moveAfter 

1558 def moveAfter(self, a: "Position") -> "Position": 

1559 """Move a position after position a.""" 

1560 p = self # Do NOT copy the position! 

1561 a._adjustPositionBeforeUnlink(p) 

1562 p._unlink() 

1563 p._linkAfter(a) 

1564 return p 

1565 #@+node:ekr.20040306060312: *4* p.moveToFirst/LastChildOf 

1566 def moveToFirstChildOf(self, parent: "Position") -> "Position": 

1567 """Move a position to the first child of parent.""" 

1568 p = self # Do NOT copy the position! 

1569 return p.moveToNthChildOf(parent, 0) # Major bug fix: 2011/12/04 

1570 

1571 def moveToLastChildOf(self, parent: "Position") -> "Position": 

1572 """Move a position to the last child of parent.""" 

1573 p = self # Do NOT copy the position! 

1574 n = parent.numberOfChildren() 

1575 if p.parent() == parent: 

1576 n -= 1 # 2011/12/10: Another bug fix. 

1577 return p.moveToNthChildOf(parent, n) # Major bug fix: 2011/12/04 

1578 #@+node:ekr.20040303175026.11: *4* p.moveToNthChildOf 

1579 def moveToNthChildOf(self, parent: "Position", n: int) -> "Position": 

1580 """Move a position to the nth child of parent.""" 

1581 p = self # Do NOT copy the position! 

1582 parent._adjustPositionBeforeUnlink(p) 

1583 p._unlink() 

1584 p._linkAsNthChild(parent, n) 

1585 return p 

1586 #@+node:ekr.20040303175026.6: *4* p.moveToRoot 

1587 def moveToRoot(self) -> "Position": 

1588 """Move self to the root position.""" 

1589 p = self # Do NOT copy the position! 

1590 # 

1591 # #1631. The old root can not possibly be affected by unlinking p. 

1592 p._unlink() 

1593 p._linkAsRoot() 

1594 return p 

1595 #@+node:ekr.20180123062833.1: *4* p.promote 

1596 def promote(self) -> None: 

1597 """A low-level promote helper.""" 

1598 p = self # Do NOT copy the position. 

1599 parent_v = p._parentVnode() 

1600 children = p.v.children 

1601 # Add the children to parent_v's children. 

1602 n = p.childIndex() + 1 

1603 z = parent_v.children[:] 

1604 parent_v.children = z[:n] 

1605 parent_v.children.extend(children) 

1606 parent_v.children.extend(z[n:]) 

1607 # Remove v's children. 

1608 p.v.children = [] 

1609 # Adjust the parent links in the moved children. 

1610 # There is no need to adjust descendant links. 

1611 for child in children: 

1612 child.parents.remove(p.v) 

1613 child.parents.append(parent_v) 

1614 #@+node:ekr.20040303175026.13: *4* p.validateOutlineWithParent 

1615 # This routine checks the structure of the receiver's tree. 

1616 

1617 def validateOutlineWithParent(self, pv: "Position") -> bool: 

1618 p = self 

1619 result = True # optimists get only unpleasant surprises. 

1620 parent = p.getParent() 

1621 childIndex = p._childIndex 

1622 #@+<< validate parent ivar >> 

1623 #@+node:ekr.20040303175026.14: *5* << validate parent ivar >> 

1624 if parent != pv: 

1625 p.invalidOutline("Invalid parent link: " + repr(parent)) 

1626 #@-<< validate parent ivar >> 

1627 #@+<< validate childIndex ivar >> 

1628 #@+node:ekr.20040303175026.15: *5* << validate childIndex ivar >> 

1629 if pv: 

1630 if childIndex < 0: 

1631 p.invalidOutline(f"missing childIndex: {childIndex!r}") 

1632 elif childIndex >= pv.numberOfChildren(): 

1633 p.invalidOutline("missing children entry for index: {childIndex!r}") 

1634 elif childIndex < 0: 

1635 p.invalidOutline("negative childIndex: {childIndex!r}") 

1636 #@-<< validate childIndex ivar >> 

1637 #@+<< validate x ivar >> 

1638 #@+node:ekr.20040303175026.16: *5* << validate x ivar >> 

1639 if not p.v and pv: 

1640 self.invalidOutline("Empty t") 

1641 #@-<< validate x ivar >> 

1642 # Recursively validate all the children. 

1643 for child in p.children(): 

1644 r = child.validateOutlineWithParent(p) 

1645 if not r: 

1646 result = False 

1647 return result 

1648 #@+node:ekr.20090128083459.74: *3* p.Properties 

1649 #@+node:ekr.20090128083459.75: *4* p.b property 

1650 def __get_b(self) -> str: 

1651 """Return the body text of a position.""" 

1652 p = self 

1653 return p.bodyString() 

1654 

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

1656 """ 

1657 Set the body text of a position. 

1658 

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

1660 c.setBodyString(). 

1661 

1662 Usually, code *should* use this setter, despite its cost, because it 

1663 update's Leo's outline pane properly. Calling c.redraw() is *not* 

1664 enough. 

1665 

1666 This performance gotcha becomes important for repetitive commands, like 

1667 cff, replace-all and recursive import. In such situations, code should 

1668 use p.v.b instead of p.b. 

1669 """ 

1670 p = self 

1671 c = p.v and p.v.context 

1672 if c: 

1673 c.setBodyString(p, val) 

1674 # Warning: c.setBodyString is *expensive*. 

1675 

1676 b = property( 

1677 __get_b, __set_b, 

1678 doc="position body string property") 

1679 #@+node:ekr.20090128083459.76: *4* p.h property 

1680 def __get_h(self) -> str: 

1681 p = self 

1682 return p.headString() 

1683 

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

1685 """ 

1686 Set the headline text of a position. 

1687 

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

1689 c.setHeadString(). 

1690 

1691 Usually, code *should* use this setter, despite its cost, because it 

1692 update's Leo's outline pane properly. Calling c.redraw() is *not* 

1693 enough. 

1694 

1695 This performance gotcha becomes important for repetitive commands, like 

1696 cff, replace-all and recursive import. In such situations, code should 

1697 use p.v.h instead of p.h. 

1698 """ 

1699 p = self 

1700 c = p.v and p.v.context 

1701 if c: 

1702 c.setHeadString(p, val) 

1703 # Warning: c.setHeadString is *expensive*. 

1704 

1705 h = property( 

1706 __get_h, __set_h, 

1707 doc="position property returning the headline string") 

1708 #@+node:ekr.20090215165030.3: *4* p.gnx property 

1709 def __get_gnx(self) -> str: 

1710 p = self 

1711 return p.v.fileIndex 

1712 

1713 gnx = property( 

1714 __get_gnx, # __set_gnx, 

1715 doc="position gnx property") 

1716 #@+node:ekr.20140203082618.15486: *4* p.script property 

1717 def __get_script(self) -> str: 

1718 p = self 

1719 return g.getScript(p.v.context, p, 

1720 useSelectedText=False, # Always return the entire expansion. 

1721 forcePythonSentinels=True, 

1722 useSentinels=False) 

1723 

1724 script = property( 

1725 __get_script, # __set_script, 

1726 doc="position property returning the script formed by p and its descendants") 

1727 #@+node:ekr.20140218040104.16761: *4* p.nosentinels property 

1728 def __get_nosentinels(self) -> str: 

1729 p = self 

1730 return ''.join([z for z in g.splitLines(p.b) if not g.isDirective(z)]) 

1731 

1732 nosentinels = property( 

1733 __get_nosentinels, # __set_nosentinels 

1734 doc="position property returning the body text without sentinels") 

1735 #@+node:ekr.20160129073222.1: *4* p.u Property 

1736 def __get_u(self) -> Any: 

1737 p = self 

1738 return p.v.u 

1739 

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

1741 p = self 

1742 p.v.u = val 

1743 

1744 u = property( 

1745 __get_u, __set_u, 

1746 doc="p.u property") 

1747 #@+node:ekr.20040305222924: *3* p.Setters 

1748 #@+node:ekr.20040306220634: *4* p.VNode proxies 

1749 #@+node:ekr.20131222112420.16371: *5* p.contract/expand/isExpanded 

1750 def contract(self) -> None: 

1751 """Contract p.v and clear p.v.expandedPositions list.""" 

1752 p, v = self, self.v 

1753 v.expandedPositions = [z for z in v.expandedPositions if z != p] 

1754 v.contract() 

1755 

1756 def expand(self) -> None: 

1757 p = self 

1758 v = self.v 

1759 v.expandedPositions = [z for z in v.expandedPositions if z != p] 

1760 for p2 in v.expandedPositions: 

1761 if p == p2: 

1762 break 

1763 else: 

1764 v.expandedPositions.append(p.copy()) 

1765 v.expand() 

1766 

1767 def isExpanded(self) -> bool: 

1768 p = self 

1769 if p.isCloned(): 

1770 c = p.v.context 

1771 return c.shouldBeExpanded(p) 

1772 return p.v.isExpanded() 

1773 #@+node:ekr.20040306220634.9: *5* p.Status bits 

1774 # Clone bits are no longer used. 

1775 # Dirty bits are handled carefully by the position class. 

1776 

1777 def clearMarked(self) -> None: 

1778 self.v.clearMarked() 

1779 

1780 def clearOrphan(self) -> None: 

1781 self.v.clearOrphan() 

1782 

1783 def clearVisited(self) -> None: 

1784 self.v.clearVisited() 

1785 

1786 def initExpandedBit(self) -> None: 

1787 self.v.initExpandedBit() 

1788 

1789 def initMarkedBit(self) -> None: 

1790 self.v.initMarkedBit() 

1791 

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

1793 self.v.initStatus(status) 

1794 

1795 def setMarked(self) -> None: 

1796 self.v.setMarked() 

1797 

1798 def setOrphan(self) -> None: 

1799 self.v.setOrphan() 

1800 

1801 def setSelected(self) -> None: 

1802 self.v.setSelected() 

1803 

1804 def setVisited(self) -> None: 

1805 self.v.setVisited() 

1806 #@+node:ekr.20040306220634.8: *5* p.computeIcon & p.setIcon 

1807 def computeIcon(self) -> int: 

1808 return self.v.computeIcon() 

1809 

1810 def setIcon(self) -> None: 

1811 pass # Compatibility routine for old scripts 

1812 #@+node:ekr.20040306220634.29: *5* p.setSelection 

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

1814 self.v.setSelection(start, length) 

1815 #@+node:ekr.20100303074003.5637: *5* p.restore/saveCursorAndScroll 

1816 def restoreCursorAndScroll(self) -> None: 

1817 self.v.restoreCursorAndScroll() 

1818 

1819 def saveCursorAndScroll(self) -> None: 

1820 self.v.saveCursorAndScroll() 

1821 #@+node:ekr.20040315034158: *4* p.setBodyString & setHeadString 

1822 def setBodyString(self, s: str) -> None: 

1823 p = self 

1824 return p.v.setBodyString(s) 

1825 

1826 initBodyString = setBodyString 

1827 setTnodeText = setBodyString 

1828 scriptSetBodyString = setBodyString 

1829 

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

1831 p = self 

1832 p.v.initHeadString(s) 

1833 

1834 def setHeadString(self, s: str) -> None: 

1835 p = self 

1836 p.v.initHeadString(s) 

1837 p.setDirty() 

1838 #@+node:ekr.20040312015908: *4* p.Visited bits 

1839 #@+node:ekr.20040306220634.17: *5* p.clearVisitedInTree 

1840 # Compatibility routine for scripts. 

1841 

1842 def clearVisitedInTree(self) -> None: 

1843 for p in self.self_and_subtree(copy=False): 

1844 p.clearVisited() 

1845 #@+node:ekr.20031218072017.3388: *5* p.clearAllVisitedInTree 

1846 def clearAllVisitedInTree(self) -> None: 

1847 for p in self.self_and_subtree(copy=False): 

1848 p.v.clearVisited() 

1849 p.v.clearWriteBit() 

1850 #@+node:ekr.20040305162628: *4* p.Dirty bits 

1851 #@+node:ekr.20040311113514: *5* p.clearDirty 

1852 def clearDirty(self) -> None: 

1853 """(p) Set p.v dirty.""" 

1854 p = self 

1855 p.v.clearDirty() 

1856 #@+node:ekr.20040702104823: *5* p.inAtIgnoreRange 

1857 def inAtIgnoreRange(self) -> bool: 

1858 """Returns True if position p or one of p's parents is an @ignore node.""" 

1859 p = self 

1860 for p in p.self_and_parents(copy=False): 

1861 if p.isAtIgnoreNode(): 

1862 return True 

1863 return False 

1864 #@+node:ekr.20040303214038: *5* p.setAllAncestorAtFileNodesDirty 

1865 def setAllAncestorAtFileNodesDirty(self) -> None: 

1866 """ 

1867 Set all ancestor @<file> nodes dirty, including ancestors of all clones of p. 

1868 """ 

1869 p = self 

1870 p.v.setAllAncestorAtFileNodesDirty() 

1871 #@+node:ekr.20040303163330: *5* p.setDirty 

1872 def setDirty(self) -> None: 

1873 """ 

1874 Mark a node and all ancestor @file nodes dirty. 

1875 

1876 p.setDirty() is no longer expensive. 

1877 """ 

1878 p = self 

1879 p.v.setAllAncestorAtFileNodesDirty() 

1880 p.v.setDirty() 

1881 #@+node:ekr.20160225153333.1: *3* p.Predicates 

1882 #@+node:ekr.20160225153414.1: *4* p.is_at_all & is_at_all_tree 

1883 def is_at_all(self) -> bool: 

1884 """Return True if p.b contains an @all directive.""" 

1885 p = self 

1886 return ( 

1887 p.isAnyAtFileNode() 

1888 and any(g.match_word(s, 0, '@all') for s in g.splitLines(p.b))) 

1889 

1890 def in_at_all_tree(self) -> bool: 

1891 """Return True if p or one of p's ancestors is an @all node.""" 

1892 p = self 

1893 for p in p.self_and_parents(copy=False): 

1894 if p.is_at_all(): 

1895 return True 

1896 return False 

1897 #@+node:ekr.20160225153430.1: *4* p.is_at_ignore & in_at_ignore_tree 

1898 def is_at_ignore(self) -> bool: 

1899 """Return True if p is an @ignore node.""" 

1900 p = self 

1901 return g.match_word(p.h, 0, '@ignore') 

1902 

1903 def in_at_ignore_tree(self) -> bool: 

1904 """Return True if p or one of p's ancestors is an @ignore node.""" 

1905 p = self 

1906 for p in p.self_and_parents(copy=False): 

1907 if g.match_word(p.h, 0, '@ignore'): 

1908 return True 

1909 return False 

1910 #@-others 

1911 

1912position = Position # compatibility. 

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

1914class PosList(list): 

1915 

1916 __slots__: List[str] = [] 

1917 

1918 #@+others 

1919 #@+node:bob.20101215134608.5897: *3* PosList.children 

1920 def children(self) -> "PosList": 

1921 """ Return a PosList instance containing pointers to 

1922 all the immediate children of nodes in PosList self. 

1923 """ 

1924 res = PosList() 

1925 for p in self: 

1926 for child_p in p.children(): 

1927 res.append(child_p.copy()) 

1928 return res 

1929 #@+node:ville.20090311190405.69: *3* PosList.filter_h 

1930 def filter_h(self, regex: str, flags: Any=re.IGNORECASE) -> "PosList": 

1931 """ 

1932 Find all the nodes in PosList self where zero or more characters at 

1933 the beginning of the headline match regex. 

1934 """ 

1935 pat = re.compile(regex, flags) 

1936 res = PosList() 

1937 for p in self: 

1938 mo = re.match(pat, p.h) 

1939 if mo: 

1940 # #2012: Don't inject pc.mo. 

1941 pc = p.copy() 

1942 res.append(pc) 

1943 return res 

1944 #@+node:ville.20090311195550.1: *3* PosList.filter_b 

1945 def filter_b(self, regex: str, flags: Any=re.IGNORECASE) -> "PosList": 

1946 """ Find all the nodes in PosList self where body matches regex 

1947 one or more times. 

1948 

1949 """ 

1950 pat = re.compile(regex, flags) 

1951 res = PosList() 

1952 for p in self: 

1953 m = re.finditer(pat, p.b) 

1954 t1, t2 = itertools.tee(m, 2) 

1955 try: 

1956 t1.__next__() 

1957 pc = p.copy() 

1958 pc.matchiter = t2 

1959 res.append(pc) 

1960 except StopIteration: 

1961 pass 

1962 return res 

1963 #@-others 

1964 

1965Poslist = PosList # compatibility. 

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

1967#@@nobeautify 

1968 

1969class VNode: 

1970 

1971 __slots__ = [ 

1972 '_bodyString', '_headString', '_p_changed', 

1973 'children', 'fileIndex', 'iconVal', 'parents', 'statusBits', 

1974 'unknownAttributes', 

1975 # Injected by read code. 

1976 'at_read', 'tempAttributes', 

1977 # Not written to any file. 

1978 'context', 'expandedPositions', 'insertSpot', 

1979 'scrollBarSpot', 'selectionLength', 'selectionStart', 

1980 ] 

1981 #@+<< VNode constants >> 

1982 #@+node:ekr.20031218072017.951: *3* << VNode constants >> 

1983 # Define the meaning of status bits in new vnodes. 

1984 # Archived... 

1985 clonedBit = 0x01 # True: VNode has clone mark. 

1986 # unused 0x02 

1987 expandedBit = 0x04 # True: VNode is expanded. 

1988 markedBit = 0x08 # True: VNode is marked 

1989 # unused = 0x10 # (was orphanBit) 

1990 selectedBit = 0x20 # True: VNode is current VNode. 

1991 topBit = 0x40 # True: VNode was top VNode when saved. 

1992 # Not archived... 

1993 richTextBit = 0x080 # Determines whether we use <bt> or <btr> tags. 

1994 visitedBit = 0x100 

1995 dirtyBit = 0x200 

1996 writeBit = 0x400 

1997 orphanBit = 0x800 # True: error in @<file> tree prevented it from being written. 

1998 #@-<< VNode constants >> 

1999 #@+others 

2000 #@+node:ekr.20031218072017.3342: *3* v.Birth & death 

2001 #@+node:ekr.20031218072017.3344: *4* v.__init__ 

2002 def __init__(self, context: "Cmdr", gnx: Optional[str]=None): 

2003 """ 

2004 Ctor for the VNode class. 

2005 To support ZODB, the code must set v._p_changed = True whenever 

2006 v.unknownAttributes or any mutable VNode object changes. 

2007 """ 

2008 # The primary data: headline and body text. 

2009 self._headString = 'newHeadline' 

2010 self._bodyString = '' 

2011 # For zodb. 

2012 self._p_changed = False 

2013 # Structure data... 

2014 self.children: List["VNode"] = [] 

2015 # Ordered list of all children of this node. 

2016 self.parents: List["VNode"] = [] 

2017 # Unordered list of all parents of this node. 

2018 # Other essential data... 

2019 self.fileIndex: Optional[str] = None 

2020 # The immutable fileIndex (gnx) for this node. Set below. 

2021 self.iconVal = 0 

2022 # The present value of the node's icon. 

2023 self.statusBits = 0 

2024 # status bits 

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

2026 self.context: Cmdr = context # The context containing context.hiddenRootNode. 

2027 # Required so we can compute top-level siblings. 

2028 # It is named .context rather than .c to emphasize its limited usage. 

2029 self.expandedPositions: List[Position] = [] 

2030 # Positions that should be expanded. 

2031 self.insertSpot: Optional[int] = None 

2032 # Location of previous insert point. 

2033 self.scrollBarSpot: Optional[int] = None 

2034 # Previous value of scrollbar position. 

2035 self.selectionLength = 0 

2036 # The length of the selected body text. 

2037 self.selectionStart = 0 

2038 # The start of the selected body text. 

2039 # 

2040 # For at.read logic. 

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

2042 # 

2043 # To make VNode's independent of Leo's core, 

2044 # wrap all calls to the VNode ctor:: 

2045 # 

2046 # def allocate_vnode(c,gnx): 

2047 # v = VNode(c) 

2048 # g.app.nodeIndices.new_vnode_helper(c,gnx,v) 

2049 g.app.nodeIndices.new_vnode_helper(context, gnx, self) 

2050 assert self.fileIndex, g.callers() 

2051 #@+node:ekr.20031218072017.3345: *4* v.__repr__ & v.__str__ 

2052 def __repr__(self) -> str: 

2053 return f"<VNode {self.gnx} {self.headString()}>" 

2054 

2055 __str__ = __repr__ 

2056 #@+node:ekr.20040312145256: *4* v.dump 

2057 def dumpLink(self, link: Optional[str]) -> str: 

2058 return link if link else "<none>" 

2059 

2060 def dump(self, label: str="") -> None: 

2061 v = self 

2062 s = '-' * 10 

2063 print(f"{s} {label} {v}") 

2064 # print('gnx: %s' % v.gnx) 

2065 print(f"len(parents): {len(v.parents)}") 

2066 print(f"len(children): {len(v.children)}") 

2067 print(f"parents: {g.listToString(v.parents)}") 

2068 print(f"children: {g.listToString(v.children)}") 

2069 #@+node:ekr.20031218072017.3346: *3* v.Comparisons 

2070 #@+node:ekr.20040705201018: *4* v.findAtFileName 

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

2072 """Return the name following one of the names in nameList or """ 

2073 # Allow h argument for unit testing. 

2074 if not h: 

2075 h = self.headString() 

2076 if not g.match(h, 0, '@'): 

2077 return "" 

2078 i = g.skip_id(h, 1, '-') 

2079 word = h[:i] 

2080 if word in names and g.match_word(h, 0, word): 

2081 name = h[i:].strip() 

2082 return name 

2083 return "" 

2084 #@+node:ekr.20031218072017.3350: *4* v.anyAtFileNodeName 

2085 def anyAtFileNodeName(self) -> str: 

2086 """Return the file name following an @file node or an empty string.""" 

2087 return ( 

2088 self.findAtFileName(g.app.atAutoNames) or 

2089 self.findAtFileName(g.app.atFileNames)) 

2090 #@+node:ekr.20031218072017.3348: *4* v.at...FileNodeName 

2091 # These return the filename following @xxx, in v.headString. 

2092 # Return the the empty string if v is not an @xxx node. 

2093 

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

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

2096 

2097 # Retain this special case as part of the "escape hatch". 

2098 # That is, we fall back on code in leoRst.py if no 

2099 # importer or writer for reStructuredText exists. 

2100 

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

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

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

2104 

2105 def atCleanNodeName(self) -> str: 

2106 names = ("@clean",) 

2107 return self.findAtFileName(names) 

2108 

2109 def atEditNodeName(self) -> str: 

2110 names = ("@edit",) 

2111 return self.findAtFileName(names) 

2112 

2113 def atFileNodeName(self) -> str: 

2114 names = ("@file", "@thin") 

2115 # Fix #403. 

2116 return self.findAtFileName(names) 

2117 

2118 def atNoSentinelsFileNodeName(self) -> str: 

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

2120 return self.findAtFileName(names) 

2121 

2122 def atRstFileNodeName(self) -> str: 

2123 names = ("@rst",) 

2124 return self.findAtFileName(names) 

2125 

2126 def atShadowFileNodeName(self) -> str: 

2127 names = ("@shadow",) 

2128 return self.findAtFileName(names) 

2129 

2130 def atSilentFileNodeName(self) -> str: 

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

2132 return self.findAtFileName(names) 

2133 

2134 def atThinFileNodeName(self) -> str: 

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

2136 return self.findAtFileName(names) 

2137 

2138 # New names, less confusing 

2139 

2140 atNoSentFileNodeName = atNoSentinelsFileNodeName 

2141 atAsisFileNodeName = atSilentFileNodeName 

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

2143 def isAtAllNode(self) -> bool: 

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

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

2146 return flag 

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

2148 def isAnyAtFileNode(self) -> bool: 

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

2150 return bool(self.anyAtFileNodeName()) 

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

2152 def isAtAutoNode(self) -> bool: 

2153 return bool(self.atAutoNodeName()) 

2154 

2155 def isAtAutoRstNode(self) -> bool: 

2156 return bool(self.atAutoRstNodeName()) 

2157 

2158 def isAtCleanNode(self) -> bool: 

2159 return bool(self.atCleanNodeName()) 

2160 

2161 def isAtEditNode(self) -> bool: 

2162 return bool(self.atEditNodeName()) 

2163 

2164 def isAtFileNode(self) -> bool: 

2165 return bool(self.atFileNodeName()) 

2166 

2167 def isAtRstFileNode(self) -> bool: 

2168 return bool(self.atRstFileNodeName()) 

2169 

2170 def isAtNoSentinelsFileNode(self) -> bool: 

2171 return bool(self.atNoSentinelsFileNodeName()) 

2172 

2173 def isAtSilentFileNode(self) -> bool: 

2174 return bool(self.atSilentFileNodeName()) 

2175 

2176 def isAtShadowFileNode(self) -> bool: 

2177 return bool(self.atShadowFileNodeName()) 

2178 

2179 def isAtThinFileNode(self) -> bool: 

2180 return bool(self.atThinFileNodeName()) 

2181 

2182 # New names, less confusing: 

2183 

2184 isAtNoSentFileNode = isAtNoSentinelsFileNode 

2185 isAtAsisFileNode = isAtSilentFileNode 

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

2187 def isAtIgnoreNode(self) -> bool: 

2188 """ 

2189 Returns True if: 

2190 

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

2192 

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

2194 """ 

2195 # v = self 

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

2197 return True 

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

2199 return flag 

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

2201 def isAtOthersNode(self) -> bool: 

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

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

2204 return flag 

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

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

2207 """ 

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

2209 

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

2211 """ 

2212 v = self 

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

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

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

2216 pattern = g.toUnicode(pattern) 

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

2218 return h.startswith(pattern) 

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

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

2221 """ 

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

2223 descendants. 

2224 

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

2226 v._addParentLinks will set all parents. 

2227 """ 

2228 v = self 

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

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

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

2232 assert v2.gnx 

2233 assert v.gnx != v2.gnx 

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

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

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

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

2238 if copyMarked and v.isMarked(): 

2239 v2.setMarked() 

2240 # Recursively copy all descendant vnodes. 

2241 for child in v.children: 

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

2243 return v2 

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

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

2246 def bodyString(self) -> str: 

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

2248 if isinstance(self._bodyString, str): 

2249 return self._bodyString 

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

2251 return g.toUnicode(self._bodyString) 

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

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

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

2255 v = self 

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

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

2258 def hasChildren(self) -> bool: 

2259 v = self 

2260 return len(v.children) > 0 

2261 

2262 hasFirstChild = hasChildren 

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

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

2265 v = self 

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

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

2268 # childIndex and nthChild are zero-based. 

2269 

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

2271 v = self 

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

2273 return v.children[n] 

2274 return None 

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

2276 def numberOfChildren(self) -> int: 

2277 v = self 

2278 return len(v.children) 

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

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

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

2282 

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

2284 v = self 

2285 return v.parents 

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

2287 def hasBody(self) -> bool: 

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

2289 s = self._bodyString 

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

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

2292 def headString(self) -> str: 

2293 """Return the headline string.""" 

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

2295 if isinstance(self._headString, str): 

2296 return self._headString 

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

2298 return g.toUnicode(self._headString) 

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

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

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

2302 v = self 

2303 if not parent_v: 

2304 return False 

2305 children = parent_v.children 

2306 if not children: 

2307 return False 

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

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

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

2311 def isCloned(self) -> bool: 

2312 return len(self.parents) > 1 

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

2314 def isDirty(self) -> bool: 

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

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

2317 def isMarked(self) -> bool: 

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

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

2320 def isOrphan(self) -> bool: 

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

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

2323 def isSelected(self) -> bool: 

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

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

2326 def isTopBitSet(self) -> bool: 

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

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

2329 def isVisited(self) -> bool: 

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

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

2332 def isWriteBit(self) -> bool: 

2333 v = self 

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

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

2336 def status(self) -> int: 

2337 return self.statusBits 

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

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

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

2341 def clearClonedBit(self) -> None: 

2342 self.statusBits &= ~self.clonedBit 

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

2344 def clearDirty(self) -> None: 

2345 """Clear the vnode dirty bit.""" 

2346 v = self 

2347 v.statusBits &= ~v.dirtyBit 

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

2349 def clearMarked(self) -> None: 

2350 self.statusBits &= ~self.markedBit 

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

2352 def clearOrphan(self) -> None: 

2353 self.statusBits &= ~self.orphanBit 

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

2355 def clearVisited(self) -> None: 

2356 self.statusBits &= ~self.visitedBit 

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

2358 def clearWriteBit(self) -> None: 

2359 self.statusBits &= ~self.writeBit 

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

2361 def contract(self) -> None: 

2362 """Contract the node.""" 

2363 self.statusBits &= ~self.expandedBit 

2364 

2365 def expand(self) -> None: 

2366 """Expand the node.""" 

2367 self.statusBits |= self.expandedBit 

2368 

2369 def initExpandedBit(self) -> None: 

2370 """Init self.statusBits.""" 

2371 self.statusBits |= self.expandedBit 

2372 

2373 def isExpanded(self) -> bool: 

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

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

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

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

2378 self.statusBits = status 

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

2380 def setClonedBit(self) -> None: 

2381 self.statusBits |= self.clonedBit 

2382 

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

2384 if val: 

2385 self.statusBits |= self.clonedBit 

2386 else: 

2387 self.statusBits &= ~self.clonedBit 

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

2389 def setDirty(self) -> None: 

2390 """ 

2391 Set the vnode dirty bit. 

2392 

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

2394 not call v.setAllAncestorAtFileNodesDirty. 

2395 """ 

2396 self.statusBits |= self.dirtyBit 

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

2398 def setMarked(self) -> None: 

2399 self.statusBits |= self.markedBit 

2400 

2401 def initMarkedBit(self) -> None: 

2402 self.statusBits |= self.markedBit 

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

2404 def setOrphan(self) -> None: 

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

2406 self.statusBits |= self.orphanBit 

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

2408 # This only sets the selected bit. 

2409 

2410 def setSelected(self) -> None: 

2411 self.statusBits |= self.selectedBit 

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

2413 # Compatibility routine for scripts 

2414 

2415 def setVisited(self) -> None: 

2416 self.statusBits |= self.visitedBit 

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

2418 def setWriteBit(self) -> None: 

2419 self.statusBits |= self.writeBit 

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

2421 def childrenModified(self) -> None: 

2422 g.childrenModifiedSet.add(self) 

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

2424 def computeIcon(self) -> int: 

2425 v = self 

2426 val = 0 

2427 if v.hasBody(): 

2428 val += 1 

2429 if v.isMarked(): 

2430 val += 2 

2431 if v.isCloned(): 

2432 val += 4 

2433 if v.isDirty(): 

2434 val += 8 

2435 return val 

2436 

2437 def setIcon(self) -> None: 

2438 pass # Compatibility routine for old scripts 

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

2440 def contentModified(self) -> None: 

2441 g.contentModifiedSet.add(self) 

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

2443 # Called only by LeoTree.selectHelper. 

2444 

2445 def restoreCursorAndScroll(self) -> None: 

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

2447 traceTime = False and not g.unitTesting 

2448 v = self 

2449 ins = v.insertSpot 

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

2451 spot = v.scrollBarSpot 

2452 body = self.context.frame.body 

2453 w = body.wrapper 

2454 # Fix bug 981849: incorrect body content shown. 

2455 if ins is None: 

2456 ins = 0 

2457 # This is very expensive for large text. 

2458 if traceTime: 

2459 t1 = time.time() 

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

2461 w.setInsertPoint(ins) 

2462 if traceTime: 

2463 delta_t = time.time() - t1 

2464 if delta_t > 0.1: 

2465 g.trace(f"{delta_t:2.3f} sec") 

2466 # Override any changes to the scrollbar setting that might 

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

2468 if spot is not None: 

2469 w.setYScrollPosition(spot) 

2470 v.scrollBarSpot = spot 

2471 # Never call w.see here. 

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

2473 def saveCursorAndScroll(self) -> None: 

2474 

2475 v = self 

2476 c = v.context 

2477 w = c.frame.body 

2478 if not w: 

2479 return 

2480 try: 

2481 v.scrollBarSpot = w.getYScrollPosition() 

2482 v.insertSpot = w.getInsertPoint() 

2483 except AttributeError: 

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

2485 pass 

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

2487 def setAllAncestorAtFileNodesDirty(self) -> None: 

2488 """ 

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

2490 

2491 Modified by EKR. 

2492 """ 

2493 v = self 

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

2495 

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

2497 if v in seen: 

2498 return 

2499 seen.add(v) 

2500 yield v 

2501 for parent_v in v.parents: 

2502 if parent_v not in seen: 

2503 yield from v_and_parents(parent_v) 

2504 

2505 for v2 in v_and_parents(v): 

2506 if v2.isAnyAtFileNode(): 

2507 v2.setDirty() 

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

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

2510 v = self 

2511 if isinstance(s, str): 

2512 v._bodyString = s 

2513 return 

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

2515 self.contentModified() # #1413. 

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

2517 

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

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

2520 # API allows headlines to contain newlines. 

2521 v = self 

2522 if isinstance(s, str): 

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

2524 return 

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

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

2527 self.contentModified() # #1413. 

2528 

2529 initBodyString = setBodyString 

2530 initHeadString = setHeadString 

2531 setHeadText = setHeadString 

2532 setTnodeText = setBodyString 

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

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

2535 v = self 

2536 v.selectionStart = start 

2537 v.selectionLength = length 

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

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

2540 # Does not check for illegal clones! 

2541 v = self 

2542 v._linkAsNthChild(parent_v, n) 

2543 return v 

2544 

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

2546 v = self 

2547 return v.insertAsNthChild(0) 

2548 

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

2550 v = self 

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

2552 

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

2554 v = self 

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

2556 v2 = VNode(v.context) 

2557 v2._linkAsNthChild(v, n) 

2558 assert v.children[n] == v2 

2559 return v2 

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

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

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

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

2564 v = self 

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

2566 parent_v.childrenModified() 

2567 # For a plugin. 

2568 # Update parent_v.children & v.parents. 

2569 parent_v.children.insert(childIndex, v) 

2570 v.parents.append(parent_v) 

2571 # Set zodb changed flags. 

2572 v._p_changed = True 

2573 parent_v._p_changed = True 

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

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

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

2577 v = self 

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

2579 parent_v.childrenModified() 

2580 # For a plugin. 

2581 # Update parent_v.children & v.parents. 

2582 parent_v.children.insert(childIndex, v) 

2583 v.parents.append(parent_v) 

2584 # Set zodb changed flags. 

2585 v._p_changed = True 

2586 parent_v._p_changed = True 

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

2588 # the parents links in the descendant tree. 

2589 # This handles clones properly when undoing a delete. 

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

2591 for child in v.children: 

2592 child._addParentLinks(parent=v) 

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

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

2595 

2596 v = self 

2597 v.parents.append(parent) 

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

2599 for child in v.children: 

2600 child._addParentLinks(parent=v) 

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

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

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

2604 v = self 

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

2606 parent_v.childrenModified() 

2607 assert parent_v.children[childIndex] == v 

2608 del parent_v.children[childIndex] 

2609 if parent_v in v.parents: 

2610 try: 

2611 v.parents.remove(parent_v) 

2612 except ValueError: 

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

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

2615 g.printObj(v.parents) 

2616 v._p_changed = True 

2617 parent_v._p_changed = True 

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

2619 # the parent links in the descendant tree. 

2620 # This handles clones properly when deleting a tree. 

2621 if not v.parents: 

2622 for child in v.children: 

2623 child._cutParentLinks(parent=v) 

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

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

2626 

2627 v = self 

2628 v.parents.remove(parent) 

2629 if not v.parents: 

2630 for child in v.children: 

2631 child._cutParentLinks(parent=v) 

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

2633 def _deleteAllChildren(self) -> None: 

2634 """ 

2635 Delete all children of self. 

2636 

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

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

2639 """ 

2640 v = self 

2641 for v2 in v.children: 

2642 try: 

2643 v2.parents.remove(v) 

2644 except ValueError: 

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

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

2647 g.printObj(v2.parents) 

2648 v.children = [] 

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

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

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

2652 v = self # The child node. 

2653 v._addLink(n, parent_v) 

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

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

2656 def __get_b(self) -> str: 

2657 v = self 

2658 return v.bodyString() 

2659 

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

2661 v = self 

2662 v.setBodyString(val) 

2663 

2664 b = property( 

2665 __get_b, __set_b, 

2666 doc="VNode body string property") 

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

2668 def __get_h(self) -> str: 

2669 v = self 

2670 return v.headString() 

2671 

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

2673 v = self 

2674 v.setHeadString(val) 

2675 

2676 h = property( 

2677 __get_h, __set_h, 

2678 doc="VNode headline string property") 

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

2680 def __get_u(self) -> Dict: 

2681 v = self 

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

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

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

2685 v.unknownAttributes = {} # type:ignore 

2686 return v.unknownAttributes # type:ignore 

2687 

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

2689 v = self 

2690 if val is None: 

2691 if hasattr(v, 'unknownAttributes'): 

2692 delattr(v, 'unknownAttributes') 

2693 elif isinstance(val, dict): 

2694 v.unknownAttributes = val # type:ignore 

2695 else: 

2696 raise ValueError 

2697 

2698 u = property( 

2699 __get_u, __set_u, 

2700 doc="VNode u property") 

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

2702 def __get_gnx(self) -> str: 

2703 v = self 

2704 return v.fileIndex 

2705 

2706 gnx = property( 

2707 __get_gnx, # __set_gnx, 

2708 doc="VNode gnx property") 

2709 #@-others 

2710vnode = VNode # compatibility. 

2711 

2712#@@beautify 

2713#@-others 

2714#@@language python 

2715#@@tabwidth -4 

2716#@@pagewidth 70 

2717#@-leo