I apologize if I have just made unnecessary noise with my post. It wasn't my intention.
> I don't see how offline data structures help recreate clones. How do they simulate Leo's low-level VNode operations? In order to see, you have to be willing to look first. We had argued about this subject several times in the past and I've never achieved to explain my idea well enough, so I've always felt in the end as not being understood at all. To me it looks like in the beginning Edward had an excellent idea of allowing clones in the outlines, but the first implementation was terrible. That was before node unification. Later, Edward has found a much better way to create and represent clones, which was great, but he became overly attached to this new solution. To me it looks a lot like the hypothetical situation of a man who had a horse-drawn carriage with wooden wheels for a while and then one day he discovers pneumatic wheels. It was a great improvement but at the same time this improvement became a wall preventing him to see or to even look for any other improvements. This improvement made him so happy and satisfied that he got so attached to this idea that no other idea could penetrate his mind ever since. Even when someone has discovered a way to make a flying car or floating car, the man wouldn't want to consider any idea which would lead him to part with the pneumatic wheels. Yes they were a great invention at the time, but now there are many other inventions to consider and some of them can make traveling so much faster and safer. In order to fly, one must leave the old car behind and jump in the plane. It can be scary at the beginning, but later it becomes natural. I believe that every technical decision should be made by exploring all tradeoffs related to it. Almost every decision gains you something and at the same time takes away from you something else, it costs you something. The design process should be based on the search for the best possible solution by comparing the gains and costs. It is not always the cheapest solution that is the best. Sometimes, the solution which has some initial costs can save you the tons of money in the future. In my mind, the fact that it is so easy to create a clone in Leo just by linking two existing v nodes isn't worth enough to let it make every other operation costly, complex and inefficient. I would argue that Leo would be greatly improved in so many other areas if it trades this "clone making simplicity" for the ability to make undo, redo, drawing, testing and some other parts of code much simpler and more efficient. Given in how many places throughout the Leo's code base it is necessary to check if the position is still valid or not, I would argue that just to be able to have stable, simple and incorruptible positions in the outline is worth enough for switching to the new data model. This alone would make so many simplifications throughout the Leo's code base. Having the simple undo/redo, being able to simplify outline drawing would come as a bonus. And if the library solves the clone creation in more than just two lines of code is it really worth arguing that some simplicity is lost? When the new model provides the function that creates clones reliably and predictably, why would you still insist on having the ability to make clones yourself by making two simple links? Please don't be offended by anything I wrote because it wasn't my intention to disrespect you or your work in any way. I am just calling you to open your eyes and see the brave new world out there beyond the existing implementation. The present solution to the representation of clones in the outline is just one of many possible solutions. It was a great solution once but there is so much evidence now that it isn't the best one (even if it remains the simplest one). Try to detach yourself from the current implementation and you might find that there are other better solutions to the same problem. Do not compare just the complexity in the clone representation. Instead, you should compare overall complexity including the complexity of writing tests, drawing outline code, undo/redo code, initialization code,... and I am sure you'll see that there are simpler solutions. Imagine that you have a module `m` that provides you with all necessary outline operations, traversals, queries... Let's say you wish to load a leo file and get the outline along with all the external files (@clean, @thin, @file, @edit, ...). You just need to make a single call `t = m.load_leo(filename)`. Now that you have the full outline in t, you may wish to iterate over all the nodes. You can write something like `for lev, p, v in m.iterate_nodes(t)` and you get the level, the position and v node in the outline order. The positions that were given to you are stable. You can later ask for the node at given position by calling `lev, v = m.node_at(t, p)`. Let's say you wish to move up, right, left or down the node at position p. You would use something like `undodata = m.move_up(t, p)`. If the move was legal, the outline has been changed and the undo data contains data necessary to undo the operation. If the move was illegal than the outline has not been changed and the undo data is None. After the outline was successfully changed all the positions are still valid. When you delete a node using `udata = m.delete_node_at(t, p)`, only the position p and all the positions inside the subtree will become invalid while all other positions in the outline remain still valid and unchanged. If you undo the delete operation the position p and its subtree positions will be valid again. Let's say you wish to save all the external files. You use the following function: `for path, content in m.find_filenodes(t)` and you'll get all the file names with the content of each file. If you wish to make a clone of the node at position p, you would use `udata, p1 = m.clone_node_at(t, p)` If you want to promote or demote node, you would use `udata = m.promote_node_at(t, p)`. If you want to insert a new node, you would use `udata, p1 = m.insert_node_at(t, p)`. If you wish to insert one outline into the other retaining clones, you would use `udata, p1 = m.insert_outline_retaining_clones(t, p, t1)` If you want to insert the outline with newly generated indices you would use `udata, p1 = m.insert_outline_copy(t, p, t1)` If you wish to change headline or the body you would use something like `udata = m.set_h(t, p, h)` or `udata = m.set_b(t, p, b)` If you need to iterate children or subtree of any given node, you would use something like `for lev, p1, v in m.children(t, p)` or `for lev, p1, v in m.subtree(t, p)` or `for lev, p1, v in m.unique_nodes(t)`. Now, let's say you want to draw the outline using the qTreeWidget. You would use something like: `m.attach_widget(t, p, qtw)` and the qtw widget will be populated with the outline t and position p would be drawn as selected. Now, whenever you modify outline using functions in module m, this widget will be updated accordingly. When you click in any node you'll get the event with the corresponding position p. Finally when you wish to detach the widget from the model you would use something like `m.detach_widget(t, qtw)`. You can also call `m.set_hoist(t, p)` or `m.dehoist(t, p)` and the widget would reflect those views. You can also call `udata = m.expand_node_at(t, p)` and `udata = m.contract_node_at(t, p)` as well as `udata = m.expand_to_level(t, p, lev)` and any other expand/contract command you can think of. The module itself is tested using hypothesis to generate thousands and thousands of pseudorandom sequences of valid outline operations, than undoing and again redoing each of them checking the correctness of the outline before and after each operation. And if by any chance sometimes in the future hypothesis find the sequence of the operations that lead to the invalid outline, you would get 100% reproducible test. That makes fixing bugs much easier. Now, try to remember how many times have you written some piece of code and a year later found out that code couldn't possibly work (or more often it works, but you cant figure out why it works) and then you rewrite it again to make it simpler and more elegant. How many different kinds of testing helpers have you written until now and yet you have recently discovered that the tests could be much improved for the tenth time... If you just look at the history of this forum, you'll find that each of the following code areas: testing, importing, undo/redo, drawing, batching the outline updates, tree selecting, initialization, loading and reloading settings, themes. Many of these code areas have their own complexity, but I am sure that you would agree that each one of them has at least to deal with the position fragility and therefore could be simplified if the positions were more stable. Also the code in each one of them can be easier and more thoroughly tested if one could instantiate vnode and position simply and without causing any hidden side effects which is impossible with the current implementation. What if all these problems can be fully (or partially) avoided by using module m? Wouldn't it be nice to have such module? Well I have written this module several times so far each time a bit better than the last time. The features that were described in this post are from the latest version. This module is sitting on my computer (and on github too) waiting for the chance to be used and useful. The last commit was a year ago, in July 2022. > I don't see how offline data structures help recreate clones. How do they simulate Leo's low-level VNode operations? Have you look at it? If not, then no wonder you didn't see how it can help. If you look and try to understand how it works, and if you need some help I could try to explain it in more details. But I didn't get the impression that you even tried to look at it. If you are unwilling to even try to understand how it works, or at least to try to use the code, to experiment a bit, then there will be no words for me to explain it well enough. But if you put some effort in understanding the code, if you try to use it, if you explore and play with it, then I might be able to help you by giving some more details and explanations if necessary. Anyway, I think I've given my best shot at selling you the idea. Now it is up to you. I won't bother you again (* at least a year ;-) unless you ask me to. On Friday, July 14, 2023 at 2:24:17 PM UTC+2 Edward K. Ream wrote: > On Fri, Jul 14 vitalije wrote: > > > > I just wished to illustrate how dangerous and tricky things can get when > we have mutable state buried so deep down in the foundations of Leo's code. > > I agree. That's what PR #3438 > <https://github.com/leo-editor/leo-editor/pull/3438> fixes, as follows: > > > - The undo code for parse-body now uses bespoke undo/redo code. > > > - All commands that previously used the retired "change tree" logic > (u.before/afterChangeTree, etc.) now use the undo/redo insert-node logic. > > > I don't see how offline data structures help recreate clones. How do they > simulate Leo's low-level VNode operations? > > > In contrast, the PR's new undo/redo insert-node logic deletes/inserts a > *single* node, thereby using the low-level operations. This change is > safe. Leonistas insert and delete nodes all the time :-) > > On Félix's advice, PR #3438 clears Leo's undo-related state when > performing undoable commands. This change should fix previous problems > with clones. > > > Edward > -- 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 [email protected]. To view this discussion on the web visit https://groups.google.com/d/msgid/leo-editor/73cb470f-9d96-42c2-9183-28aef10ca522n%40googlegroups.com.
