1

I am trying to write a small code snippet in Elisp.

Basically, I want to create a function so that the content of the whole buffer is copied and, then, copied to the kill ring.

I can achieve this by the execution of command M-x mark-whole-buffer and M-x copy-region-as-kill. It works.

Hence, I decided to translate it to Elisp code. After opening a tiny Elisp evaluator with M-x eval-expression I inserted:

(copy-region-as-kill (mark-whole-buffer))

The mark-whole-buffer part seems to work. However, copy-region-as-kill does not work. Emacs echoes the following error message:

eval: Wrong number of arguments: (2 . 3), 0

The documentation indicates:

copy-region-as-kill is an interactive compiled Lisp function in
‘simple.el’.

(copy-region-as-kill BEG END &optional REGION)

Save the region as if killed, but don’t kill it.
In Transient Mark mode, deactivate the mark.
If ‘interprogram-cut-function’ is non-nil, also save the text for a window
system cut and paste.

The copied text is filtered by ‘filter-buffer-substring’ before it is
saved in the kill ring, so the actual saved text might be different
from what was in the buffer.

When called from Lisp, save in the kill ring the stretch of text
between BEG and END, unless the optional argument REGION is
non-nil, in which case ignore BEG and END, and save the current
region instead.

I thought I was sending the region optional argument.

What is wrong with my approach? How can I fix it?

Obs.: I am building this as an intermediary step for this problem.

Pedro Delfino
  • 1,369
  • 3
  • 13

2 Answers2

7

What's wrong with your attempt

(copy-region-as-kill (mark-whole-buffer))

This calls the function copy-region-as-kill with one argument, which is the value returned by mark-whole-buffer. All Emacs Lisp functions return exactly one value.

copy-region-as-kill expects either two or three arguments: it has two mandatory arguments start and end, and an optional argument region. You aren't “sending the region optional argument”: you never indicated that the argument you're passing is supposed to be region, and anyway Emacs Lisp doesn't have a way to do that. Whatever you're passing is the first argument, which will be the value of start.

Since copy-region-as-kill expects at least 2 arguments and at most 3, but you're passing 1, this triggers an error:

(wrong-number-of-arguments (2 . 3) 1)

That is: the function expected between 2 and 3 arguments, but got 1. The error specifies 1, not 0: if you got an error with 0, it must have been from the code (copy-region-as-kill).

The documentation of mark-whole-buffer does not document what value it returns. (As of Emacs 26.3, it happens to return 1, or a larger value in a minibuffer, but this is just an accident of how the function was written, not meant to be useful, and it could change in other versions since it's not documented.)

The documentation of mark-whole-buffer also states

This function is for interactive use only.

which leads us to…

What you should do

Commands should [set the mark] only if it has a potential use to the user, and never for their own internal purposes

The mark is meant for interactive use. It's a way to designate a portion of the buffer so that commands that operate on a portion of the buffer will act on that portion. It saves every such command from having to implement a mechanism to select part of the buffer. In a Lisp program, you don't need that. You can use variables (or more generally Lisp expressions) to designate a part of the buffer. Commands that operate on the region are generally implemented by a function that takes two arguments, the boundaries of the region (conventionally called start and end).

So instead of calling mark-whole-buffer to say “I want the whole buffer”, pass the boundaries of the buffer: (point-min) and (point-max). (Replace (point-min) by (minibuffer-prompt-end) if you want to exclude the prompt when your function is called from a minibuffer.)

(copy-region-as-kill (point-min) (point-max))

Furthermore, copy-region-as-kill is also intended for interactive use has some fancy behavior you probably don't want, such as potentially appending to the kill ring instead of setting it. However, another fancy behavior is to allow modes to filter the text, and that might be desirable. The function to just push some text into the kill ring is kill-new.

(kill-new (filter-buffer-substring beg end))
1

I thought I was sending the region optional argument.

What it is wrong with my approach? How can I fix it?

Function arguments in Emacs Lisp are positional. The first value specified by the caller is assigned to the first argument, the second value is assigned to the second argument, etc. The names and data types don’t matter, only the order.

The function you were calling has two required arguments followed by one optional argument. This means that you are required to specify values for the first two arguments before you can give a value for the third (optional) one.

The function you wrote will work, but composing two function calls in sequence is more typically done by simply calling them in sequence like this:

(defun pmd/select-and-kill ()
  (mark-whole-buffer)
  (copy-region-as-kill (mark) (point)))

Using and like that is something you will see from time to time, but typically only when the author wants to make something conditional. As an example (and a b) only evaluates b if a returns nil. If a returns anything other than nil, b is skipped. In your case mark-whole-buffer always returns nil, so it does work. It does look a little funny though.

db48x
  • 15,741
  • 1
  • 19
  • 23