Currently Leo decides whether position should be expanded using list of 
expanded positions which is kept as an ivar on the v node instance 
(v.expandedPositions). This schema allows Leo to show some clones as 
expanded while others as collapsed. The problem is that positions in these 
lists can become invalid after some outline changes (like inserting or 
deleting a node).

The other problem is with nodes that are not clones, but have one or more 
ancestors that are clones. Leo currently doesn't allow this positions to 
have separate expanded/collapsed state. Actually user can collapse one of 
such nodes, independently of other instances of same node, but if user 
expands one of those nodes, then all other instances will be expanded too.

Nodes that appear only once in the outline are immune to this kind of 
problems. For such nodes p.v.isExpanded() bit is sufficient to answer the 
question whether position should be expanded or not. So, only when showing 
cloned node and its subtree, Leo needs some way to distinguish between one 
v-node occurrence and another.

We can look at the positions as some kind of labels for v-node occurrences 
in the outline. Different occurrences have different labels (positions). 
But this is not the only possible way to enumerate all different v-node 
occurrences with different labels.

Let's look at different way to make different labels for different 
occurrences of the same v-node. Instead of keeping child index in each 
level, let's keep the number of occurrences of the same v-node among 
previous siblings. This number paired with the gnx of v-node unmistakably 
identifies every child. By joining all ancestor's labels we can have a 
string which can be used as a label for every position. Here is the code to 
calculate the label for any given position:
def make_label(p):
    def it():
        yield from p.stack
        yield p.v, p._childIndex
    def it2():
        root = c.hiddenRootNode
        for v, i in it():
            yield root, v, i
            root = v
    def it3():
        for parv, ch, i in it2():
            j = len([x for x in parv.children[:i] if x == ch])
            yield f'{ch.fileIndex}[{j}]'
    return ' '.join(it3())


This label is more imune to outline changes. The only outline change that 
can cause these labels to become invalid is inserting a clone node or 
deleting one. But in that case we can easily calculate what should be the 
values of new labels after change.

For example:

def inserted_clone(expandedLabels, parentPos, oldParentLabel, index):
    '''Updates expandedLabels set after insertion of a cloned node
    at given index. parentPos is position of parent node, and oldParentLabel
    is label that parentPos had before.
    '''
    par = parentPos.v
    ch = par.children[index]
    j = 0
    changes = []
    for i, v in enumerate(par.children):
        if v != ch: continue
        if index <= i:
            a = f'{oldParentLabel} {v.fileIndex}[{j-1}]'
            b = f'{oldParentLabel} {v.fileIndex}[{j}]'
            oldlabels = set(x for x in expandedLabels if x.startswith(a))
            newlabels = set(x.replace(a, b) for x in oldlabels)
            changes.append(
                ( oldlabels
                , newlabels
                ))
        j += 1
    for a, b in changes:
        expandedLabels -= a
        expandedLabels += b

def deleted_clone(expandedLabels, parentPos, oldParentLabel, index):
    '''Updates expandedLabels set after deletion of a cloned node
    at given index. parentPos is position of parent node, and oldParentLabel
    is label that parentPos had before.
    '''
    par = parentPos.v
    ch = par.children[index]
    j = 0
    changes = []
    for i, v in enumerate(par.children):
        if v != ch: continue
        if index <= i:
            a = f'{oldParentLabel} {v.fileIndex}[{j+1}]'
            b = f'{oldParentLabel} {v.fileIndex}[{j}]'
            oldlabels = set(x for x in expandedLabels if x.startswith(a))
            newlabels = set(x.replace(a, b) for x in oldlabels)
            changes.append(
                ( oldlabels
                , newlabels
                ))
        j += 1
    for a, b in changes:
        expandedLabels -= a
        expandedLabels += b

These two functions can update set of expanded labels after insertion of a 
node that was already among children of the parent node, or when deleting 
node which have clones among its siblings. In any other kind of outline 
change there is no need to update expanded labels. This means cff and cfa 
commands won't need to call inserted_clone function, nor deletion of nodes 
created with those commands would have to call deleted_clone function. Only 
when inserted node appears more than once among its siblings, or when 
deleting such node, labels may change, and only then is necessary to call 
one of these two functions to update set of expanded labels.

In case that user script makes some changes to the outline without calling 
these functions, nothing horrible would happen. The worse thing that could 
happen is that some of the clones would loose their expanded/collapsed 
state. But most of the ordinary nodes will have their expanded state 
preserved.

Your comments.
Vitalije

-- 
You received this message because you are subscribed to the Google Groups 
"leo-editor" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to leo-editor+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/leo-editor/5cf6de23-4e0b-4608-b60c-96d874cccf0ao%40googlegroups.com.

Reply via email to