Towards a polymorphic extensions implementation · Issue #677 · Alexander-Miller/...
source link: https://github.com/Alexander-Miller/treemacs/issues/677
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign upNew issue
Towards a polymorphic extensions implementation #677
Alexander-Miller opened this issue on 20 May · 10 comments
Comments
The extension api is due for an overhaul. It contains many obsolete atavisms that complicate the implementation and by now can be safely deleted. The biggest problem however is the use of macros to generate many specialized functions which greatly complicates development, debugging and introspection. Fortunately the problem space is ideal for an OO-based approach - all the different node types are all just different implementations of the same behavior, declaring their icons, faces query-functions etc. Tracking state is not necessary either, so using static functions will be well enough. I see 2 possible downsides that must be considered: Performance: eieio method calls are about 4 times slower than normal function calls. Should this have an actual impact on performance a workaround can be applied, albeit at the cost of slightly more complex architecture: the node objects methods, instead of directly producing values like their faces, will need to instead output a function. That will eliminate performace concerns, since using Backward compatibility: Most of the rebuild should focus on interior implementation details, but if breakage does turn out to be unavoidable it's probably best to publish the new implementation in a new module with a different name (and delete the old one some time down the road), so packages using the extension api can upgrade whenever they are ready. |
ping @yyoncho As a heavy user of extensions I am sure you have plenty of ideas how things can be improved. |
I personally would vote for a data-driven solution in which the model is readable. We have created a wrapper providing this functionality. The model is just a plist with :icon/:label/:children/:children-async, and related keys. This probably will lead to slower access to the fields, but this is not an issue for us. |
Would that plist have to be descriptive or prescriptive? If it's the former I can produce one as a side effect, or even build something interactive that pretty prints all available info. |
I am not sure I understand the difference between the two in this context, here it a runnable(after you load lsp-treemacs) example: (display-buffer (lsp-treemacs-render '((:key "foo" :label "Root" :icon dir-open :children-async (lambda (_ callback) (run-with-idle-timer 2 nil (lambda (callback) (funcall callback '((:key "foo" :label "Async" :icon dir-open :children ((:key "foo" :label "Sync" :icon dir-open :ret-action ignore :actions (["Right Click selected" ignore]))))))) callback)) :ret-action ignore :actions (["Right Click selected" ignore]))) "Demo" nil nil '(["Right Click Nothing selected" ignore]))) |
I mean for the prescriptive version you would be able to put the plist into some variable, and changing that variable would then change the behavior it represents. So you could change things programmatically, without any need re-eval. But your example is also very interesting. It looks like you have defined a single generic node type whose exact behavior you specify with the plist. Is that right? I'll have to think about how to integrate that into my existing plans. At the very least I would want to natively support async query-functions. |
I do that something like that in lsp-treemacs-render in current implementation - but it is wrapped in the
The async part is challenging, especially for the functionality like expand all. |
Small update: things are going very well so far, though there's plenty of work to do yet - there are many things I can do better and, more importantly, simpler. In the core I went with a lambda-producing struct approach now. It's no plist, but it will give you a lot more introspective power. You write this: (treemacs-define-expandable-node-type buffer-group :closed-icon "x " :open-icon "o " :label (symbol-name item) :face 'font-lock-variable-name-face :key item :children (treemacs--buffers-by-mode (treemacs-button-get node :major-mode)) :child-type 'buffer-leaf :more-properties `(:major-mode ,item)) and get something like this: (defconst treemacs-buffer-group-extension-instance (treemacs-extension->create! :label (lambda (&optional item) "" (ignore item) (symbol-name item)) :key (lambda (&optional item) "" (ignore item) item) :open-icon (lambda (&optional item) "" (ignore item) "o ") :closed-icon (lambda (&optional item) "" (ignore item) "x ") :children (lambda (&optional node) "" (ignore node) (treemacs--buffers-by-mode (treemacs-button-get node :major-mode))) :face (lambda (&optional item) "" (ignore item) 'font-lock-variable-name-face) :more-properties (lambda (item) "" (ignore item) `(:major-mode ,item)) :child-type (lambda nil "" (symbol-value 'treemacs-buffer-leaf-extension-instance)) :open-state (lambda nil "" 'treemacs-buffer-group-open-state) :closed-state (lambda nil "" 'treemacs-buffer-group-closed-state))) |
Can you elaborate what is the idea behind having a definition for each node type? We have line 15+ view with probably 50 different types and for me having to define a nodetype for each entry will be a lot of work and it is not clear what will be the benefit. Also, with plists as model we can create different representations and it won't be coupled to treemacs. E. g. I could create dired like flat browsing with inserting and collapsing the items. I am interested in support of open/expanded/no-children state being expressed in node itself, not as part of node definition due to the fact that there is no open expanded icons for all types and we prefix the icons with a symbol. |
(Some small degree of) static typing and separation of concerns. When you put everything into one type you'd have to constantly dispatch on the actual state for every action - if we open a list of classes go collect the classes, if we open a class go collect its members etc, if we open a method go collect its local variables, and so on for other properties like icons and faces. With the current approach these things would be split into separate node types. I figured that'd be the cleaner approach. It does scale well enough for the kind of extensions I am considering - a buffer list (1 root type, 1 buffer group type, 1 buffer type), a mu4e sidebar (1 root mail account type, 1 mail directory leaf type), a docker sidebar (1 root type for images/containers, 1 leaf type for their instances). At any rate removing boiler plate necessities is part of my agenda as well, I just haven't been advertising and optimizing for it just yet. My intention is to eventually whip the whole api into a shape that requires only a single node definition, or maybe 2 to deal with the necessary entry-point house keeping.
Not sure what you mean by that. Can you make an example for what that'd look like? |
No one assigned
None yet
No milestone
Successfully merging a pull request may close this issue.
None yet
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK