11

I often do some testing implementation with hardcoded values which I later want to change. Like

ref = open('specific-file-name')

Which I would like to change into

ref = open(fname)

I'm used to vim and there I can type ci(fname assuming that point is inside the brackets. (ci( stands for replace inner brackets).

How can I achieve something like 'replace inner ' in emacs?

4 Answers 4

13

Using expand-region

I would recommend installing the expand-region package (available on Melpa too). It would be useful for editing situations mentioned in your example and lot more.

expand-region is intelligent. Based on the major mode, it will try to figure out what you are trying to select. I don't know from which language you have the example snippet so I will use your example code snippet and slightly modify it for Python (remove ;). So my below explanation is with the below code snippet in a python-mode buffer.

ref = open('specific-file-name') 

If point is next to a single quote

The black vertical rectangles below indicate how the cursor will look.

# point on left of left quote, the cursor is hiding the quote under it
ref = open(▮specific-file-name') 

# point on left of right parenthesis, the cursor is hiding the parenthesis under it       
ref = open('specific-file-name'▮

In either case, when you do M-x er/expand-region, 'specific-file-name' will get selected and you can then hit DEL (or backspace) to delete the selection.

If you have delete-selection-mode enabled (I like to have that enabled personally), you can start typing away after doing the selection and that will overwrite the selection.

Of course, if you use er/expand-region a lot, you would bind it to a short key binding. I like to bind it to C-\. So with point next to a single quote: C-\fname will give me ref = open(fname);

enter image description here

If point is somewhere inside the quotes

Using expand-region, the point does not need to be near the quotes. But in this case, you will need just few more (usually one or two more) key strokes.

Let's say the cursor position is as below now:

# cursor shown below is hiding the i character under it
ref = open('specif▮c-file-name')

Using the same binding as I used above:

  • First time I hit C-\ will select specific.

  • Now I just need to hit \ to expand the selection (expand-region feature). That will select specific-file-name.

    If you had bound er/expand-region to C-=, hitting = again will do the incremental expansion. Read its documentation on how to binding keys for incremental expansion/contraction of selection.

  • Hitting \ once more will select 'specific-file-name'.

  • Now you proceed with hitting DEL to delete the selection and then type fname. If delete-selection-mode is enabled, you can skip the DEL stroke.

In summary, with delete-selection-mode enabled, the key strokes look like: C-\\\fname.

enter image description here


Using change-inner

I also came across change-inner package by the expand-region author magnars (which uses expand-region as back-end) to do exactly what the Vim ci does (and also change outer).

From the package README:

change-inner gives you vim's ci command, building on expand-region. It is most easily explained by example:

function test() {
  return "semantic kill";
}

With point after the word semantic

change-inner " would kill the contents of the string
change-outer " would kill the entire string
change-inner { would kill the return-statement
change-outer { would kill the entire block

0
4

With a region

To do this with a region, starting from:

ref = open(|'specific-file-name');

press C-SPC C-b C-M-n C-b C-w.

Faster way

Starting from the point before paren, just like in Vim:

ref = open|('specific-file-name');

C-M-k (kill-sexp) will result in:

ref = open|;

If you have electric-pair-mode on, you can re-insert () with only (. So the full key sequence is C-M-k ( - not much longer than ci(.

3
  • 1
    Also, if the point is before the left quote, ref = open(|'specific-file-name');, C-M-k will give ref = open(|);. Quotes are also balanced sexps. Jul 17, 2015 at 12:25
  • I was thinking of delete-everything-inside-parens sort of thing, not delete-first-sexp-inside-parens.
    – abo-abo
    Jul 17, 2015 at 13:08
  • Nice solution but it requires me to move point before the expression I want to replace. The 'change inner' package works when point is anywhere inside the expression (same as vi).
    – kain88
    Jul 17, 2015 at 13:25
1

evil emulates most of Vim, and it does text objects out of the box.

In other words, with evil activated and using the default key bindings, ci( will do exactly what you're asking: change what's inside the enclosing parentheses.

More precisely, ci( will chain evil-change to evil-inner-paren. You can also create your own arbitrary text objects as you like.

4
  • I know about evil (I've used it for a while now). I would like to know how this can be done in 'pure' emacs.
    – kain88
    Jul 17, 2015 at 11:47
  • @kain88: fair enough: I figure there will be multiple answers to this question. Out of curiosity, though, if you already use evil, why would you want the non-evil alternative?
    – Dan
    Jul 17, 2015 at 12:11
  • (non-serious comment) @Dan He's probably preparing to come to the dark side. Jul 17, 2015 at 12:26
  • I noticed that evil sometimes doesn't play well with other modes. Plus I'm curious to see how editing would be using just emacs
    – kain88
    Jul 17, 2015 at 13:23
1

Using smartparens

Kill

1) Use C-M-u (sp-backward-up-sexp) to get out of ' delimiters.

2) Use C-0 C-M-k (0 prefix + sp-kill-sexp) to kill stuff inside current sexp or C-k (sp-kill-hybrid-sexp). The latter only works when smartparens-strict-mode is enabled and current sexp fits in one line.

ref = open('specific|-file-name')
// C-M-u
ref = open(|'specific-file-name')
// C-k
ref = open(|)

Mark and delete/kill/copy

1) Find your way to the beginning of sexp (in this example C-M-u sp-backward-up-sexp is enough), set mark C-SPC and go to the end of sexp with sp-end-of-sexp.

2) Then you can do whatever you wish: C-w to kill region, M-w to kill-ring-save (copy) region, backspace to delete it without cluttering kill ring (only with delete-selection-mode)

ref = open('specific|-file-name')
// C-M-u
ref = open(|'specific-file-name')
// C-SPC - mark is now active
ref = open(|'specific-file-name')
// sp-end-of-sexp (my binding: M-e)
ref = open(<'specific-file-name'|)
// backspace
ref = open(|)

Mark-inside-sexp function

smartparens package has sp-mark-sexp function that marks whole next sexp, i.e. delimiters and stuff between them.

You could use a function like this to throw delimiters out of the selection:

(defun mark-inside-sexp-using-sp ()
  (interactive)
  (sp-mark-sexp)
  (exchange-point-and-mark) (backward-char)
  (exchange-point-and-mark) (forward-char)
  (message "MARK INSIDE SEXP (using sp)"))

(global-set-key (kbd "s-SPC") 'mark-inside-sexp-using-sp)
;; this is "super" modifier (win key) with space

Get out of your sexp using C-M-u repeatedly and fire mark-inside-sexp-using-sp and then decide what to do with active region.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.