13

Does emacs have the equivalent of VIMs ci" command? Basically I want to replace the text inside a set of quotation marks.

In vim I can ci) or ci] or ci} to "change inner *"...

10 Answers 10

19

Off the top of my head, the closest command is M-z " which deletes everything from point to the next occurance of the " character.

There is also C-M-k, aka "kill balanced expression", which will delete a full parenthesized statement or double quoted string etc. based on the current modes definition of "balanced expression" and the character currently under point (ie. it only works if the cursor is on the opening '"' or '(' etc.).

7

Similarly to Justin's suggestion C-M-SPACE gives you "mark-sexp" which will select to the balancing paren, quote, etc. and then you can C-w or whatever to make it go away. In case you want to SEE what you're about to delete before you delete it...

6

Yes! The equivalent of VIMs ci" command in Emacs is... ci" :-)
http://www.emacswiki.org/emacs-de/Vimpulse

3

Just stumbled upon this question; here is a custom solution that's worked for me:

(defun seek-backward-to-char (chr)
  "Seek backwards to a character"
  (interactive "cSeek back to char: ")
  (while (not (= (char-after) chr))
    (forward-char -1)))


(defun delete-between-pair (char)
  "Delete in between the given pair"
  (interactive "cDelete between char: ")
  (seek-backward-to-char char)
  (forward-char 1)
  (zap-to-char 1 char)
  (insert char)
  (forward-char -1))

Then bind delete-between-pair to whatever key you like. For me, I have it bound on C-z i.

2

I'm afraid I don't know about VIM's ci feature, but have you looked at Emacs regexp replace? I can't speak to the exact semantics or how easy it is to use in comparison, but it's what I would use for what I think you want.

1
  • emacs regexp replace is the equivalent of vi :s/regex/replacement/, what he wants is a keypress to delete and replace the current balanced expression at point. Mar 26, 2010 at 12:53
2

I've made a minor mode which provides some of those Vim facilities called Markit.

1

Here's my version which will delete everything within (or including) a matching pair of characters. The character pairs are defined in a list so that the matching start/end character are known. I've mapped it to "C-c i" for change in and "C-c a" for change all of.

It also copies the removed characters to the clip board for pasting later.

; Re-create ci" ca"...
(defun seek-backward-to-char (chr)
  "Seek backwards to a character"
  (interactive "cSeek back to char: ")
  (while (not (= (char-after) chr))
    (forward-char -1)))

(setq char-pairs
      '(( ?\" . ?\" )
        ( ?\' . ?\' )
        ( ?\( . ?\) )
        ( ?\[ . ?\] )
        ( ?\{ . ?\} )
        ( ?<  . ?>  )))

(defun get-char-pair (chr)
  (let ((result ()))
    (dolist (x char-pairs)
      (setq start (car x))
      (setq end (cdr x))
      (when (or (= chr start) (= chr end))
        (setq result x)))
      result))

(defun get-start-char (chr)
  (car (get-char-pair chr)))
(defun get-end-char (chr)
  (cdr (get-char-pair chr)))

(defun seek-to-matching-char (start end count)
  (while (> count 0)
    (if (= (following-char) end)
        (setq count (- count 1))
      (if (= (following-char) start)
          (setq count (+ count 1))))
    (forward-char 1)))

(defun seek-backward-to-matching-char (start end count)
  (if (= (following-char) end)
      (forward-char -1))
  (while (> count 0)
    (if (= (following-char) start)
        (setq count (- count 1))
      (if (= (following-char) end)
          (setq count (+ count 1))))
    (if (> count 0)
        (forward-char -1))))

(defun delete-between-pair (char)
  "Delete in between the given pair"
  (interactive "cDelete between char: ")
  (seek-backward-to-matching-char (get-start-char char) (get-end-char char) 1)
  (forward-char 1)
  (setq mark (point))
  (seek-to-matching-char (get-start-char char) (get-end-char char) 1)
  (forward-char -1)
  (kill-region mark (point)))

(defun delete-all-pair (char)
  "Delete in between the given pair and the characters"
  (interactive "cDelete all char: ")
  (seek-backward-to-matching-char (get-start-char char) (get-end-char char) 1)
  (setq mark (point))
  (forward-char 1)
  (seek-to-matching-char (get-start-char char) (get-end-char char) 1)
  (kill-region mark (point)))

(global-set-key (kbd "C-c i") 'delete-between-pair)
(global-set-key (kbd "C-c a") 'delete-all-pair)
0

Magnars (author of EmacsRocks site) wrote this plugin to do exactly what you are asking for.

https://github.com/magnars/change-inner.el

Obviously, you could also use Evil mode.

0

This was something that I was missing from Vim, and zap-to-char didn't seem to cut it right.

Here is my humble attempt to recreate "ci" and "ca":

(defun change-outer (str)
  (interactive "sChange outer: ")
  (condition-case nil
      (search-backward str (line-beginning-position))
    (error (search-forward str (line-end-position))
       (forward-char -1)))
  (kill-sexp)
)

(defun change-inner (str)
  (interactive "sChange inner: ")
  (condition-case nil
      (search-backward str (line-beginning-position))
    (error (search-forward str (line-end-position))
       (forward-char -1)))
  (push-mark)
  (forward-sexp)
  (forward-char -1)
  (exchange-point-and-mark)
  (forward-char 1)
  (kill-region (point) (mark))
)

Normally, the condition-case is not necessary, since the third (optional) parameter of search-forward/search-backward is meant to indicate what to do in case the search fails. But for some reason, placing a the second search as the third parameter for the first one yields strange behavior.

0

I tried the solutions here, but found each of them wanting in some way, so I came up with this. It accepts either a beginning or ending delimiter, and uses built-in Emacs functions to avoid needing a translation table for delimiters.

(defun change-inner (prefix character)
  "Kill region inside delimiters, using either beginning or
ending delimiter.  With prefix arg, kill including delimiters."

  (interactive "p\nc")
  (let ((initial-point (point))
        (start)
        (end)
        (move-point-by (if (> prefix 1) 0 1)))

    (condition-case nil
        (progn
          ;; Search forward for given char
          (search-forward (char-to-string character))
          (setq end (- (point) move-point-by))

          (condition-case nil
              (backward-sexp)
            (error (backward-list)))

          (setq start (+ (point) move-point-by))
          (kill-region start end)
          (or prefix (forward-char)))

      (error (progn
               ;; Reset and search backward for given char
               (goto-char initial-point)
               (search-backward (char-to-string character))
               (setq start (+ (point) move-point-by))

               (condition-case nil
                   (forward-list)
                 (error (forward-sexp))))

             (setq end (- (point) move-point-by))
             (kill-region start end)
             (or prefix (backward-char))))))
(global-set-key (kbd "M-i") 'change-inner)

(defun change-outer ()
  (interactive)
  (let ((current-prefix-arg '(4)))
    (call-interactively 'change-inner)))
(global-set-key (kbd "M-o") 'change-outer) 

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .