skeleton-complete

Gimme a skeleton, and I'll complete you, like Jerry Maguire does. Or at least lemme make it scratch your back, heeheehee.

Jokes aside, skeleton-complete is my effort to go beyond hippie-expand. With it you can save a lot of typing even more than hippie-expand. To be more specific, it is hippie-expand's try-expand-dabbrev and try-expand-line functions that I want to do better with skeleton-complete.

You can get it on github.

1 What's not good enough with hippie-expand

Emacs completion (aka expand) is great, the hippie-expand is mostly useful.

But I have found it insufficient, for the following reasons.

  1. Hippie-expand requires you to start matching at the beginning.

    But not all interesting (and thus easy to remember and easy to type) components are at the beginning. A lot of software including Emacs itself have started to allow user type a component from anywhere in the final string and match it just fine. For e.g., the anything.el (now renamed helm); Firefox's URL type and match; and Emacs's built in C-h f (where you can type -hello and Tab, and Emacs will expand it to view-hello-file: you do not need start with view. Not so with hippie-expand). See the following picture for firefox URL completion (you do not need start with http://):

    firefox-complete-xx.png

  2. Selecting from multiple matches is not instinctive.

    Given the file below as an example, say the point is at where ** is located, when you type M-/ repeatedly, you will get hello3, 2, 1, and then hello4, 5, 6, in that order (what will you get after hello6 is dependent on what is contained in other buffers of your current Emacs session, what modules you have loaded and symbols you can access, etc. See hippie-expand-try-functions-list. This is NICE and inspiring!).

    hello1
    hello2
    hello3
    he**
    hello4
    hello5
    hello6
    

    Now, the order is not satisfactory to me, I'd like 3, 4, 2, 5, 1, 6 better according roughly to the distance they are to the point.

    And like anything.el, I'd like to see all the matching entries at once, probably over multiple pages, but I can scroll up and down to choose, instead of typing M-/ repeatedly and blindly, not knowing what the next entry will be, or how many times I need to press M-/.

    Last but not least, as has been stated in 1), I need start with he**, and if what I wanted is hello6, then 6 times I must press along M-/.

2 How does skeleton-complete fix it

Taking the above example, if you wanted to get hello6, you need only type the skeleton, the “interesting” bones that we want to flesh out: the h and 6 is enough in this example, see below (only he is changed to h6 compared to above example):

hello1
hello2
hello3
h6**
hello4
hello5
hello6

If you type M-g <return> at the point, you will get hello6 instantly.

2.1 Regexp rewrite

Here's how it works. Skeleton-complete will take the (current-word), here h6, and break this “skeleton” down into bare pieces of bones, h and 6. Then it strings these bones together, and we get h.*6, and behold, this is a regexp! What use do we have regexps? Right, searching. We can search and find 2 matches for h.*6: h6 and hello6. Now the first one does not make much sense as it is the same as the skeleton we started with; so we can happily complete with hello6, and the user is not bothered to select from multiple matches at all!

(In Emacs version 24.3, you will notice in isearch-forward, typing many white spaces has the same effect as one, and it will match any number of contiguous white spaces (except the newline). Déjà vu?)

2.2 Choosing from multiple matches

But of course, often the user still need to choose from multiple matches. Skeleton-complete uses ecomplete.el for presenting the user the matches he/she can choose from:

skeleton-id.png

From the screenshot, you can see that the matches are presented in a order according to their distance to the point. According to principle of locality, the closer the match to the point, the earlier it should appear in the list of choices. In the figure above, displayed first is hyhyhy1 which comes immediately above the point, then the h1 itself should have come, which is below or on the same line as the point, but it has been deleted as it makes no sense as a completion for itself. So next comes hahaha1 which is the next above the point, followed by hihihi1 which is the next below, and so on.

You can use C-n and C-p to scroll up and down the list, or off the ends to the next and previous pages of matches.

2.3 The second flavor of skeleton-complete

The first flavor discussed above is for completing identifiers only. Say I have a long expression, and I will complete the whole expression, once again you type an “interesting” skeleton, and type M-s <return>, it will get fleshed out:

skeleton-exp.png

Here's how it works. First the skeleton is broken down to bare bones, $, h, 1 and }. Then they are stringed to become \\$.*?h.*?1.*?} (note how the $ is quoted because besides being a bone, it's also a “meta”bone; also note the usage of the non-greedy .*?, it is dark magic already, do not make it worse by being greedy1!).

Then all the expressions in the current buffer are matched, and they are listed in the same way as the first flavor (Thus the user should say, “oh, I have seen this before.” and feel easy. Déjà vu2?).

3 Usage notes

There are several tried and proved enhancements in skeleton-complete that you might want to know.

  1. Besides the current buffer, all visible buffers are searched for matches.

    Edit: think try-expand-dabbrev-visible.

  2. If no matches are found in all the visible buffers, the buried buffers are dug up for skeleton bones (but don't sweat, it will stop as soon as the first dark buffer is finished where any match is found).

    Edit: think try-expand-dabbrev-all-buffers.

  3. Because of 1) and the fact that ecomplete.el is used, you can use skeleton-complete in the minibuffer. I made another key binding for the second flavor, M-g x, you probably need use it too, because M-s <return> is often not usable in the minibuffer.

    I mean, you won't get punished with Command attempted to use minibuffer while in minibuffer for digging skeletons in the lowest place of all, the minibuffer.

    Edit: This is also the reason why I have not yet replaced ecomplete.el with anything.el to choose from multiple matches: the latter can't be used in the minibuffer without the error above.

  4. If the first character of a 2nd flavor skeleton is a word constituent character, it must match at beginning of a word.

    For e.g., lr will not match hello world, you must change the skeleton to hr (the last character of the skeleton is not constrained).

    A skeleton without a right skull is very difficult to complete!3

  5. Arbitrary single line string can be completed with the 2nd flavor. Not necessarily only expressions!
  6. By default, the 2nd flavor will find its skeleton by searching backward for the first non-blank character. You can override this by activating the region over your desired skeleton and include white spaces into it.
  7. Use interesting bones as much as possible.

    In the above example, the h in h6 is not interesting, because everybody has this bone. You can discard it and use 6 alone, because only hello6 has this interesting bone in it, and it is exactly what you wanted.

  8. You can use complete and modify if you need input a similar but not exactly same string to an existing one.

4 Bugs

Because the rewrote regexp can be complex, sometimes (but very rarely!) it may seem hung for the 2nd flavor. You can type C-g to quit searching for matches, and examine what is wrong with your skeleton.

Beware of buffers with extremely long lines in it! Those are monsters, don't try to use 2nd flavor skeleton-complete with them, run away!

Footnotes:

1

In the first flavor, either greedy or non-greedy will do. But in the 2nd flaver, I must use non-greedy to give user more control: given $double_hash{$ref1}{$ref2}, greedy ${} and ${}} will both match the whole thing, but the non-greedy version will empower user to choose $double_hash{$ref1} with ${} and $double_hash{$ref1}{$ref2} with ${}} respectively.

2

I typed the 2nd and this Déjà vu with dvu and M-s <return>. And the 1st is typed as D\'ej\`a vu and M-x iso-tex2iso, if you don't know already😊

3

The real reason is, without this constraint, hello world will generate 2 meaningless matches for the skeleton lr and user is likely to get disappointed: llo wor and lo wor. As for the tail, we can easily extend it to the word boundary: llo world and lo world, but for the missing head, let's simply forbid it, because it's also a performance hit by causing these unwanted matches.