import leo.core.leoNodes as leoNodes
# already exists in leoCommands.py
...
def new_vnode(self, gnx=None):
c = self
return leoNodes.VNode(context=c, gnx=gnx)O.k. No more complains from me.
Well, I hope you mean, no more complaints about the VNode class ;-)
Every time we look at our code with fresh eyes we see something that could be improved.
Well, I hope you mean, no more complaints about the VNode class ;-)To me it is obvious that we look at the same code and see quite opposite things.
I feel like few last threads that I have started brought nothing good, and yet they made us both spend lots of time, perhaps even spoiled our days.
So, I guess it would have been better if I didn't start them at all.
I can live with the present code. I will try to fix as many bugs as I can. If I ever make something worthy adding to Leo I will share it. But it seems to me that criticizing the code won't help so I will do my best to avoid it.
If you ever look again at the VNode class and see that something isn't right I will be glad to answer any question you may have regarding how I see it. But, I won't start the discussion myself.
Oh my. I do apologize for distressing you.
self.context = context # The context containing context.hiddenRootNode.
# Required so we can compute top-level siblings.
# It is named .context rather than .c to emphasize its limited usage.
Vitalije: > [The VNode class] also knows indirectly about c.fileCommands.gnxDict.
Edward: To my knowledge, this is not true. The VNode class does not in any way become entangled with other classes merely because v.context exists! The VNode class knows nothing about how other classes use gnx's.
import leo.core.leoNodes as leoNodesclass DummyC: def __init__(self): self.hiddenRootNode = Nonedc = DummyC()v = leoNodes.VNode(dc, gnx="dewrwer.213213432244")g.es('ok')
AttributeError: 'DummyC' object has no attribute 'fileCommands'
from collections import defaultdictclass VNode(object): __pool = defaultdict(dict) def __init__(self, context, gnx=None): # in this case context can be just a string for example c.hash() # for backward compatibility if isinstance(context, Commander): context = context.hash() # issue a deprecation warning ... if gnx is None: gnx = make_new_gnx(context) if context: # passing context = None prevents pool polution VNode.__pool[context][gnx] = self
@staticmethod def getGnxDict(ctx): return VNode.__pool[ctx] class FileCommands(object): .... @property def gnxDict(self): return VNode.getGnxDict(self.c.hash())# inside VNode class definitiondef to_list(self): ua = None if self.u: ua = base64.encode(pickle.dumps(self.u)) return [self.gnx, self.h, self.b, ua, self.statusBits, [v.to_list() for v in self.children]]
def copyOutline(self, set_clipboard): set_clipboard(json.dumps(self.to_list()))
# inside VNode class definition
def _setB(self, b): VNode.__pool[self.context][self.gnx][1] = bdef _getB(self): return VNode.__pool[self.context][self.gnx][1]b = property(_getB, _setB)I don't think these issues are because Leo is badly designed or
implemented, I think it's because Leo's nearly twenty years old(?) and
has evolved,
vertical integration between layers makes it hard to develop Leo, from the point of view of understanding code flow and working out where changes can be made.
From a general design principle, I find it extremely jarring to need a
Commands object to instantiate a VNode.
This is the (general) question of layering I've tried to express before.
Back here I was trying to express the idea of layers (I was
calling them levels) within the GUI layer:
https://groups.google.com/d/topic/leo-editor/Cgzy6CYGJ8w/discussion
But the GUI layer itself is just one layer in a higher lever set of
layers I started to try and list a couple of days ago:
- node storage and mutation
- outline storage / representation / manipulation
- outline loading / session stuff (Commands object here?)
- importers here? or as plugins?
- collection of outlines
- maybe now the GUI layer (with sub-layers as in above linked thread)
- plug-ins and FNMW like paste-as-xml
Not suggesting this is complete or optimal or anything. Key point is
that things higher on the list know nothing of things lower on the list.
Nodes knowing about outlines doesn't ruin Leo, perhaps that has no
impact at all from a user perspective. But I do think a lot of the
vertical integration between layers makes it hard to develop Leo,
from the point of view of understanding code flow and working out where
changes can be made.
It seems there's a similar cross linking of layers in the Find
code.
I'd expect clone-find-all, mark-finds, regular user interaction
type find etc. to call a generic find function that returns a list or
generator and is completely ignorant of the client code's use of the
result, the call to the find code includes kwargs etc. indicating what
is to be done with the found nodes, and I would have needed to add my
action in with the existing options.
Which doesn't work well for a plugin, so I just went with a more simplistic recursive find.
I don't think these issues are because Leo is badly designed or
implemented, I think it's because Leo's nearly twenty years old(?) and
has evolved, perhaps from something unusual (a literate code editor) to
something quite unique (a tree based information manager / environment).
So GUI startup for example. Leo used to be a three widget app. - tree,
log, and body. Then over time a bunch more widgets got jammed into the
log pane, and suddenly startup logging and plugins loading widgets is
interrelated. And it works fine, it's just hard to change. Who could
have known, back at the beginning, that managing a bunch more widgets
no one had thought of could be a consideration?
Finally though another point that I think Vitalije also touches on -
these discussions are not unhealthy, but they're not really productive
either, unless there's serious interest in major restructuring.
I think we'd really need to consider whether these changes could be
made within Leo, or more efficiently as LeoII - either way is a scary
amount of work.
Kind of a long email to wrap up by questioning the productivity of the
discussion, but oh well :-)
Because we are not sure what and where can be broken when we change something, most often we are fixing bugs using just minor tweaks, adding guards here and there, adding more kwargs. That strategy may suffice for a first-aid. But in long terms it accumulates more and more complexity and over twenty years we have what we have.
I think it might be wise (at least once in a while) to invest some time in serious refactorings aimed at reducing accumulated complexity.
Keeping backward compatibility wherever is possible is great. But I wish that we stay open also for the cases when breaking backward compatibility in certain areas can bring great reduce of complexity.
In such situations, perhaps we may ask users to vote or to present their reasons against breaking backward compatibility.
I don't think it is wise to keep backward compatibility forever.
If there is a chance to improve code quality or/and its readability by breaking backward compatibility I would suggest keeping it for some limited time, and announcing that it will be dropped in one of the next releases.
1. It would be crazy to change the API of the Position and VNode classes. The payoff would not remotely justify a fork in Leo's code base. Do not go there.Imo, it is tragic that python decided to fork itself into python 3. The pain will be eternal. There were other alternatives (from future import x). Forking python was a huge blunder. Do not repeat that mistake!
Have you looked in the code examples I gave in my previous message.
Moving gnxDict from c.fileCommands to VNode itself and keeping c.fileCommands.gnxDict as a property that links to VNode would be 100% backward compatible and yet it would allow instantiation of VNode without commander instance?
To me having most basic data element that can't be instantiated on its own is a big issue. While on the other side you think it is not a big deal.
However if we examine the VNode class, we would find that c.hiddenRootNode has been mentioned only once: in method findAllPotentiallyDirtyNodes where it is used only to be excluded from the results of this method.
There are some places in Leo code where the execution path depends on whether we have a brand new vnode or vnode which is already known (i.e. clone).
And test to distinguish those two cases is the presence or absence of the node in c.fileCommands.gnxDict. In some cases it doesn't matter if the node is already attached to the main tree or not. But in some cases it matters. Just instantiating a vnode creates the node instance which is present in gnxDict, but is not part of the tree yet. This can lead to mysterious bugs, that is hard to spot.
There are cases when one might want to have vnode with perhaps some children and grandchildren to keep around in detached state. For example settings whose values are subtrees. Also when building and transforming tree it is sometimes useful to build it outside of the main tree without disturbing gnxDict.
It also seem to me that the relation between commander and vnode violates logic. There should be no commander without hiddenRootNode and yet node can't be created without commander instance. That causes complications in initialization process which is complex enough on its own.
Now, how this situation could be avoided:
This is backward compatible. All scripts that use c.fileCommands.gnxDict can continue to work. However if one needs to build temporary tree of nodes it can be done cleanly and easily.
Pasting can be also method of vnode as I have demonstrated it in the prototype recently.
Both, copyOutline and pasteOutline would be completely self contained and the way they work will be completely hidden from the world outside VNode class.
We can go even further if we like.
The perception of productivity is probably partly tied to cost -
discussions like this and previous similar ones often consume 100% of
the time I have available to work on Leo in a day, so they need
valuable tangible outcomes to justify themselves.