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.
- 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
andTab
, and Emacs will expand it toview-hello-file
: you do not need start withview
. Not so with hippie-expand). See the following picture for firefox URL completion (you do not need start withhttp://
): - Selecting from multiple matches is not instinctive.
Given the file below as an example, say the point is at where
**
is located, when you typeM-/
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. Seehippie-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 pressM-/
.Last but not least, as has been stated in 1), I need start with
he**
, and if what I wanted ishello6
, then 6 times I must press alongM-/
.
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:
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:
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.
- Besides the current buffer, all visible buffers are searched for matches.
Edit: think
try-expand-dabbrev-visible
. - 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
. - 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, becauseM-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.
- 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 matchhello world
, you must change the skeleton tohr
(the last character of the skeleton is not constrained).A skeleton without a right skull is very difficult to complete!3
- Arbitrary single line string can be completed with the 2nd flavor. Not necessarily only expressions!
- 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.
- Use interesting bones as much as possible.
In the above example, the
h
inh6
is not interesting, because everybody has this bone. You can discard it and use6
alone, because onlyhello6
has this interesting bone in it, and it is exactly what you wanted. - 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:
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.
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😊
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.