This is
#1539. I assigned this issue to 6.3 because because of this method's long history of failure.
Yesterday I saw a new approach. If this new approach is sound, and I think it is, I am willing to consider putting the new code in 6.2.
Thought experiment
I considered the possibility the following approach might be the only way:
1. Generate a list of positions meeting some criterion.
2. Delete one of the positions.
3. Regenerate the list of positions meeting the same criterion.
This surely must work. However, c.deletePositionsInList is not privy to the criterion, so a practical way forward is required.
Aha: p.position_after_deleting(p2)
The essence of our woes is that deleting any position P in the outline may affect all the other positions in the list passed to c.deletePositionsInList. Rather than guessing, we must compute the effect of deleting P on all positions in this list.
p.position_after_deleting(p2) will return None if p will not exist after deleting p2. Otherwise, it will return the adjusted value of p.
Calculating this adjusted value is non-trivial, but surely it is possible. Something like this:
- If p2 is p or an ancestor of p, p will no longer exist. Return None.
- Otherwise, len(p.stack) will remain unchanged, as will parent.v for parents in p.stack.
- If p2 is a previous sibling of p_i, (p or one of p's parents), then p_i.childIndex will decrease by 1.
A sound algorithm
The following is untested. Imo, it's ideas are most likely sound.
def deletePositionsInList(self, aList):
"""Delete all positions in the given list."""
c = self
while aList:
# del_p is the next position to be deleted.
del_p = aList.pop()
# Calculate new_positions.
new_positions = []
for p in aList:
new_p = p.position_after_deleting(del_p)
if new_p:
new_positions.append(new_p)
# Delete del_p.
p.doDelete()
# Check the new positions.
for p in new_positions:
assert c.positionExists(p)
# Update aList.
aList = new_positions
# Make sure c.hiddenRootNode always has at least one child.
if not c.hiddenRootNode.children:
v = leoNodes.VNode(context=c)
v._addCopiedLink(childIndex=0, parent_v=c.hiddenRootNode)
# Redraw.
c.selectPosition(c.rootPosition())
Notes:
- aList.pop() ensures that the loop terminates.
- p.position_after_deleting makes it possible to call p.doDelete rather than vnode-based code.
Summary
Since day 1, c.deletePositionsInList has been based on guesswork and wishful thinking. I am completely responsible for this.
p.position_after_deleting promises to put c.deletePositionsInList on a solid foundation. I am willing to consider putting the new code in Leo 6.2, provided a substantial set of unit tests for p.position_after_deleting exists.
I'll attempt p.position_after_deleting later today. It's time for bed.
All comments welcome.
Edward