5

For example, I want to sort a file, then remove the duplicate lines. I run M-x sort followed by M-x delete-duplicate-lines. I wanted to combine them into a single interactive function. Here is an attempt at solving this.

(defun sort-and-dedup ()
 ""
 (interactive)
 (sort)
 (delete-duplicate-lines)
)

This gives an error saying the underlying functions don't have sufficient number of arguments. I kind of get why that is the case (the interactive vs non-interactive argument, among a few others), but I don't know what to do exactly. Can you tell me how to correct this?

Edit 1:

The above code is wrong in many ways. Look at the accepted answer for information. And thanks for the help.

Edit 2:

This is what I intended to do.

(defun sort-and-dedup-region (beg end)
  (interactive "r")
  (sort-lines nil beg end)
  (delete-duplicate-lines beg end nil t))

(defun sort-and-dedup ()
  (interactive)
  (sort-and-dedup-region
   (point-min)
   (point-max)))

nomad
  • 247
  • 1
  • 6
  • Start by reading the docstrings to the functions to see what arguments they take. For example, you can do `C-h f` and then ask for `sort` or `delete-duplicate-lines`. (`C-h f` is the default keybinding for the command `describe-function`.) – Dan Sep 18 '20 at 17:14
  • @Dan, I did, both the functions have arguments, but I didn't get what to put exactly. The interactive commands didn't need any arguments though. – nomad Sep 18 '20 at 17:28
  • The docstrings tell you what arguments you need. An alternative is that you can use the function `call-interactively` to call these functions from your elisp code as interactive commands. – Dan Sep 18 '20 at 17:30
  • I did as you said. Error: `Symbol's value as variable is void: sort` – nomad Sep 18 '20 at 17:47
  • Have a look at the [elisp manual node on `call-interactively`](https://www.gnu.org/software/emacs/manual/html_node/elisp/Interactive-Call.html). – Dan Sep 18 '20 at 17:52
  • Can you give the solution for this time? There is some dissonance in the documentation. Due to my misunderstanding of the context, I can't figure out what to draw from the errors. – nomad Sep 18 '20 at 18:11
  • Do you mean `sort-lines`? `sort` is a generic sorting function that's used by all the higher-level sorting functions but it itself is non-interactive. That's the first hint. The second hint is: when you said `M-x sort-lines` did you have to do any preparation beforehand? – NickD Sep 18 '20 at 18:27
  • 1
    "The interactive commands didn't need any arguments though" -- in fact they *do* need arguments. Whichever arguments are mandatory for non-interactive calls must also be established for interactive calls. It is the responsibility of the function's `interactive` form to supply those arguments (which may or may not involve prompting the user for any of the values, but which necessarily *must* result in values for all of the required arguments.) – phils Sep 19 '20 at 02:25
  • @phils, I was wrong, please disregard that. – nomad Sep 19 '20 at 06:15

1 Answers1

5

[I believe you meant sort-lines, not sort - that's the only way the question makes sense, so I am going to assume it.]

Both sort-lines and delete-duplicate-lines operate on the selected region. You must select a region before calling them, otherwise they will complain that there is no region. So you are going to have to do the same thing in your function: assume that you are given a region and complain if there is none.

A region is specified by two positions in the buffer, conventionally named BEG and `END. So your function will look like this:

(defun sort-and-dedup (beg end)
   (interactive <mumble>)
   (sort-lines nil beg end)
   (delete-duplicate-lines beg end))

The first argument to sort lines tells it whether to sort in reverse order.

When you call the function interactively, you specify a region by setting a mark at one end and then moving point to the other end (or by starting at one end and dragging with the mouse to the other end). So how do you communicate those positions to the function? By giving an argument to interactive: if you look at its doc string (with C-h f interactive RET), you will find that r is what you need in order to specify a region - Emacs will arrange to translate the region you chose (however you chose it) into a pair of positions (BEG and END) that will be passed to your function.

So the function looks like this:

(defun sort-and-dedup (beg end)
   (interactive "r")
   (sort-lines nil beg end)
   (delete-duplicate-lines beg end))

Alternatively, as @Dan suggests, you can use call-interactively and let each function figure out what it needs. But you will still have to specify a region beforehand:

(defun sort-and-dedup ()
   (interactive)
   (call-interactively #'sort-lines)
   (call-interactively #'delete-duplicate-lines))

EDIT: incorporating @phil's suggestion (and now I see that you have actually implemented this and added it to your question), you can write a function that calls (the first definition of) sort-and-dedup with the necessary arguments to operate on the whole buffer:

(defun sort-and-dedup-whole-buffer()
   (interactive)
   (sort-and-dedup (point-min) (point-max)))

You can bind it to a key sequence if you are going to do that frequently:

(define-key global-map (kbd "M-S-<f10>" #'sort-and-dedup-whole-buffer)

although I would personally not do that: key sequences are a scarce commodity, so I would try it using M-x sort-and-dedup-whole-buffer RET for a while; if that becomes a nuisance, then I would bind it to a key sequence.

I also revisit all my key defs every couple of years and reclaim ones that I don't use any more. I keep them in their own file, loaded explicitly by my init file, so I can find and review them easily.

NickD
  • 27,023
  • 3
  • 23
  • 42
  • Thanks man. I am pretty sure it was `sort`. `sort-lines` requires you to mark a region. `sort` doesn't. Also, I don't think `delete-duplicate-lines` asked for a region either. – nomad Sep 18 '20 at 20:23
  • 1
    But `sort` takes a sequence as argument and it is not interactive: do `C-h f sort RET`. I just tried `delete-duplicate-lines` with no mark set (i.e. no region). I get `Debugger entered--Lisp error: (error "The mark is not set now, so there is no region")`. – NickD Sep 18 '20 at 20:35
  • So sorry dude, I was using `evil-mode`'s `:` instead of `M-x`. I assumed they were the same. But there are little differences (like this one). `:sort` works without a region but `M-x sort` is not an interactive function. I'll mark your answer. – nomad Sep 19 '20 at 05:44
  • Also, can you tell me if there is a way to automatically select the whole buffer for the command? I know there's `mark-whole-buffer`, but how do you use it in conjunction with the given commands? – nomad Sep 19 '20 at 06:07
  • 1
    To act on the whole buffer, pass `(point-min)` and `(point-max)` as the beginning/end arguments in your function calls. – phils Sep 19 '20 at 06:39