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.