(or emacs irrelevant)

tiny.el - the little package that could

The Challenge

It all started with a heated discussion with the author of yasnippet over some minor nonsense. In the end, we agreed to disagree, but not before he suggested:

So I hereby challenge you to create this stripped down, no-crap, version of yasnippet. Dub it " tiny is not yasnippet " after your grandiose views and in the glorious unix tradition of recursive acronyms

The Thought Process

Well, doing exactly that would probably be lame, but I really loved the acronym. Somewhere around that time I saw some post about using eval-and-replace, i.e. inserting some Elisp in your non-Elisp buffer and then replacing that code in-place with the result of the eval.

Here's the type of code that I was playing around with:

(mapcar
 (lambda (x) (* x x))
 (number-sequence 1 7))

Then I realized that the code should probably produce a string. Here's a more refined version:

(mapconcat
 (lambda (x)
   (format "hex: 0x%x"
           (* x x)))
 (number-sequence 1 7)
 ";\n")

Loops are a useful thing to have, they are a blind spot of yasnippet, and looping is exactly what the code above does. The parameters for this loop expansion are:

  • integer range start: 1
  • integer range end: 7
  • separator to join the expressions: ";\n"
  • Elisp expression to transform the linear range: (* x x)
  • format expression for the result: "hex: 0x%x"

So ideally, in order to have a package called tiny, I'd like to keep only the parameters and throw away everything else.

The Result

Here's the final result of the shortening, and what tiny-expand would produce:

  • 
    hex: 0x1;
    hex: 0x4;
    hex: 0x9;
    hex: 0x10;
    hex: 0x19;
    hex: 0x24;
    hex: 0x31
    

As you see, it's pretty compact, with only two characters which are not actually the parameters of the template:

  • m signifies the start of the template. I think this way is much better than something like having to mark the template body with a region before expanding. tiny-expand should be called from the end of the snippet, so there's no need to mark the end position.
  • | signifies the end of the Elisp expression and the start of the format string. It can be omitted if your format string starts with a %.

Note also the use of shortened Elisp. You can still use the full thing if you want. Or just use only the closing parens to resolve the ambiguities.

The Demos

Here are some more snippets, you can click on them to see what they expand to. You can also find them and more in the comments section of the source code:

  • 0 1 2 3 4 5 6 7 8 9 10
    

  • 5 6 7 8 9 10
    

  • 5,6,7,8,9,10
    

  • 25 36 49 64 81 100
    

  • 19 24 31 40 51 64
    

  • 0x19 0x24 0x31 0x40 0x51 0x64
    

  • a b c d e f g h i j k l m n o p q r s t u v w x y z
    

  • A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
    

  • a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z
    

  • aa,bb,cc,dd,ee,ff,gg,hh,ii,jj,kk,ll,mm,nn,oo,pp,qq,rr,ss,tt,uu,vv,ww,xx,yy,zz
    

  • aA,bB,cC,dD,eE,fF,gG,hH,iI,jJ,kK,lL,mM,nN,oO,pP,qQ,rR,sS,tT,uU,vV,wW,xX
    

  • aAa,bBb,cCc,dDd,eEe,fFf,gGg,hHh,iIi,jJj,kKk,lLl,mMm,nNn,oOo,pPp,qQq,rRr,sSs,tTt,uUu,vVv,wWw,xXx
    

  • 0 and 0 and 0
    2 and 1 and 1
    4 and 4 and 2
    6 and 9 and 3
    8 and 16 and 4
    10 and 25 and 5
    12 and 36 and 6
    14 and 49 and 7
    16 and 64 and 8
    18 and 81 and 9
    20 and 100 and 10
    

  • 6 8 10 12 14 16 18 20 22 24 26
    

  • 1.0
    2.718281828459045
    7.38905609893065
    20.085536923187668
    54.598150033144236
    148.4131591025766
    403.4287934927351
    1096.6331584284585
    2980.9579870417283
    8103.083927575384
    22026.465794806718
    

  • 00000000002.72
    00000000007.39
    00000000020.09
    00000000054.60
    00000000148.41
    00000000403.43
    00000001096.63
    00000002980.96
    00000008103.08
    00000022026.47
    00000059874.14
    00000162754.79
    00000442413.39
    00001202604.28
    00003269017.37
    00008886110.52
    00024154952.75
    00065659969.14
    00178482300.96
    00485165195.41
    

  • 1 2 4 8 16 32 64 128
    

  • 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07
    

  • 0.00 1.00 2.00 3.00 4.00 5.00 6.00 7.00 8.00 9.00 10.00
    

  • * TODO http://emacsrocks.com/e01.html
    * TODO http://emacsrocks.com/e02.html
    * TODO http://emacsrocks.com/e03.html
    * TODO http://emacsrocks.com/e04.html
    * TODO http://emacsrocks.com/e05.html
    * TODO http://emacsrocks.com/e06.html
    * TODO http://emacsrocks.com/e07.html
    * TODO http://emacsrocks.com/e08.html
    * TODO http://emacsrocks.com/e09.html
    * TODO http://emacsrocks.com/e10.html
    * TODO http://emacsrocks.com/e11.html
    * TODO http://emacsrocks.com/e12.html
    * TODO http://emacsrocks.com/e13.html
    * TODO http://emacsrocks.com/e14.html
    

  • * TODO Wash dog 2
    DEADLINE: <2015-01-01 Thu>
    * TODO Wash dog 3
    DEADLINE: <2015-01-06 Tue>
    * TODO Wash dog 4
    DEADLINE: <2015-01-11 Sun>
    * TODO Wash dog 5
    DEADLINE: <2015-01-16 Fri>
    * TODO Wash dog 6
    DEADLINE: <2015-01-21 Wed>
    * TODO Wash dog 7
    DEADLINE: <2015-01-26 Mon>
    * TODO Wash dog 8
    DEADLINE: <2015-01-31 Sat>
    * TODO Wash dog 9
    DEADLINE: <2015-02-05 Thu>
    * TODO Wash dog 10
    DEADLINE: <2015-02-10 Tue>
    

    You can expand them one-by-one to see what they do. As you can see, Ruby-style interpolation is available in the format string. There's also one special function called date that you can use there. It takes the start date as a string ("Jan 1" in the example) and an integer shift and prints an org-style date.

    The full syntax

    The full syntax for the snippet is:

    m{range start:=0}{separator:= }{range end}{Lisp expr:=indentity}|{format expr:=%d}
    • You always start with m.
    • Then optional range start that defaults to 0.
    • Then optional separator that defaults to a single space.
    • Then mandatory range end.
    • Then optional Lisp expr, that defaults to identity.
    • Then optional format-style string, that defaults to %d. You have to separate it with | if the format string does not start with %. You can also Ruby-style interpolation here, e.g. %(* x x).
    • With the point at the end of the snippet, M-xtiny-expand.

    The Summary

    In the end, tiny lives up to the name, implementing only one snippet that can be used in a variety of ways.

    tiny is not yasnippet