"HTML document title."
nil
"<title>" _ "</title>")
The define-skeleton function sets up the skeletal HTML code to be inserted, and it does this by writing a Lisp function based on the template you pass it. Its first argument is the name of the Lisp function to define, and the next is a documentation string for that function explaining what it inserts. After that comes an optional prompt that can be used to customize the content to be inserted. We don't need any customization, so we leave it as nil to skip the prompt. Finally comes the list of strings to be inserted, and we mark where we want the cursor to end up with "_". (To learn more about the way this skeleton system works, invoke describe-function on insert-skeleton.)
With these changes, our new commands work just like the other insertion tools in HTML mode. Even more than the specific Lisp code that came out of this example, the technique we used to create it is worth learning. If you can develop the skills and habits involved in tracking down an example from the built-in libraries that is close to what you want, and digging into how it works just enough to come up with a variant that solves your problem, you'll be well on your way to becoming the friendly Emacs Lisp guru your friends rely on when they need a cool new trick.
Here is a third example. Let's say you program in C, and you want a Lisp function that counts the number of C function definitions in a file. The following function does the trick; it is somewhat similar to the count-lines-buffer example earlier in the chapter. The function goes through the current buffer looking for (and counting) C function definitions by searching for { at the beginning of a line (admittedly, this simplistic approach assumes a particular and rigid C coding style):
(defun count-functions-buffer ( )
"Count the number of C function definitions in the buffer."
(interactive)
(save-excursion
(goto-char (point-min))
(let ((count 0))
(while (re-search-forward "^{" nil t)
(setq count (1+ count)))
(message "%d functions defined." count))))
The re-search-forward call in this function has two extra arguments; the third (last) of these means "if not found, just return nil, don't signal an error." The second argument must be set to nil, its default, so that the third argument can be supplied.[83]
Now assume we want to bind this function to C-c f in C mode. Here is how we would set the value of c-mode-hook:
(add-hook 'c-mode-hook
'(lambda ( )
(define-key c-mode-map "\C-cf" 'count-functions-buffer)))
Put this code and the function definition given earlier in your .emacs file, and this functionality will be available to you in C mode.
As a final example of mode hooks, we'll make good on a promise from the previous chapter. When discussing C++ mode, we noted that the commands c-forward-into-nomenclature and c-backward-into-nomenclature are included as alternatives to forward-word and backward-word that treat WordsLikeThis as three words instead of one, and that this feature is useful for C++ programmers. The question is how to make the keystrokes that normally invoke forward-word and backward-word invoke the new commands instead.
At first, you might think the answer is simply to create a hook for C++ mode that rebinds M-f and M-b, the default bindings for forward-word and backward-word, to the new commands, like this:
(add-hook 'c++-mode-hook
'(lambda ( )
(define-key c++-mode-map "\ef"
'c-forward-into-nomenclature)
(define-key c++-mode-map "\eb"
'c-backward-into-nomenclature)))
(Notice that we are using c++-mode-map, the local keymap for C++ mode, for our key bindings.) But what if those keys have already been rebound, or what if forward-word and backward-word are also bound to other keystroke sequences (which they usually are anyway)? We need a way to find out what keystrokes are bound to these functions, so that we can reset all of them to the new functions.
Luckily, an obscure function gives us this information, where-is-internal. This function implements the "guts" of the where-is help command, which we will see in Chapter 14. where-is-internal returns a list of keystroke atoms that are bound to the function given as an argument. We can use this list in a while loop to do all of the rebinding necessary. Here is the code:
(add-hook 'c++-mode-hook
'(lambda ( )
(let ((fbinds (where-is-internal 'forward-word))
(bbinds (where-is-internal 'backward-word)))
(while fbinds
(define-key c++-mode-map (car fbinds)
'c-forward-into-nomenclature)
(setq fbinds (cdr fbinds)))
(while bbinds
(define-key c++-mode-map (car bbinds)
'c-backward-into-nomenclature)
(setq bbinds (cdr bbinds))))))
The two lines in the top of the let statement get all of the key bindings of the commands forward-word and backward-word into the local variables fbinds and bbinds, respectively.
After that, there are two while loops that work like the print-stack function of the calculator mode shown earlier in this chapter. This use of while is a very common Lisp programming construct: it iterates through the elements of a list by taking the first element (the car), using it in some way, and deleting it from the list ((setq list (cdr list)). The loop finishes when the list becomes empty (nil), causing the while test to fail.
In this case, the first while loop takes each of the bindings that where-is-internal found for forward-word and creates a binding in C++ mode's local keymap, c++-mode-map, for the new command c-forward-into-nomenclature. The second while loop does the same for backward-word and c-backward-into-nomenclature.
The surrounding code installs these loops as a hook to C++ mode, so that the rebinding takes place only when C++ mode is invoked and is active only in buffers that are in that mode.
One final word about hooks: you may have noticed that some of the mode customizations we have shown in previous chapters include hooks and others do not. For example, the code in the previous chapter to set your preferred C or C++ indentation style included a hook:
83
The second argument to re-search-forward—and other search functions—gives a bound to the search: if given an integer value nil, the default, means don't give the search a bound.