Êíèãà: Learning GNU Emacs, 3rd Edition

11.6 Customizing Existing Modes

11.6 Customizing Existing Modes

Now that you understand some of what goes into programming a major mode, you may decide you want to customize an existing one. Luckily, in most cases, you don't have to worry about changing any mode's existing Lisp code to do this; you may not even have to look at the code. All Emacs major modes have "hooks" for letting you add your own code to them. Appropriately, these are called mode-hooks. Every built-in major mode in Emacs has a mode hook called mode-name-hook, where mode-name is the name of the mode or the function that invokes it. For example, C mode has c-mode-hook, shell mode has shell-mode-hook, etc.

What exactly is a hook? It is a variable whose value is some Lisp code to run when the mode is invoked. When you invoke a mode, you run a Lisp function that typically does many things (e.g., sets up key bindings for special commands, creates buffers and local variables, etc.); the last thing a mode-invoking function usually does is run the mode's hook if it exists. Thus, hooks are "positioned" to give you a chance to override anything the mode's code may have set up. For example, any key bindings you define override the mode's default bindings.

We saw earlier that Lisp code can be used as the value of a Lisp variable; this use comes in handy when you create hooks. Before we show you exactly how to create a hook, we need to introduce yet another Lisp primitive function: lambda. lambda is very much like defun in that it is used to define functions; the difference is that lambda defines functions that don't have names (or, in Lisp parlance, "anonymous functions"). The format of lambda is:

(lambda (args)
  code)

where args are arguments to the function and code is the body of the function. To assign a lambda function as the value of a variable, you need to "quote" it to prevent it from being evaluated (run). That is, you use the form:

(setq var-name
      '(lambda ( )
         code))

Therefore, to create code for a mode hook, you could use the form:

(setq mode-name-hook
      '(lambda ( )
         code for mode hook))

However, it's quite possible that the mode you want to customize already has hooks defined. If you use the setq form, you override whatever hooks already exist. To avoid this, you can use the function add-hook instead:

(add-hook 'mode-name-hook
       '(lambda ( )
  code for mode hook))

The most common thing done with mode hooks is to change one or more of the key bindings for a mode's special commands. Here is an example: in Chapter 7 we saw that picture mode is a useful tool for creating simple line drawings. Several commands in picture mode set the default drawing direction. The command to set the direction to "down," picture-movement-down, is bound to C-c . (C-c followed by a period). This is not as mnemonic a binding as C-c < for picture-movement-left or C-c ^ for picture-movement-up, so let's say you want to make C-c v the binding for picture-movement-down instead. The keymap for picture mode is, not surprisingly, called picture-mode-map, so the code you need to set this key binding is this:

(define-key picture-mode-map "C-cv" 'picture-movement-down)

The hook for picture mode is called edit-picture-hook (because edit-picture is the command that invokes picture mode). So, to put this code into the hook for picture mode, the following should go into your .emacs file:

(add-hook 'edit-picture-hook
      '(lambda ( )
         (define-key picture-mode-map "C-cv" 'picture-movement-down)))

This instruction creates a lambda function with the one key binding command as its body. Then, whenever you enter picture mode (starting with the next time you invoke Emacs), this binding will be in effect.

As a slightly more complex example, let's say you create a lot of HTML pages. You use HTML mode (see Chapter 8), but you find that there are no Emacs commands that enter standard head and title tags, despite the fact that the help text reminds you of their importance. You want to write your own functions to insert these strings, and you want to bind them to keystrokes in HTML mode.

To do this, you first need to write the functions that insert the tag strings. The simplest approach would just be to insert the text:

(defun html-head ( )
  (interactive)
  (insert "<head></head>"))
(defun html-title( )
  (interactive)
  (insert "<title></title>"))

Remember that the calls to (interactive) are necessary so that Emacs can use these functions as user commands.

The next step is to write code that binds these functions to keystrokes in HTML mode's keymap, which is called html-mode-map, using the techniques described in Chapter 10. Assume you want to bind these functions to C-c C-h (head) and C-c C-t (title). C-c is used as a prefix key in many Emacs modes, such as the language modes we saw in the last chapter. Again, this is no problem:

(define-key html-mode-map"C-cC-h" 'html-head)
(define-key html-mode-map"C-cC-t" 'html-title))

Finally, you need to convert these lines of Lisp into a value for html-mode-hook. Here is the code to do this:

(add-hook 'html-mode-hook
      '(lambda ( )
         (define-key html-mode-map"C-cC-h" 'html-head)
         (define-key html-mode-map"C-cC-t" 'html-title)))

If you put this code in your .emacs file, together with the earlier function definitions, you get the desired functionality whenever you use HTML mode.

If you try using these functions, though, you'll find they have some noticeable drawbacks compared to the other tag insertion commands in HTML mode. For one thing, while the other helper commands leave your cursor in between the opening and closing tags, our insertions leave the cursor after the closing tag, which is not only inconsistent, but it's much less helpful. Also, while the other tags you insert can be customized in terms of your preferred capitalization, or wrapped around existing content in the document, our simple-minded insert calls give us no such capabilities.

Luckily, it's not hard to add the smarts we want. It turns out that HTML mode is defined in the file sgml-mode.el (we learned this by applying help's handy describe-function command, C-h f, to the mode-defining function HTML mode. Armed with this knowledge, it was an easy matter to pull up and study the Lisp code that makes it work using the find-library-file utility shown in "A Treasure Trove of Examples" earlier in this chapter. A little quick hunting to find a parallel example revealed that the tag support is implemented using a skeletal function generator. Without going into too much detail, it turns out that the code we want to use is this:

(define-skeleton html-head
  "HTML document header section."
  nil
  "<head>" _ "</head>")
(define-skeleton html-title
  "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:

(add-hook 'c-mode-hook
      '(lambda ( )
         (c-set-style "stylename")
         (c-toggle-auto-state)))

whereas the code that sets an alternative C preprocessor command name for the c-macro-expand command did not:

(setq c-macro-preprocessor "/usr/local/lib/cpp -C")

Why is this? Actually, the correct way to customize any mode is through its hook—for example, the preceding example should really be:

(add-hook 'c-mode-hook
      '(lambda ( )
         (setq c-macro-preprocessor "/usr/local/lib/cpp -C")))

If you merely want to set values of variables, you can get away without a hook, but a hook is strictly required if you want to run functions like c-set-style or those used to bind keystrokes. The precise reason for this dichotomy takes us into the murky depths of Lisp language design, but it's essentially as follows.

Variables that are local to modes, like c-macro-preprocessor, do not exist if you don't invoke the mode in which they are defined. So, if you aren't editing C or C++ code, then c-macro-preprocessor doesn't exist in your running Emacs, because you haven't loaded C mode (see below). Yet if your .emacs file contains a setq to set this variable's value, then you call the variable into existence whether or not you ever use C mode. Emacs can deal with this: when it loads C mode, it notices that you have already set the variable's value and does not override it.

However, the situation is different for functions. If you put a call to a mode-local function like c-set-style in your .emacs file, then (in most cases) Emacs complains, with the message Error in init file, because it does not know about this function and thus cannot assume anything about what it does. Therefore you must attach this function to a hook for C mode: by the time Emacs runs your hook, it has already loaded the mode and therefore knows what the function does.

These examples of hooks are only the briefest indication of how far you can go in customizing Emacs's major modes. The best part is that, with hooks, you can do an incredible amount of customization without touching the code that implements the modes. In exchange, you should remember, when you do write your own modes, to think about useful places to put hooks so others can take advantage of them.

Îãëàâëåíèå êíèãè


Ãåíåðàöèÿ: 1.709. Çàïðîñîâ Ê ÁÄ/Cache: 3 / 1
ïîäåëèòüñÿ
Ââåðõ Âíèç