22

Towards a polymorphic extensions implementation · Issue #677 · Alexander-Miller/...

 4 years ago
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.
neoserver,ios ssh client

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 up

New 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 funcall is just as fast as a normal function call.

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.

Copy link

Owner

Author

Alexander-Miller commented on 20 May

ping @yyoncho As a heavy user of extensions I am sure you have plenty of ideas how things can be improved.

Copy link

Contributor

yyoncho commented on 21 May

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.

Copy link

Owner

Author

Alexander-Miller commented on 21 May

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.

Copy link

Contributor

yyoncho commented on 21 May

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])))

Copy link

Owner

Author

Alexander-Miller commented on 21 May

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.

Copy link

Contributor

yyoncho commented on 21 May

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.

I do that something like that in lsp-treemacs-render in current implementation - but it is wrapped in the lsp-treemacs-render.

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?

The async part is challenging, especially for the functionality like expand all.

Copy link

Owner

Author

Alexander-Miller commented on 31 May

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)))

Copy link

Owner

Author

Alexander-Miller commented 17 days ago

It's been quiet here for a long time, so I'd like to show a small progress update:

async

It's just a test implementation for now, but I am not far off from having something I can upload for you to test.

Copy link

Contributor

yyoncho commented 17 days ago

It's no plist, but it will give you a lot more introspective power. You write this:

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.

Copy link

Owner

Author

Alexander-Miller commented 16 days ago

Can you elaborate what is the idea behind having a definition for each node type?

(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.

I am interested in support of open/expanded/no-children state being expressed in node itself, not as part of node definition

Not sure what you mean by that. Can you make an example for what that'd look like?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Assignees

No one assigned

Labels
None yet
Projects

None yet

Milestone

No milestone

Linked pull requests

Successfully merging a pull request may close this issue.

None yet

2 participants

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK