elemakil
elemakil

Reputation: 3811

Multiple `interactive` options

I have written a small function to reverse the items in the marked region using the comma as item separator. The function code is:

(defun reverse-list (beg end)
  "Reverses a list in-place, where comma ',' is the list item separator."
  (interactive "r")
  (if (region-active-p)
    (let ((region-list (reverse (split-string (region-as-string) ","))))
      (kill-region beg end)
      (loop for s in region-list do (progn
                                      (insert (chomp s))
                                      (insert ", ")))
      (delete-char -2))
    (message "Error: No region selected!")))  

where chomp strips leading/trailing whitespace from a string and region-as-string yields the region as a string.

The function is very useful, however it would be great to be able to select the separator on the fly. The behaviour I'm looking for is:

I tried to achieve this, but haven't been successful in doing so. It would be great if you could provide help!

Thanks in advance,
elemakil

Upvotes: 4

Views: 903

Answers (2)

Gareth Rees
Gareth Rees

Reputation: 65854

You can use the interactive code P to read the "raw" prefix argument. This is nil if there was no prefix argument, and non-nil if there was a prefix argument, so you can test this and decide whether to set the separator to "," or to use read-string to prompt the user to enter it.

Also, I'd make the following comments on your code:

  1. Your function only works if called interactively, so that beg and end are really the beginning and end of the region. If called non-interactively, these might not match (or there might not even be a region) and in that case your function will go wrong (because it deletes the buffer between beg and end but inserts the reversal of the region). So you need to call (buffer-substring beg end), not (region-as-string).

  2. [Edited, see comments] It's not a good idea to raise an error in the case where the region is inactive. In Emacs, the region continues to exist even when it's inactive (that is, no longer highlighted). Or the user might have turned off transient-mark-mode and so never have an active region at all. In both cases, the user might still want to run your command.

    At most, you might change the behavior of the command when the region is active (see for example, comment-dwim).

  3. You delete the region by calling kill-region, which copies the removed text to the kill-ring. Is this really what you want to do? It might surprise the user. It's better to call delete-region unless you actually mean to save the deleted text.

  4. You don't ensure that point is in the right place before you start calling insert. In interactive use, you'll get away with it, but for non-interactive use point could be anywhere, so you ought to move it explicitly. And also use save-excursion, of course.

  5. It seems very inelegant to insert an extra ", " and then have to delete it afterwards. Better not to insert it in the first place.

Here's some revised code that fixes all of the above:

(defun reverse-list (beg end read-separator)
  "Reverse the region in-place, treating it as a list of items
separated by commas. With a prefix argument, prompt for the
separator."
  (interactive "r\nP")
  (save-excursion
    (let* ((separator (if read-separator (read-string "Separator: ") ","))
           (region-list (nreverse (split-string (buffer-substring beg end) separator)))
           (separator (concat separator " ")))
      (goto-char beg)
      (delete-region beg end)
      (loop for s in region-list
            for sep = "" then separator
            do (insert sep) (insert (chomp s))))))

Upvotes: 7

louxiu
louxiu

Reputation: 2915

r -- Region: point and mark as 2 numeric args, smallest first.  Does no I/O.

Because r is always the first two argument. If you use C-u to pass arguments. Then you may need to pass the argument to the third argument as separator. But you can not pass the third argument without the first and second argument. (I am not sure here. I am a beginner too)

I purpose you to use a r and a s option to achieve it. Though you always need to provide the separator argument.

Example:

(defun reverse-list (beg end &optional sepeartor )
  (interactive "r
ssepeartor:")
  (princ beg)
  (princ "*")
  (princ end)
  (princ sepeartor))

Upvotes: -1

Related Questions