23

In GNU Emacs, I want to run a program, figlet, on the currently selected text. I then want to comment the region which is produced.

I have figured out how to do it using the standard Emacs commands:

  • set mark with C-<space> at the start of the word
  • move cursor to the end of the word
  • C-u M-x shell-command-on-region RET figlet RET
  • M-x comment-region RET

However, I have failed to work out how to write an Emacs lisp program to do all this. Here is my attempt:

(defun figlet-region () 
  (interactive)
  (push-mark)
  (shell-command-on-region "figlet")
  (comment-region (mark) (point))
  (pop-mark)
)

(global-set-key "\C-c\C-f" 'figlet-region)

Then C-<space>; M-x figlet-region produces garbage:

figlet-region: Wrong number of arguments: #[(start end command &optional output-buffer replace error-buffer display-error-buffer) "ÆÇÈ  
\"!É
'jÊ!j;j
0Wb
?Ë`Ì\"Í ÎQÎDRÎÉ!\"&
ffÏ )ãÐqÑ!#Ò#p=¬É$]d|e^|Íed ΠÎD¡ÎÉ!\"&â%qÉ$Á&%Ó *Í ÉØ#DÚ#É!\"&*#Ô!#ÕÖ×!8WrÐ!qd`Z'o   ØcÙÉ\"d'Zb)(Úp!)Û!*" [error-buffer small-temporary-file-directory temporary-file-directory exit-status error-file replace make-temp-file expand-file-name "scor" nil ...] 9 1945557 (let (string) (unless (mark) (error "The mark is not set now, so there is no region")) (setq string (read-from-minibuffer "Shell command on region: " nil nil nil (quote shell-command-history))) (list (region-beginning) (region-end) string current-prefix-arg current-prefix-arg shell-command-default-error-buffer t))], 1

Answer

(defun figlet-region (&optional b e) 
  (interactive "r")
  (shell-command-on-region b e "figlet" (current-buffer) t)
  (comment-region (mark) (point)))

(This is based on Trey Jackson's answer.)

Example (Lisp Interaction mode)

;;  _   _                 _        
;; | |_| |__   __ _ _ __ | | _____ 
;; | __| '_ \ / _` | '_ \| |/ / __|
;; | |_| | | | (_| | | | |   <\__ \
;;  \__|_| |_|\__,_|_| |_|_|\_\___/

Example (CPerl mode)

#  _   _                 _        
# | |_| |__   __ _ _ __ | | _____ 
# | __| '_ \ / _` | '_ \| |/ / __|
# | |_| | | | (_| | | | |   <\__ \
#  \__|_| |_|\__,_|_| |_|_|\_\___/
Paulo Tomé
  • 1,910
  • 3
  • 18
  • 27

3 Answers3

25

I'm unsure what you're trying to accomplish with the pushing and popping of the marks, I believe you'd get the same functionality by doing this:

(defun figlet-region (&optional b e) 
  (interactive "r")
  (shell-command-on-region b e "figlet")
  (comment-region b e))

The argument to interactive tells Emacs to pass the region (point and mark) in as the first two arguments to the command.

Trey Jackson
  • 73,529
  • 11
  • 197
  • 229
  • note that `shell-command-on-region` may modify the region, so the last line should read `(comment-region (mark) (point))` as in my answer. – sds Apr 01 '14 at 15:34
  • In the form used in my answer, it will not modify the region (unless you're calling it from the buffer `*Shell Command Output*` which is just weird). If you're going to use `(mark)` and `(point)` - there's no point in passing in the region via arguments, for it will fail to work as expected when *not* called interactively. – Trey Jackson Apr 01 '14 at 22:37
  • imagine `shell-command-on-region` replaces the region `b`-`e` with an empty string. will then `comment-region` DRT? – sds Apr 01 '14 at 23:16
  • Yes, *if* `shell-command-on-region` were called with more arguments, *then* what you're saying is correct. But it is *not* (in my example), so your point doesn't apply. My answer doesn't solve the entirety of his question, but he's figured it out. – Trey Jackson Apr 02 '14 at 15:02
9

It is not a very good idea to use an interactive command like shell-command-on-region in a lisp program. You should use call-process-region instead:

(defun figlet-region (&optional b e) 
  (interactive "r")
  (call-process-region b e "figlet" t t)
  (comment-region (mark) (point)))

It should be more resilient against various user options.

sds
  • 58,617
  • 29
  • 161
  • 278
5

Well, I'm not sure where the garbage is coming from, but the error itself is coming from shell-command-region. When used in elisp, it expects at least 3 arguments, START END and COMMAND.

Also, in general, it is bad practice to mess with the mark in functions. Here is what the doc of push-mark has to say on the subject:

Novice Emacs Lisp programmers often try to use the mark for the wrong purposes. See the documentation of `set-mark' for more information.

Bahbar
  • 17,760
  • 43
  • 62