Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Refactoring in Emacs

I am working on splitting code into smaller files and refactoring it a bit. Consider the following code below as the section I want to extract:

(require 'package)
(add-to-list 'package-archives
             '("marmalade" . "http://marmalade-repo.org/packages/") t)
(package-initialize)
(when (not package-archive-contents)
  (package-refresh-contents))

(defvar my-packages '(org magit)
  "A list of packages to ensure are installed at launch.")

(dolist (p my-packages)
  (when (not (package-installed-p p))
    (package-install p)))
  • I want to take the section above and replace it with something like (require `file-name)
  • Then take the text replaced and place that in a new file in the current directory named file-name.el
  • And then add a line to the top of the file (provides `file-name)

It would be great if I could hit a keychord and then type a name and have this happen. If there is an easy way to do this then I would love to hear possible solutions.

Edit: I'm starting a bounty because I think this applies to more types of code than Lisp and I would like to have something a little more general that I can expand upon.

I have considered yasnippet but I don't think it's powerful enough to perform the task at hand. Basically the ideal workflow would be marking the lines to be extracted, replacing that with an appropriate require or include directive and sending the text off to it's own file. Ideally one command and something that is aware of the type of file being edited or at least the major mode so the behavior can be customized, again yasnippet is good at performing different tasks when editing in different major modes however I would have no idea how to make that work or evaluate the possibility of making it work.

Let me know if you need any more information.

like image 382
Devin M Avatar asked Dec 22 '11 06:12

Devin M


2 Answers

A general solution to this type of problem are keyboard macros (not to be confused with (Emacs) LISP macros). Basically Emacs allows you to record a sequence of keystrokes and "play them back" afterwards. This can be a very handy tool in situations where writing custom LISP code seems overkill.

For instance you could create the following keyboard macro (type the key combinations on the left hand side, the right hand side shows explanations for each key stroke):

C-x (                            ; start recording a keyboard macro
C-x h                            ; mark whole buffer
C-w                              ; kill region
(require 'file-name)             ; insert a require statement into the buffer
C-x C-s                          ; save buffer
C-x C-f                          ; find file
file-name.el <RET>               ; specify the name of the file
M-<                              ; move to the beginning of the buffer
C-u C-y                          ; insert the previously killed text, leaving point where it is
(provide 'file-name) <RET> <RET> ; insert a provide statement into the buffer
C-x )                            ; stop recording the keyboard macro

Now you can re-play that macro in some other buffer by typing C-x e, or save it for later use. You can also bind a macro to a shortcut just like a function.

However, there is one weakness with this approach: you want to be able to actually specify the file-name, and not just use the string "file-name" every time. That is a bit difficult - by default, keyboard macros provide no general facility for querying the user (except the very minimal C-x q, as documented here).

The Emacs Wiki has some work-arounds for that, however, instead of prompting the user in the minibuffer, it can sometimes be sufficient to start the macro by killing the current line and saving its text to a register.

C-x (
C-e C-<SPC> C-a    ; mark current line
C-x r s T          ; copy line to register T
C-k C-k            ; kill current line
...                ; actual macro
C-x )

Now when you want to use your macro, you would first write the desired file-name in an otherwise empty line, and then do C-x e in that line. Whenever the value of the file-name is needed in the macro you can retrieve it from the register T:

C-x r i T          ; insert file-name into buffer

For instance, for the provide statement in the above macro, you could write: (provide ' C-x r i T ). Note that this technique (inserting) also works in the minibuffer, and of course you could save multiple lines to different registers.

May sound complicated, but is actually quite easy in practice.

like image 190
Thomas Avatar answered Oct 18 '22 15:10

Thomas


Slightly tested:

(defun extract-to-package (name start end)
  (interactive (list (read-string "Package name to create: ")
                     (region-beginning) (region-end)))
  (let ((snip (buffer-substring start end)))
    (delete-region start end)
    (insert (format "(require '%s)\n" name))
    (with-current-buffer (find-file-noselect (concat name ".el"))
      (insert snip)
      (insert (format "(provide '%s)\n" name))
      (save-buffer))))
like image 38
huaiyuan Avatar answered Oct 18 '22 15:10

huaiyuan