minad / tempel

:classical_building: TempEl - Simple templates for Emacs
GNU General Public License v3.0
521 stars 27 forks source link

wiki containing various user contributed snippets #15

Closed Luis-Henriquez-Perez closed 2 years ago

Luis-Henriquez-Perez commented 2 years ago

We mentioned in #9 that it would benefit us to understand and try out the template syntax so that we can make better decisions about whether or not/how to extend it. Perhaps having a place where users can contribute snippets they find useful. We could get ideas by looking at clever snippets others came up with.

minad commented 2 years ago

As I understood @tpeacock19 is preparing some snippets. There could be an official set of snippets in a separate repository, plus interesting examples in the wiki of that repository.

minad commented 2 years ago

One could auto convert the yasnippets. Maybe you want to write a converter @Luis-Henriquez-Perez? But @tpeacock19 and I seem to agree that most of the usual trivial yasnippets are obsolete and one should use the normal capf completion instead. I mean trivial ones like pt=(point). The interesting ones should expand to more text: defun, class boilerplate, larger patterns, larger org snippets etc.

Luis-Henriquez-Perez commented 2 years ago

As I understood @tpeacock19 is preparing some snippets.

Cool. I'll contribute to those when they're out.

One could auto convert the yasnippets. Maybe you want to write a converter @Luis-Henriquez-Perez?

I could give it a shot. However, I'm not as eager to work on this as I am of minad/corfu#94 so I will likely work on the latter first.

most of the usual trivial yasnippets are obsolete and one should use the normal capf completion instead.

Yea I didn't like many of the snippets in yasnippets. I thought they were better than nothing, but that they were not thorough enough (for example I think it would be really cool if the elisp defun snippet would capitalize any arguments you mention in the docstring). When I used yasnippet, I ended up rewriting many of them.

The interesting ones should expand to more text: defun, class boilerplate, larger patterns, larger org snippets etc.

Agreed.

Luis-Henriquez-Perez commented 2 years ago

There could be an official set of snippets in a separate repository, plus interesting examples in the wiki of that repository.

I think this a good idea.

However, I'm not as eager to work on this as I am of

I want to elaborate on why I am not as eager to write the a yasnippet converter so that I am transparent.

The greatest benefit as I see it is that writing it will facilitate the transition greatly for users that are using yasnippet and may wish to switch.

My concerns are that:

  1. I suspect it will be involved--at least if I want to write a thorough and complete converter

I'll need to carefully review the yasnippet syntax (which I haven't looked at in a long time).

If yasnippet syntax is extended in the future, it could break the converter. So as long as yasnippet is in active maintenance, the work would continue.

  1. It will distract me from working on a feature specific for this package.

I would rather work on a feature that improves the quality of this package and makes it more powerful than a "maybe convenience" that depends on whether the user's already using yasnippet.

  1. It might not be entirely worth it

There are a number of users that don't use yasnippet who would not be affected by this change. Or users like me who'd likely end up converting the snippets themselves anyway.

minad commented 2 years ago

@Luis-Henriquez-Perez I agree that there are many reasons to not work on this, and it is understandable if you are not interested. There are also reasons why this would be useful, in particular it would help us figuring out syntax elements which are not (or not yet) supported by Tempel. Of course people may be interested in having ready made template sets and a converter is the fastest way from 0 to 100.

minad commented 2 years ago

I would rather work on a feature that improves the quality of this package and makes it more powerful than a "maybe convenience" that depends on whether the user's already using yasnippet.

Which functionality or feature do you miss in particular? To me it seems as if Tempo is already almost feature complete as soon as we merge your #4.

Luis-Henriquez-Perez commented 2 years ago

Which functionality or feature do you miss in particular? To me it seems as if Tempo is already almost feature complete as soon as we merge your #4.

I want to use the template syntax extensively to determine if it is "rich" enough for writing complex snippets. Or if we have to add to it with new elements. The following are topics that I have been considering as I have written templates.

I want to make sure that it's easy to reference things in lisp forms form use in other fields.

Here is a concrete example--I was actually going to ask about in this before your comment but I was waiting to see if I could figure it out. This is a snippet I've wanted for creating an org id link. It should prompt the user for a heading with org-refile-get-location, then based on the heading the user provides it will create a link with the ID of that heading and that's named after that heading.

I want the form referenced by the variable id to return the id of the heading but I also want to keep a reference to file so I can use it in the form bound to headline. I'm not sure how to do this with the current syntax. Maybe I'm missing something but it's not obvious to me.

(let* ((id `(-let [(_ file _ point) (org-refile-get-location "id")]
         (with-current-buffer (get-file-buffer file) (org-id-get point))))
      (headline `(with-current-buffer file (goto-char point) (oo-org-get-headline)))
      (template `("[[id:" (p ,id) "]["  ,headline  "]" (insert "]" (p "")))))
  (tempel-define-template 'idlk 'text-mode template))

While I love that the tempel syntax is in lisp, sometimes it gets rather cluttered and it does not format well. It's to be expected because it is basically a list of atoms and lists. However, sometimes it makes the structure of the template difficult to see at a glance. That's why I wrote the example above the way I did, inserting the lisp forms via backquotes (imagine what it would look like if I didn't). Maybe this should be addressed somehow. Or maybe nothing should be done. I don't know.

I thought it would be interesting if templates supported destructuring names.

So, for example, (p PROMPT coordinates) could be (p PROMPT (x y)) so you don't have to do (car coordinates) and (cadr coordinates) yourself.

minad commented 2 years ago
  1. Referencing: I would say it is easy to reference other vars, e.g., (s var). The example you've shown does not look correct to me. Does it work? Is there not some Org built-in functionality to insert links you could use instead? Generally there seems to be a thin line on when to use templates. If you write more logic than text, better write an elisp function.

  2. Formatting: I would not do anything. Honestly I find the Tempo syntax quite concise. Compare

(class "public class " (p (file-name-base (or (buffer-file-name) (buffer-name)))) " {" n> r> n "}")

with the corresponding textmate/yasnippet. But of course Tempo syntax is not for everyone. I would say it is meant for people who like the Emacs ways and often write and tweak Elisp.

  1. Destructuring: At first this seems like a good idea, but when taking a closer look it does not hold up. First I'd like to avoid extensions if not strictly needed. I assume that destructuring would be used rarely. Second it only makes sense with noinsert, for exampl (p "Prompt: " (x y) noinsert), since if the value is inserted it must be a string. But you can add a user element to your init.el, which translates (let (x y) some-expression) appropriately. See tempel-user-elements.
Luis-Henriquez-Perez commented 2 years ago

Does it work?

That example does not work. It's purpose was to illustrate what I could not do.

Is there not some Org built-in functionality to insert links you could use instead?

Not any that does exactly this. And even if there were, I can't see how it would be more efficient than the snippet I described (I find org-insert-link very inefficient and planned to do all link insertion via snippets).

Generally there seems to be a thin line on when to use templates. If you write more logic than text, better write an elisp function.

I do not see why a snippet would/should not be able to do what I want. The ability to reference variables in lisp expressions does not seem (to me) otherworldly or inappropriate for a snippet to do.

I would say it is easy to reference other vars, e.g., (s var)

Perhaps I did not communicate my point well. I'll try to illustrate with a working example (a simplified variant of the above) of something I can do.

(let* ((id `(-let [(_ file _ point) (org-refile-get-location "id")]
          (with-current-buffer (get-file-buffer file) (org-id-get point))))
       (template `("[[id:" (p ,id id) "]["  id  "]]")))
  (push `(lambda () (when (derived-mode-p 'text-mode) '(,(cons 'idlk template))))
    tempel-template-sources))

With a sample heading I get the result.

[[id:fbe84f98-881e-4cc3-a47f-939e917c0601][fbe84f98-881e-4cc3-a47f-939e917c0601]]

My question is: what if I wanted to store file in the form (-let [(_ file _ point) ...) instead of (or maybe in addition) to what the form evaluates to--namely the id? How would I do that? The variable I want to store is in the lisp expression. I don't see how I can use (s var) for this. I want to store file and point so I can use it to get the headline via (with-current-buffer file (goto-char point) (oo-org-get-headline)).

e.g.

(does not work)

`("[[id:" (p ,id id) "][" (with-current-buffer (get-file-buffer file) (goto-char (point)) (org-get-heading)) "]]")
Luis-Henriquez-Perez commented 2 years ago

If it's true that there is no way to achieve this via (s var) (again, maybe I missed some clever way) the easiest way I can think of to implement this is via a variable the user can use that's let bound during snippet expansion such as tempel-stored-variables. That way from a lisp expression the user could push any additional variables they want to store via (push (cons 'var-I-want-to-store value-of-var-I-want-to-store) tempel-stored-variables).

minad commented 2 years ago

If you want to access multiple values, you have to do it as follows, since we lack destructuring and binding multiple variables.

(p (list val1 val2 val3) var noinsert)
...
(car var)
...
(cadr val)
...
Luis-Henriquez-Perez commented 2 years ago

you have to do it as follows, since we lack destructuring and binding multiple variables.

Ok this is good. Below is a working example with the approach you suggested. I think this is fine for now. There is room for improvement with destructuring and the let tempo-user-element you suggested--but the main thing is it works.

(let* ((form `(-let* ((org-refile-use-outline-path t)
              ((_ file _ point) (org-refile-get-location "id"))
              (id (with-current-buffer (get-file-buffer file) (org-id-get point))))
        (list file point id)))
       (heading `(with-current-buffer (get-file-buffer (cl-first vars))
           (save-excursion (goto-char (cl-second vars)) (org-get-heading))))
       (template `((p ,form vars t) "[[id:" (cl-third vars) "][" ,heading "]]")))
  (push `(lambda () (when (derived-mode-p 'text-mode) '(,(cons 'idlk template))))
    tempel-template-sources))

I'd like to avoid extensions if not strictly needed.

Destructuring is a bit of a pandora's box for now. I agree with you that I don't think it's a good idea to implement destructuring for this package alone. This has been on my mind long before I started working on tempel: but I think there is a need for a "generic" destructuring emacs package that provides a extendable destructuring that can be used in several different packages. But this is another story.

I assume that destructuring would be used rarely.

Seeing this example, I think I would use it frequently. But it is clear we have different ideas/interpretations/preferences of how to use the snippets--which is good because we can represent different user perspectives.

But you can add a user element to your init.el, which translates (let (x y) some-expression) appropriately. See tempel-user-elements.

I am very interested in this. I'll look into it.

minad commented 2 years ago

Destructuring is a bit of a pandora's box for now. I agree with you that I don't think it's a good idea to implement destructuring for this package alone. This has been on my mind long before I started working on tempel: but I think there is a need for a "generic" destructuring emacs package that provides a extendable destructuring that can be used in several different packages. But this is another story.

There is no need for another destructuring package. Emacs supports pcase out of the box and cl-lib also implements destructuring. It seems, that the third-party dash package also provides a form of destructuring.

Seeing this example, I think I would use it frequently. But it is clear we have different ideas/interpretations/preferences of how to use the snippets--which is good because we can represent different user perspectives.

Sure. What makes me skeptical about your example is that there is almost no snippet in the snippet. Is is almost completely elisp code. At this point I tend to prefer plain functions which are easier to read and to write. Compare this with the simpler snippets from the tempel README. I've also skimmed a bit through the usual yasnippets and I would say that 99% of them are simple or of the level of the snippets from the tempel README.

Luis-Henriquez-Perez commented 2 years ago

There is no need for another destructuring package.

I disagree. But I can understand why you would think this. Yes, there is pcase and dash and cl-lib but there are some kinds of things I would like to do that are not possible with any of those packages.

For example, I'd like one-or-more, zero-or-more, (>= N) as well as optional--basically all the things you can do with regexps, but that work with predicates instead.

For example (a (one-or-more b) (optional c) d d) would match:

(a b d d) (a b b d d) (a b c d d) (a b b b c d d)

etc.

I even asked about extending pcase hoping that maybe I could add optional features to it, but it is crazy difficult. And doesn't work out well at the end.

What makes me skeptical about your example is that there is almost no snippet in the snippet. Is is almost completely elisp code. At this point I tend to prefer plain functions which are easier to read and to write.

I'd have to re-implement the field/default values snippet logic if I wrote my own functions, as well as the creating a capf and setting up the trigger for it to expand. I might agree with you if I always wanted a link of the form [[id:my-id][heading-name]], but above I want the heading name to be the "default" name of the link, but I might want to have my own custom name also instead.

It is a more complex snippet, but I think it should still be a snippet.

and I would say that 99% of them are simple or of the level of the snippets from the tempel README.

I'm assuming that you're referring to the snippets in [yasnippet-snippets](https://github.com/AndreaCrotti/yasnippet-snippets). I find the snippets in this package extremely lacking. Perhaps, not to make things too opinionated they made very bland, simple snippets. They are better than nothing, certainly. But I want snippets to do as much for me as possible.

minad commented 2 years ago

I disagree. But I can understand why you would think this.

My reasons are different than the ones you assume. Maybe pcase is lacking. But there is no point in splitting the basic Emcs library ecosystem to introduce a better destructuring. The current destructuring is good enough for most cases. For example I am not happy with having s, f, ht, dash, seq, cl-lib, pcase and so on. It is better to work with the defaults and as I argue they are good enough. If not, make proposals to extend it. These have happened in the past. In the worst case, you don't get your desired better syntax and you end up with slightly more verbose code. But I've written quite a few Emacs packages, and I don't think that Elisp is really lacking on that front. Elisp is overall not a bad language. I don't know where you are coming from, maybe Clojure? There are other parts which are more seriously lacking, for example missing string functions or worker threads or async features to communicate with workers. Destructuring is a minor language feature in comparison.

Luis-Henriquez-Perez commented 2 years ago

My reasons are different than the ones you assume.

I just mean I've gotten a similar responses when I've mentioned this.

The current destructuring is good enough for most cases.

It may be useful for the majority of cases but it is not good enough for my use cases. There are several macro's I've written where I have wanted better pattern-matching/destructing.

Elisp is overall not a bad language.

Yes, I completely agree. Considering how old it is, and especially comparing it to something like vimscript, it's clear that we should extremely grateful it is as good as it is. And crucially, it's a lisp--so we can add special language constructs.

For example I am not happy with having s, f, ht, dash, seq, cl-lib, pcase and so on. It is better to work with the defaults and as I argue they are good enough.

I would agree that they are good enough. The job can get done with/without these packages--which you've demonstrated with the great packages you've written.

However, I believe there value in abstracting common syntax patterns. I detest repeating common idioms. Even as I was writing my PR for this package, for example, I was dying to use something like loopy whenever I would write the let dolist reverse idiom. Now, it has to be said we're referring to different contexts.

In the context of writing packages, I can see the appeal in wanting to have a clear delineated standard and don't want to introduce too much complexity so code is easy to introspect and predictable. And other packages could introduce bugs and so on.

If not, make proposals to extend it. These have happened in the past. In the worst case, you don't get your desired better syntax and you end up with slightly more verbose code.

I'm not sure about this. All else the same, I would prefer contributing to an existing project over creating a new one, of course. But some problematic issues are how easy it is to extend pcase to do these things--doesn't look easy. And even whether pcase's design is appropriate for this. Also, I don't expect my opinion/desire for this is shared by the emacs maintainers. And then there's the greater barrier to entry of contributions and being tied to Emacs's development cycle. It feels more flexible just to make a package--if others don't like it or feel the current destructuring is good enough, they don't have to use it.

minad commented 2 years ago

I just mean I've gotten a similar responses when I've mentioned this.

This means that you should probably rethink your opinion.

It may be useful for the majority of cases but it is not good enough for my use cases. There are several macro's I've written where I have wanted better pattern-matching/destructing.

I don't believe this. pcase is quite good. And in cases where it is not good enough you can always resort to car, cdr, cadr, ... Of course you can argue that you want to destructure all kinds of obscurely nested datastructures and that the current destructuring is not powerful enough for that. But for every language feature one introduces, the complexity of the language goes up. So it may make sense to draw a line somewhere and not go further.

Regarding pcase: Compare https://github.com/emacs-mirror/emacs/blob/bef9fcc999af5ed5524990c86968be9f9c4497ea/lisp/tempo.el#L321-L393 with

https://github.com/minad/tempel/blob/3054bbfdbc0d4c82be32ca05fc9c6be0473fd7ed/tempel.el#L274-L299

For the kind of simple destructuring I am doing here, pcase is good. And I argue that it covers most of the scenarios.

Even as I was writing my PR for this package, for example, I was dying to use something like loopy whenever I would write the let dolist reverse idiom. Now, it has to be said we're referring to different contexts.

Use cl-loop? loopy is just a nicer cl-loop. I also prefer the loopy syntax, however cl-loop is what we've got and it does not make sense to add a dependency only to write a slightly nicer loop.

I'm not sure about this. All else the same, I would prefer contributing to an existing project over creating a new one, of course. But some problematic issues are how easy it is to extend pcase to do these things--doesn't look easy. And even whether pcase's design is appropriate for this. Also, I don't expect my opinion/desire for this is shared by the emacs maintainers. And then there's the greater barrier to entry of contributions and being tied to Emacs's development cycle. It feels more flexible just to make a package--if others don't like it or feel the current destructuring is good enough, they don't have to use it.

Yes, I know. Contributing upstring is nothing but a pain. But I still argue against the creation of library-level packages as long as they have no reasonable chance of adoption, or when they are only simple competitors to existing Emacs core functionality (loopy vs cl-loop, fancy-destructuring vs pcase, dash vs cl-lib). Packages like s, dash etc only lead to an ecosystem split, which I am not fond of. It leads to different packages written in different styles. I'd prefer if more packages were a bit more idiomatic and keeping things simple.

The situation is different if you create an algorithms or datastructure package, let's say for graphs, for trees, persistent datastructures, special search or graph algorithms, etc. Then these can go to a separate ELPA package, and maybe it will see larger adoption, or will even get adopted by Emacs itself. The difference is that such a package would offer additional value beyond nicer syntax.

Luis-Henriquez-Perez commented 2 years ago

I genuinely enjoy this dicsussion and I would love to continue it. You have written well-thought-out replies that deserve well-thought anwers. However, I can't help but noticed it has veered a bit of track from the topic of the issue and I don't want to fill the issue with many unrelated--or vaguely related--comments. I would be happy to continue this via email or some other means if you'd like?

minad commented 2 years ago

Sure, feel free to drop me a mail.

minad commented 2 years ago

Btw, there exist many *-snippet packages on MELPA, which provide all kinds of special snippets tailored for specific languages. These packages seems to go far beyond what the yasnippet-snippets offer. So maybe there is more value in a converter than we suspected? For reference some links:

Luis-Henriquez-Perez commented 2 years ago

These packages seems to go far beyond what the yasnippet-snippets offer. So maybe there is more value in a converter than we suspected?

I don't deny there is value in a converter. I am concerned that the tempel syntax is still not sufficiently fleshed out--just today I created #23. And there is at least one more issue I plan to create concerning the capabilities of the template syntax. The wiki of tempel templates is still in the works. I've been creating more snippets and with each one I'm learning more about the syntax.

My thoughts are that the syntax should be solid and I should have a good understanding of the syntax before writing a converter for that syntax. However, a good first step--which I'm already doing--is to peruse snippet repos you've mentioned (thanks for listing them) to search for interesting snippets. To me "uninteresting" means "this is easy to do with tempel". I think you make a good point when you suggest "it would help us figuring out syntax elements which are not (or not yet) supported by Tempel".

Besides this, I want prioritize making progress with corfu-indexed today.

minad commented 2 years ago

I opened the wiki: https://github.com/minad/tempel/wiki