Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Emacs: how to write a defun which acts on region, but acts on point if there's no region?

Tags:

emacs

elisp

I write a simple defun for a region, and I want to apply it even if there's no region – i.e. call it with no selection at all. I thought I could do something like the following:

(defun region-study (strt end)
  (interactive "r")
  (if (= strt end)
    (progn ....) ;; then
    (progn ....))) ;; else

But it doesn't work. As it turns out, when you call (interactive "r") with no region it doesn't just set boundaries to be equal. Try this:

(defun region-study (strt end)
  (interactive "r")
  (message "strt=%d; end=%d" strt end))

So my question is that: "how to write a defun which acts on region, but acts on point if there's no region?"

Edit:

So I wanted to put selection in brackets or just to insert brackets and (backward-char 1). Here's a solution:

(defun put-in-lft-rit (lft rit)
  (interactive "k")
  (if (use-region-p) ;; act on region
    (progn 
      (setq pP (point)) 

      (setq strt (region-beginning))
      (setq end (region-end))

      (setq meat (buffer-substring-no-properties strt end))
      (setq news (concat lft meat rit))
      (delete-region strt end)
      (goto-char strt)
      (insert news)

      (if (= pP strt)
      (goto-char strt) ; then
      (goto-char (+ end 1))))  ; else
    (progn ;; act on point
      (insert lft rit)
      (backward-char 1))))

(defun bk-put-in-braces ()
  (interactive)
  (put-in-lft-rit "(" ")"))

(defun bk-put-in-curly-braces ()
  (interactive)
  (put-in-lft-rit "{" "}"))

(defun bk-put-in-quotes ()
  (interactive)
  (put-in-lft-rit "'" "'"))

(defun bk-put-in-double-quotes ()
  (interactive)
  (put-in-lft-rit "\"" "\""))

(defun bk-put-in-square-brackes ()
  (interactive)
  (put-in-lft-rit "[" "]"))

And then you bind in .emacs:

(global-set-key (kbd "C-<f9>") 'bk-put-in-square-brackes)
(global-set-key (kbd "<f9>") 'bk-put-in-curly-braces)
(global-set-key (kbd "S-<f7>") 'bk-put-in-quotes)
(global-set-key (kbd "S-<f8>") 'bk-put-in-double-quotes)
(global-set-key (kbd "S-<f9>") 'bk-put-in-braces)

That's it! Should be working in all modes.

Edit2:

@phils Thanks. You are definetely right. One thing though - my code had an additional feature of leaving the point at the beginning or end of the region - depending on where it was in the selection. Here's Your code with this feature added:

(defun put-in-lft-rit (lft rit)
  (interactive "k")
  (if (use-region-p) ;; act on region
      (let ((strt (region-beginning))
            (end (region-end))
            (pP (point)))

        (save-excursion
          (goto-char end)
          (insert rit)
          (goto-char strt)
          (insert lft))

        (if (= pP strt)
          (goto-char strt) ; then
          (goto-char (+ end 1))))  ; else

    (progn ;; act on point
      (insert lft rit)
      (backward-char 1))))
like image 689
Adobe Avatar asked Sep 12 '11 11:09

Adobe


3 Answers

A few notes on your solution...

  1. It's good practice to avoid unnecessary global-scope setqs. Use (let) instead to define a temporary scope for your variables.

  2. You are doing a lot more work than required. Instead of copying the region, concatenating that copy and the delimiters into a 'news' variable, deleting the region, and then inserting 'news', all you need to do is insert the delimiter characters at the beginning and end of the region.

    (In general, if you try to "think like an editor" when writing elisp, and focus on manipulating buffers rather than variables, you'll generally wind up with more efficient code.)

  3. save-excursion is very useful (along with several other save- and with- forms).

 

(defun put-in-lft-rit (lft rit)
  (interactive "k")
  (if (use-region-p) ;; act on region
      (let ((strt (region-beginning))
            (end (region-end)))
        (save-excursion
          (goto-char end)
          (insert rit)
          (goto-char strt)
          (insert lft)))
    (progn ;; act on point
      (insert lft rit)
      (backward-char 1))))
like image 93
phils Avatar answered Nov 15 '22 08:11

phils


use-region-p should return t if your function should act on the region instead of at a point.

like image 21
Wooble Avatar answered Nov 15 '22 10:11

Wooble


You may like to use the function region-or-word-at-point defined in thingatpt+.el

like image 38
RichieHH Avatar answered Nov 15 '22 10:11

RichieHH