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

11.1.3 Turning Lisp Functions into Emacs Commands

11.1.3 Turning Lisp Functions into Emacs Commands

The count-words-buffer function that we've just finished works, but it still isn't as convenient to use as the Emacs commands you work with daily. If you have typed it in, try it yourself. First you need to get Emacs to evaluate the lines you typed in, thereby actually defining the function. To do this, move your cursor to just after the last closing parenthesis in the function and type C-j (or Linefeed)—the "evaluate" key in Lisp interaction mode—to tell Emacs to perform the function definition. You should see the name of the function appear again in the buffer; the return value of the defun function is the symbol that has been defined. (If instead you get an error message, double check that your function looks exactly like the example and that you haven't typed in the line numbers, and try again.)

Once the function is defined, you can execute it by typing (count-words-buffer) on its own line in your Lisp interaction window, and once again typing C-j after the closing parenthesis.

Now that you can execute the function correctly from a Lisp interaction window, try executing the function with M-x, as with any other Emacs command. Try typing M-x count-words-buffer Enter: you will get the error message [No match]. (You can type C-g to cancel this failed attempt.) You get this error message because you need to "register" a function with Emacs to make it available for interactive use. The function to do this is interactive, which has the form:

(interactive "prompt-string")

This statement should be the first in a function, that is, right after the line containing the defun and the documentation string (which we will cover shortly). Using interactive causes Emacs to register the function as a command and to prompt the user for the arguments declared in the defun statement. The prompt string is optional.

The prompt string has a special format: for each argument you want to prompt the user for, you provide a section of prompt string. The sections are separated by newlines (n). The first letter of each section is a code for the type of argument you want. There are many choices; the most commonly used are listed in Table 11-2.

Table 11-2. Argument codes for interactive functions

Code User is prompted for:
b Name of an existing buffer
e Event (mouse action or function key press)
f Name of an existing file
n Number (integer)
s String
  Most of these have uppercase variations
B Name of a buffer that may not exist
F Name of a file that may not exist
N Number, unless command is invoked with a prefix argument, in which case use the prefix argument and skip this prompt
S Symbol

With the b and f options, Emacs signals an error if the buffer or file given does not already exist. Another useful option to interactive is r, which we will see later. There are many other option letters; consult the documentation for function interactive for the details. The rest of each section is the actual prompt that appears in the minibuffer.

The way interactive is used to fill in function arguments is somewhat complicated and best explained through an example. A simple example is in the function goto-percent, which we will see shortly. It contains the statement

(interactive "nPercent: ")

The n in the prompt string tells Emacs to prompt for an integer; the string Percent: appears in the minibuffer.

As a slightly more complicated example, let's say we want to write our own version of the replace-string command. Here's how we would do the prompting:

(defun replace-string (from to)
  (interactive "sReplace string: nsReplace string %s with: ")
  ...)

The prompt string consists of two sections, sReplace string: and sReplace string %s with:, separated by a Newline. The initial s in each means that a string is expected; the %s is a formatting operator (as in the previous message function) that Emacs replaces with the user's response to the first prompt. When applying formatting operators in a prompt, it is as if message has been called with a list of all responses read so far, so the first formatting operator is applied to the first response, and so on.

When this command is invoked, first the prompt Replace string: appears in the minibuffer. Assume the user types fred in response. After the user presses Enter, the prompt Replace fred with: appears. The user types the replacement string and presses Enter again.

The two strings the user types are used as values of the function arguments from and to (in that order), and the command runs to completion. Thus, interactive supplies values to the function's arguments in the order of the sections of the prompt string.

The use of interactive does not preclude calling the function from other Lisp code; in this case, the calling function needs to supply values for all arguments. For example, if we were interested in calling our version of replace-string from another Lisp function that needs to replace all occurrences of "Bill" with "Deb" in a file, we would use

(replace-string "Bill" "Deb")

The function is not being called interactively in this case, so the interactive statement has no effect; the argument from is set to "Bill," and to is set to "Deb."

Getting back to our count-words-buffer command: it has no arguments, so its interactive command does not need a prompt string. The final modification we want to make to our command is to add a documentation string (or doc string for short), which is shown by online help facilities such as describe-function(C-h f). Doc strings are normal Lisp strings; they are optional and can be arbitrarily many lines long, although, by convention, the first line is a terse, complete sentence summarizing the command's functionality. Remember that any double quotes inside a string need to be preceded by backslashes.

With all of the fixes taken into account, the complete function looks like this:

(defun count-words-buffer ( )
  "Count the number of words in the current buffer;
print a message in the minibuffer with the result."
  (interactive)
  (save-excursion
    (let ((count 0))
      (goto-char (point-min))
      (while (< (point) (point-max))
        (forward-word 1)
        (setq count (1+ count)))
      (message "buffer contains %d words." count))))

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


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