2

I have a program that looks something like (this is a silly example to illustrate my point, what it does is not very important)

count = 0

def average(search_term):
    average = 0
    page = 0
    current = download(search_term, page)
    while current:
        def add_up(downloaded):
            results = downloaded.body.get_results()
            count += len(results)
            return sum(result.score for result in results)
        total = average*count
        total += add_up(current)
        average = total/count
        print('Average so far: {:2f}'.format(average))
        page += 1
        current = download(search_term, page)

If I have the cursor on any of the lines 8–11 and press a key combination I want Emacs to copy or kill the add_up function, and then I want to move the cursor to line 2 and press a key combination and paste the function there, with the correct level of indentation for the context it is pasted in.

Is this possible, and if so, how would I do that?

2
  • python-mode.el provides a bunch of copy-functions. See in menu Edit. py-copy-def for example. Commented Dec 18, 2015 at 9:18
  • Note that the mode Andreas suggests is a completely different mode from the python-mode that ships with Emacs. Commented Dec 19, 2015 at 19:41

3 Answers 3

3

With python-mode.el py-kill-def and yank would do the job. However, there are some restrictions. py-kill-def must be called from inside def in question. So needs to go upward from line 11 first. Also indenting after insert poses some problems: as indent is syntax, sometimes Emacs can't know which indentation is wanted. In example below have an indent of 4 first and of 8 in add_up probably is not wanted - however it's legal code. After indenting first line in body of add_up, py-indent-and-forward should be convenient for the remaining.

def average(search_term):
    average = 0
    def add_up(downloaded):
            results = downloaded.body.get_results()
            count += len(results)
            return sum(result.score for result in results)
    page = 0
    current = download(search_term, page)
    while current:

        total = average*count
        total += add_up(current)
        average = total/count
        print('Average so far: {:2f}'.format(average))
        page += 1
        current = download(search_term, page)
Sign up to request clarification or add additional context in comments.

Comments

1

For this type of thing I usually use expand-region, which I choose to bind to C-=.

Using your example I can select the add_up() function by pressing C-= once, kill the region normally (C-k), move to line 2, and yank as usual (C-y).

Depending on what else you have configured for Python you may have to clean up some whitespace, or it may get cleaned up for you. For example, aggressive-indent would be helpful.

One manual option would be to reindent the pasted code with something like C-x C-x M-\.

Comments

1

I've been using smart-shift (available in Melpa) for this sort of thing. global-smart-shift-mode to enable (beware, it binds keys). Select the block you want to move (I'd use expand-region like Chris), and the default keybind C-S-c <arrow> starts moving it. Once you're shifting, the arrows (without C-S-c) shift further. Horizontal shifts use the major mode's indent offset (python-indent-offset for python.el).

3 Comments

It sounds like smart-shift might be a good alternative to drag-stuff, which I'm using now. Thanks for the tip, I'll check it out.
Do I understand it correctly that I will still need to indent "manually" with smart-shift? What is "smart" about smart-shift?
@krq: Yes, you'll have to manually position the block. It's "smart" because it knows about the indent offset for (many) major modes and moves horizontally according to that instead of by single columns. That's why I like it better than drag-stuff.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.